JaycePC 1 개월 전
커밋
75f4d06148
100개의 변경된 파일5800개의 추가작업 그리고 0개의 파일을 삭제
  1. 77 0
      .gitignore
  2. 1 0
      app/.gitignore
  3. 136 0
      app/build.gradle
  4. BIN
      app/libs/USBPrintSDK.jar
  5. BIN
      app/libs/arcsoft_face.jar
  6. BIN
      app/libs/arcsoft_image_util.jar
  7. BIN
      app/libs/armeabi-v7a/libserial_port_wh.so
  8. BIN
      app/libs/autoreplyprint.aar
  9. BIN
      app/libs/commons-codec-1.4.jar
  10. BIN
      app/libs/facedetector-1.0.0.aar
  11. BIN
      app/libs/flowlayout-1.0.0.aar
  12. BIN
      app/libs/sdkapi.jar
  13. BIN
      app/libs/tbs_sdk_v4.3.0.165_20210628_103707.jar
  14. BIN
      app/libs/whdrawlabel1.1.jar
  15. 21 0
      app/proguard-rules.pro
  16. 26 0
      app/src/androidTest/java/xn/hxp/ExampleInstrumentedTest.java
  17. 310 0
      app/src/main/AndroidManifest.xml
  18. 8 0
      app/src/main/java/com/rc/core/event/RefreshEvent.kt
  19. 71 0
      app/src/main/java/com/rc/core/ui/ActivityCollector.kt
  20. 132 0
      app/src/main/java/com/rc/core/ui/activity/RcBaseActivity.kt
  21. 236 0
      app/src/main/java/com/rc/core/ui/activity/RcRefreshActivity.kt
  22. 39 0
      app/src/main/java/com/rc/core/ui/common/AbsUIDelegate.kt
  23. 27 0
      app/src/main/java/com/rc/core/ui/common/IUIListener.kt
  24. 104 0
      app/src/main/java/com/rc/core/ui/common/UIDelegateImpl.kt
  25. 104 0
      app/src/main/java/com/rc/core/ui/dialog/LoadingDialog.kt
  26. 113 0
      app/src/main/java/com/rc/core/ui/dialog/RcBaseDialog.kt
  27. 88 0
      app/src/main/java/com/rc/core/ui/fragment/RcBaseFragment.kt
  28. 37 0
      app/src/main/java/com/rc/core/ui/fragment/RcLazyFragment.kt
  29. 225 0
      app/src/main/java/com/rc/core/ui/fragment/RcRefreshFragment.kt
  30. 301 0
      app/src/main/java/com/rc/core/ui/widget/MultipleStatusView.kt
  31. 53 0
      app/src/main/java/com/rc/core/ui/widget/decoration/GridSpacingItemDecoration.java
  32. 148 0
      app/src/main/java/com/rc/core/ui/widget/decoration/NoLastLineItemDecoration.java
  33. 195 0
      app/src/main/java/com/rc/core/util/ApkController.kt
  34. 61 0
      app/src/main/java/com/rc/core/util/ApkUpdater.kt
  35. 208 0
      app/src/main/java/com/rc/core/util/CrashHandler.kt
  36. 120 0
      app/src/main/java/com/rc/core/util/CrashHelper.kt
  37. 102 0
      app/src/main/java/com/rc/core/util/DateUtils.kt
  38. 75 0
      app/src/main/java/com/rc/core/util/DeviceUtils.kt
  39. 68 0
      app/src/main/java/com/rc/core/util/EscapeUnescape.java
  40. 18 0
      app/src/main/java/com/rc/core/util/Extension.kt
  41. 32 0
      app/src/main/java/com/rc/core/util/FastClickDelegate.kt
  42. 69 0
      app/src/main/java/com/rc/core/util/Format.kt
  43. 162 0
      app/src/main/java/com/rc/core/util/MediaUtils.kt
  44. 131 0
      app/src/main/java/com/rc/core/util/ScreenAdapter.kt
  45. 50 0
      app/src/main/java/com/rc/core/util/VideoFullScreenWebChromeClient.kt
  46. 27 0
      app/src/main/java/com/rc/core/util/Weak.kt
  47. 155 0
      app/src/main/java/com/rc/core/util/WebViewHelper.kt
  48. 35 0
      app/src/main/java/com/rc/core/util/net/NetChangeListener.kt
  49. 20 0
      app/src/main/java/com/rc/core/util/net/NetConnectedListener.kt
  50. 145 0
      app/src/main/java/com/rc/core/util/net/NetWatchdog.kt
  51. 120 0
      app/src/main/java/com/rc/httpcore/HttpClient.kt
  52. 39 0
      app/src/main/java/com/rc/httpcore/HttpConfig.kt
  53. 91 0
      app/src/main/java/com/rc/httpcore/OkHttpUtils.java
  54. 41 0
      app/src/main/java/com/rc/httpcore/apk/AdminInfo.kt
  55. 9 0
      app/src/main/java/com/rc/httpcore/apk/AppInfo.kt
  56. 12 0
      app/src/main/java/com/rc/httpcore/apk/ChemicalExpire.kt
  57. 9 0
      app/src/main/java/com/rc/httpcore/apk/LabInfo.kt
  58. 43 0
      app/src/main/java/com/rc/httpcore/apk/ProcessedBean.kt
  59. 41 0
      app/src/main/java/com/rc/httpcore/apk/SafeInfo.kt
  60. 8 0
      app/src/main/java/com/rc/httpcore/apk/WarningNoticeLogVo.kt
  61. 22 0
      app/src/main/java/com/rc/httpcore/bean/AddChemical.java
  62. 3 0
      app/src/main/java/com/rc/httpcore/bean/AdminVoListBean.kt
  63. 12 0
      app/src/main/java/com/rc/httpcore/bean/BelongingPersonBean.kt
  64. 17 0
      app/src/main/java/com/rc/httpcore/bean/CabinetDoorVo.kt
  65. 6 0
      app/src/main/java/com/rc/httpcore/bean/CertitudeBean.java
  66. 26 0
      app/src/main/java/com/rc/httpcore/bean/ChemicalABean.kt
  67. 21 0
      app/src/main/java/com/rc/httpcore/bean/ChemicalConfsBean.kt
  68. 13 0
      app/src/main/java/com/rc/httpcore/bean/ChemicalDataBean.kt
  69. 20 0
      app/src/main/java/com/rc/httpcore/bean/ChemicalInfoBean.kt
  70. 33 0
      app/src/main/java/com/rc/httpcore/bean/ChemicalSearchBean.kt
  71. 14 0
      app/src/main/java/com/rc/httpcore/bean/ChemicalTimeoutListBean.kt
  72. 11 0
      app/src/main/java/com/rc/httpcore/bean/ChemicalTypeBean.kt
  73. 21 0
      app/src/main/java/com/rc/httpcore/bean/ChemicalWarnInfoBean.kt
  74. 26 0
      app/src/main/java/com/rc/httpcore/bean/ChemistryBean.kt
  75. 33 0
      app/src/main/java/com/rc/httpcore/bean/ConfigBean.kt
  76. 14 0
      app/src/main/java/com/rc/httpcore/bean/ConfigModel.kt
  77. 14 0
      app/src/main/java/com/rc/httpcore/bean/EarlyWarningBean.kt
  78. 10 0
      app/src/main/java/com/rc/httpcore/bean/EarlyWarningListBean.kt
  79. 27 0
      app/src/main/java/com/rc/httpcore/bean/GiveBackBean.kt
  80. 448 0
      app/src/main/java/com/rc/httpcore/bean/HxpChemicalVo.java
  81. 164 0
      app/src/main/java/com/rc/httpcore/bean/HxpControlConfig.java
  82. 66 0
      app/src/main/java/com/rc/httpcore/bean/HxpStockWaitListBean.kt
  83. 14 0
      app/src/main/java/com/rc/httpcore/bean/HxpStockWaitModeBean.kt
  84. 65 0
      app/src/main/java/com/rc/httpcore/bean/InventoryBean.kt
  85. 14 0
      app/src/main/java/com/rc/httpcore/bean/InventoryListBean.kt
  86. 12 0
      app/src/main/java/com/rc/httpcore/bean/LockVoListBean.kt
  87. 14 0
      app/src/main/java/com/rc/httpcore/bean/MsdsBean.kt
  88. 21 0
      app/src/main/java/com/rc/httpcore/bean/MsdsListBean.kt
  89. 11 0
      app/src/main/java/com/rc/httpcore/bean/PageHxpStockBean.kt
  90. 14 0
      app/src/main/java/com/rc/httpcore/bean/QueryTwoBean.kt
  91. 63 0
      app/src/main/java/com/rc/httpcore/bean/QueryTwoListBean.kt
  92. 81 0
      app/src/main/java/com/rc/httpcore/bean/ReturnDetailsBean.kt
  93. 6 0
      app/src/main/java/com/rc/httpcore/bean/ReturnGiveBackBean.kt
  94. 14 0
      app/src/main/java/com/rc/httpcore/bean/ReturningChemicalsBean.kt
  95. 63 0
      app/src/main/java/com/rc/httpcore/bean/ReturningList.kt
  96. 29 0
      app/src/main/java/com/rc/httpcore/bean/RuleBean.kt
  97. 3 0
      app/src/main/java/com/rc/httpcore/bean/SearchBean.kt
  98. 3 0
      app/src/main/java/com/rc/httpcore/bean/SearchSpecBean.kt
  99. 9 0
      app/src/main/java/com/rc/httpcore/bean/StockDetailsModel.kt
  100. 0 0
      app/src/main/java/com/rc/httpcore/bean/StockModeBean.kt

+ 77 - 0
.gitignore

@@ -0,0 +1,77 @@
+# 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/*

+ 1 - 0
app/.gitignore

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

+ 136 - 0
app/build.gradle

@@ -0,0 +1,136 @@
+plugins {
+    alias(libs.plugins.android.application)
+    alias(libs.plugins.kotlin.android)
+}
+
+android {
+    namespace 'xn.hxp'
+    compileSdk 35
+
+    defaultConfig {
+        applicationId "xn.hxp"
+        minSdk 24
+        //noinspection ExpiredTargetSdkVersion,OldTargetApi
+        targetSdk 28
+        versionCode 1
+        versionName "1.0"
+
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+        multiDexEnabled = true
+        ndk {
+            abiFilters "armeabi-v7a"/*, "arm64-v8a", "x86"*/
+        }
+    }
+
+    sourceSets {
+        main {
+            jniLibs.srcDirs = ['libs']
+        }
+    }
+
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+    }
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_17
+        targetCompatibility JavaVersion.VERSION_17
+    }
+    kotlinOptions {
+        jvmTarget = '17'
+    }
+    buildFeatures {
+        viewBinding = true
+    }
+}
+
+dependencies {
+    implementation fileTree(dir: 'libs', include: ['*.aar'])
+    implementation fileTree(dir: 'libs', include: ['*.jar'])
+
+    implementation project(':serialport')
+
+    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
+
+    //noinspection UseTomlInstead
+    implementation 'com.github.jenly1314:zxing-lite:3.2.0'
+    //noinspection UseTomlInstead
+    implementation 'io.fotoapparat:fotoapparat:2.7.0'
+    //noinspection UseTomlInstead
+    implementation 'com.airbnb.android:lottie:6.6.2'
+    //noinspection UseTomlInstead
+    implementation "io.github.cymchad:BaseRecyclerViewAdapterHelper:3.0.14"
+    //noinspection UseTomlInstead
+    implementation 'org.greenrobot:eventbus:3.3.1'
+    //noinspection UseTomlInstead
+    implementation 'com.blankj:utilcodex:1.31.1'
+    //noinspection UseTomlInstead
+    implementation 'com.github.bumptech.glide:glide:4.16.0'
+
+    def retrofit = "2.11.0"
+    //noinspection UseTomlInstead
+    implementation("com.squareup.retrofit2:retrofit:${retrofit}") // 最新稳定版本
+    // Gson 转换器 (用于 JSON) - 你很可能需要这个  noinspection UseTomlInstead
+    implementation("com.squareup.retrofit2:converter-gson:${retrofit}") // 与 Retrofit 相同的版本
+    // RxJava 3 Retrofit 适配器 noinspection UseTomlInstead
+    implementation("com.squareup.retrofit2:adapter-rxjava3:${retrofit}") // 与 Retrofit 相同的版本
+
+    def okhttp = "4.12.0"
+    //noinspection UseTomlInstead
+    implementation("com.squareup.okhttp3:okhttp:${okhttp}") // 最新稳定版本
+    // OkHttp 日志拦截器 (用于调试) - 强烈推荐  noinspection UseTomlInstead
+    implementation("com.squareup.okhttp3:logging-interceptor:${okhttp}") // 与 OkHttp 相同的版本
+
+    // RxJava 3 noinspection GradleDependency,UseTomlInstead
+    implementation("io.reactivex.rxjava3:rxjava:3.1.8") // 最新稳定版本
+    //noinspection UseTomlInstead
+    implementation("io.reactivex.rxjava3:rxandroid:3.0.2") // 最新稳定版本
+
+    // Gson noinspection UseTomlInstead
+    implementation("com.google.code.gson:gson:2.12.1") // 最新稳定版本
+
+    //noinspection UseTomlInstead
+    implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
+
+    //noinspection UseTomlInstead
+    implementation 'com.github.codbking:PickTime:v1.0.1'
+
+    //noinspection UseTomlInstead
+    implementation "com.kongzue.dialogx:DialogX:0.0.49"
+
+    //noinspection UseTomlInstead
+    implementation 'com.github.getActivity:XXPermissions:20.0'
+
+    //noinspection UseTomlInstead
+    implementation "androidx.multidex:multidex:2.0.1"
+
+    def room_version = "2.6.1"
+    //noinspection UseTomlInstead
+    implementation "androidx.room:room-runtime:$room_version"
+    // If this project uses any Kotlin source, use Kotlin Symbol Processing (KSP)
+    // See KSP Quickstart to add KSP to your build
+//    ksp "androidx.room:room-compiler:$room_version"
+    // If this project only uses Java source, use the Java annotationProcessor
+    // No additional plugins are necessary
+    annotationProcessor "androidx.room:room-compiler:$room_version"
+
+    def camerax_version = "1.0.0"
+    implementation "androidx.camera:camera-core:${camerax_version}"
+    implementation "androidx.camera:camera-camera2:${camerax_version}"
+    implementation "androidx.camera:camera-lifecycle:${camerax_version}"
+    implementation "androidx.camera:camera-view:1.0.0-alpha28"
+    implementation "androidx.camera:camera-extensions:1.0.0-alpha28"
+
+    implementation 'com.google.mlkit:face-detection:16.0.0'
+
+
+}

BIN
app/libs/USBPrintSDK.jar


BIN
app/libs/arcsoft_face.jar


BIN
app/libs/arcsoft_image_util.jar


BIN
app/libs/armeabi-v7a/libserial_port_wh.so


BIN
app/libs/autoreplyprint.aar


BIN
app/libs/commons-codec-1.4.jar


BIN
app/libs/facedetector-1.0.0.aar


BIN
app/libs/flowlayout-1.0.0.aar


BIN
app/libs/sdkapi.jar


BIN
app/libs/tbs_sdk_v4.3.0.165_20210628_103707.jar


BIN
app/libs/whdrawlabel1.1.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());
+    }
+}

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

@@ -0,0 +1,310 @@
+<?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.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" />
+    <uses-permission android:name="android.permission.ACCESS_FINE_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" />
+
+    <application
+        android:name=".app.ChemicalApp"
+        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:largeHeap="true"
+        android:networkSecurityConfig="@xml/network_security_config"
+        android:requestLegacyExternalStorage="true"
+        android:roundIcon="@mipmap/ic_launcher_round"
+        android:supportsRtl="true"
+        android:theme="@style/Theme.AppFullTheme"
+        tools:targetApi="m">
+        <activity
+            android:name=".ui.plan.save_list.SaveListActivity"
+            android:exported="false" />
+        <activity
+            android:name=".ui.verify.DoubleVerifyActivity"
+            android:exported="false" />
+        <activity
+            android:name=".ui.plan.unlock.UnlockActivity"
+            android:exported="false" />
+        <activity
+            android:name=".ui.plan.change.ChangeCabinetActivity"
+            android:exported="false" />
+        <activity
+            android:name=".ui.plan.add.AddActivity"
+            android:exported="false" />
+        <activity
+            android:name=".ui.plan.PlanAddActivity"
+            android:exported="false" />
+
+        <meta-data
+            android:name="ScopedStorage"
+            android:value="true" />
+
+        <activity
+            android:name=".ui.verify.TwoVerificationActivity"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|fontScale"
+            android:launchMode="singleTask"
+            android:screenOrientation="landscape"
+            android:windowSoftInputMode="adjustPan" />
+        <activity
+            android:name=".ui.SettingActivity"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|fontScale"
+            android:launchMode="singleTask"
+            android:screenOrientation="landscape"
+            android:windowSoftInputMode="adjustPan" />
+        <activity
+            android:name=".ui.login.ScanLoginActivity"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|fontScale"
+            android:launchMode="singleTask"
+            android:screenOrientation="landscape"
+            android:windowSoftInputMode="adjustPan" />
+        <activity
+            android:name=".ui.uses.ProcessedActivity"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|fontScale"
+            android:launchMode="singleTask"
+            android:screenOrientation="landscape"
+            android:windowSoftInputMode="adjustPan" />
+        <activity
+            android:name=".ui.warehousing.ChemicalLabelingActivity"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|fontScale"
+            android:launchMode="singleTask"
+            android:screenOrientation="landscape"
+            android:windowSoftInputMode="adjustPan" />
+        <activity
+            android:name=".ui.discard.ChecklistsActivity"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|fontScale"
+            android:launchMode="singleTask"
+            android:screenOrientation="landscape"
+            android:windowSoftInputMode="adjustPan" />
+        <activity
+            android:name=".ui.discard.ListReturnedActivity"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|fontScale"
+            android:launchMode="singleTask"
+            android:screenOrientation="landscape"
+            android:windowSoftInputMode="adjustPan" />
+        <activity
+            android:name=".ui.verify.SwipeCodeTwoActivity"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|fontScale"
+            android:launchMode="singleTask"
+            android:screenOrientation="landscape"
+            android:windowSoftInputMode="adjustPan" />
+        <activity
+            android:name=".ui.verify.ScanCodeTwoActivity"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|fontScale"
+            android:launchMode="singleTask"
+            android:screenOrientation="landscape"
+            android:windowSoftInputMode="adjustPan" />
+        <activity
+            android:name=".ui.verify.TwoPersonActivity"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|fontScale"
+            android:launchMode="singleTask"
+            android:screenOrientation="landscape"
+            android:windowSoftInputMode="adjustPan" />
+        <activity
+            android:name=".ui.login.FacialCardActivity"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|fontScale"
+            android:launchMode="singleTask"
+            android:screenOrientation="landscape"
+            android:windowSoftInputMode="adjustPan" />
+        <activity
+            android:name=".ui.still.MsdsScreenActivity"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|fontScale"
+            android:launchMode="singleTask"
+            android:screenOrientation="landscape"
+            android:windowSoftInputMode="adjustPan" />
+        <activity
+            android:name=".ui.still.MsdsActivity"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|fontScale"
+            android:launchMode="singleTask"
+            android:screenOrientation="landscape"
+            android:windowSoftInputMode="adjustPan" />
+        <activity
+            android:name=".ui.discard.LedgerActivity"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|fontScale"
+            android:launchMode="singleTask"
+            android:screenOrientation="landscape"
+            android:windowSoftInputMode="adjustPan" />
+        <activity
+            android:name=".ui.still.DemonstrateActivity"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|fontScale"
+            android:launchMode="singleTask"
+            android:screenOrientation="landscape"
+            android:windowSoftInputMode="adjustPan" />
+        <activity
+            android:name=".ui.uses.WarningEventsActivity"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|fontScale"
+            android:launchMode="singleTask"
+            android:screenOrientation="landscape"
+            android:windowSoftInputMode="adjustPan" />
+        <activity
+            android:name=".ui.warehousing.ElectronicLedgerActivity"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|fontScale"
+            android:launchMode="singleTask"
+            android:screenOrientation="landscape"
+            android:windowSoftInputMode="adjustPan" />
+        <activity
+            android:name=".ui.inquiry.QueryDetailsActivity"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|fontScale"
+            android:launchMode="singleTask"
+            android:screenOrientation="landscape"
+            android:windowSoftInputMode="adjustPan" />
+        <activity
+            android:name=".ui.inquiry.InquiryActivity"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|fontScale"
+            android:launchMode="singleTask"
+            android:screenOrientation="landscape"
+            android:windowSoftInputMode="adjustPan" />
+        <activity
+            android:name=".ui.discard.WasteChemicalsActivity"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|fontScale"
+            android:launchMode="singleTask"
+            android:screenOrientation="landscape"
+            android:windowSoftInputMode="adjustPan" />
+        <activity
+            android:name=".ui.still.ChemicalsAlsoActivity"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|fontScale"
+            android:launchMode="singleTask"
+            android:screenOrientation="landscape"
+            android:windowSoftInputMode="adjustPan" />
+        <activity
+            android:name=".ui.uses.UseActivity"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|fontScale"
+            android:launchMode="singleTask"
+            android:screenOrientation="landscape"
+            android:windowSoftInputMode="adjustPan" />
+        <activity
+            android:name=".ui.warehousing.WarehousingActivity"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|fontScale"
+            android:launchMode="singleTask"
+            android:screenOrientation="landscape"
+            android:windowSoftInputMode="adjustPan" />
+        <activity
+            android:name=".ui.newly.AddedChemicalsActivity"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|fontScale"
+            android:launchMode="singleTask"
+            android:screenOrientation="landscape"
+            android:windowSoftInputMode="adjustPan" />
+        <activity
+            android:name=".ui.login.SwipeActivity"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|fontScale"
+            android:launchMode="singleTask"
+            android:screenOrientation="landscape"
+            android:windowSoftInputMode="adjustPan" />
+        <activity
+            android:name=".ui.login.FacialLoginActivity"
+            android:screenOrientation="landscape"
+            android:windowSoftInputMode="adjustPan" />
+        <activity
+            android:name=".ui.SplashActivity"
+            android:exported="true"
+            android:launchMode="singleTop"
+            android:screenOrientation="landscape">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <service
+            android:name=".receiver.PortScanService"
+            android:enabled="true"
+            android:exported="false" />
+
+        <provider
+            android:name="androidx.core.content.FileProvider"
+            android:authorities="${applicationId}.fileprovider"
+            android:exported="false"
+            android:grantUriPermissions="true">
+            <meta-data
+                android:name="android.support.FILE_PROVIDER_PATHS"
+                android:resource="@xml/file_paths_public" />
+        </provider>
+
+        <activity
+            android:name=".ui.MainActivity"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|fontScale"
+            android:launchMode="singleTask"
+            android:screenOrientation="landscape"
+            android:windowSoftInputMode="adjustPan">
+
+            <!-- USB -->
+            <intent-filter>
+                <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
+                <action android:name="ACTION_USB_PERMISSION_ISSUER" />
+            </intent-filter>
+
+            <meta-data
+                android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
+                android:resource="@xml/device_filter" />
+        </activity>
+
+        <receiver
+            android:name=".receiver.ProcessReceiver"
+            android:exported="true"
+            tools:ignore="ExportedReceiver">
+            <intent-filter>
+                <action android:name="XN_ACTION" />
+            </intent-filter>
+        </receiver>
+    </application>
+
+</manifest>

+ 8 - 0
app/src/main/java/com/rc/core/event/RefreshEvent.kt

@@ -0,0 +1,8 @@
+package com.rc.core.event
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+data class RefreshEvent(var refresh: Boolean = true, var flag: String)

+ 71 - 0
app/src/main/java/com/rc/core/ui/ActivityCollector.kt

@@ -0,0 +1,71 @@
+package com.rc.core.ui
+
+import android.app.Activity
+import java.util.ArrayList
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+object ActivityCollector {
+
+    /**
+     * 存放应用已开启的所有Activity.
+     */
+    private val mActivities by lazy { ArrayList<Activity>() }
+
+    /**
+     * 添加一个Activity到Activity列表中.
+     *
+     * @param activity
+     * : Activity实例.
+     */
+    fun addActivity(activity: Activity) {
+        mActivities.add(activity)
+    }
+
+    /**
+     * 在Activity列表中,从顶部删除一定数量的Activity.
+     * -1表示删除所有的Activity, 如果count大于Activity列表的size, 将删除所有的Activity.
+     *
+     * @param count
+     * : 要删的Activity数量.
+     */
+    fun removeActivity(count: Int) {
+        val listSize = mActivities.size
+        val delCount = if (-1 == count || count > listSize) listSize else count
+        for (i in 0 until delCount) {
+            val activity = mActivities[listSize - i - 1]
+            if (!activity.isFinishing) {
+                activity.finish()
+            }
+            mActivities.remove(activity)
+        }
+    }
+
+    /**
+     * 从Activity列表中删除指定的Activity.
+     *
+     * @param activity
+     * : 要删的Activity.
+     */
+    fun removeActivity(activity: Activity) {
+        if (mActivities.contains(activity)) {
+            mActivities.remove(activity)
+        }
+    }
+
+    /**
+     * 关闭所有的Activity, 退出程序.
+     */
+    fun finishAll() {
+        for (activity in mActivities) {
+            if (!activity.isFinishing) {
+                activity.finish()
+            }
+        }
+        mActivities.clear()
+    }
+
+}

+ 132 - 0
app/src/main/java/com/rc/core/ui/activity/RcBaseActivity.kt

@@ -0,0 +1,132 @@
+package com.rc.core.ui.activity
+
+import android.graphics.Color
+import android.os.Build
+import android.os.Bundle
+import android.view.View
+import android.view.inputmethod.InputMethodManager
+import androidx.annotation.ColorInt
+import androidx.appcompat.app.AppCompatActivity
+import androidx.recyclerview.widget.RecyclerView
+import androidx.viewbinding.ViewBinding
+import com.rc.core.ui.ActivityCollector
+import com.rc.core.ui.common.AbsUIDelegate
+import com.rc.core.ui.common.IUIListener
+import com.rc.core.util.ScreenAdapter
+import io.reactivex.rxjava3.disposables.Disposable
+
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+abstract class RcBaseActivity<VB : ViewBinding> : AppCompatActivity(), IUIListener {
+
+    private lateinit var _viewBinding: VB
+
+    protected val viewBinding get() = _viewBinding
+
+    protected lateinit var mUIDelegate: AbsUIDelegate
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        ActivityCollector.addActivity(this)
+        ScreenAdapter.setCustomDensity(this)
+        mUIDelegate = AbsUIDelegate.create()
+        configImmersiveMode()
+        beforeSetContentView()
+        _viewBinding = createViewBinding()
+        setContentView(_viewBinding.root)
+        initViews(savedInstanceState)
+        initListener()
+        initData()
+    }
+
+    override fun onResume() {
+        super.onResume()
+        ScreenAdapter.setCustomDensity(this)
+    }
+
+    protected abstract fun createViewBinding(): VB
+
+    protected open fun configImmersiveMode() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+                window.decorView.systemUiVisibility =
+                    View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
+            }
+            window.statusBarColor = statusBarColor()
+        }
+    }
+
+    @ColorInt
+    protected open fun statusBarColor(): Int = Color.WHITE
+
+    protected open fun beforeSetContentView() {
+    }
+
+    protected open fun initViews(savedInstanceState: Bundle?) {
+    }
+
+    protected open fun initListener() {
+    }
+
+    protected open fun initData() {
+    }
+
+    override fun onDestroy() {
+        super.onDestroy()
+        mUIDelegate.clearDisposable()
+        ActivityCollector.removeActivity(this)
+    }
+
+    override fun showLoading(message: String?, cancelable: Boolean) {
+        if (!isDestroyed) {
+            mUIDelegate.showLoading(this, message, cancelable)
+        }
+    }
+
+    override fun dismissLoading() {
+        mUIDelegate.dismissLoading()
+    }
+
+    override fun showToast(message: String) {
+        mUIDelegate.showToast(this, message)
+    }
+
+    override fun showNetError(throwable: Throwable) {
+        mUIDelegate.showNetError(this, throwable)
+    }
+
+    override fun addDisposable(disposable: Disposable) {
+        mUIDelegate.addDisposable(disposable)
+    }
+
+    override fun delDisposable(disposable: Disposable) {
+        mUIDelegate.delDisposable(disposable)
+    }
+
+    override fun createItemDecoration(): RecyclerView.ItemDecoration? {
+        return mUIDelegate.createItemDecoration(this)
+    }
+
+    /**
+     * 关闭软键盘
+     */
+    open fun hintKeyBoard() {
+        //拿到InputMethodManager
+        val imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
+        //如果window上view获取焦点 && view不为空
+        if (imm.isActive && currentFocus != null) {
+            //拿到view的token 不为空
+            if (currentFocus!!.windowToken != null) {
+                //表示软键盘窗口总是隐藏,除非开始时以SHOW_FORCED显示。
+                imm.hideSoftInputFromWindow(
+                    currentFocus!!.windowToken,
+                    InputMethodManager.HIDE_NOT_ALWAYS
+                )
+            }
+        }
+    }
+}

+ 236 - 0
app/src/main/java/com/rc/core/ui/activity/RcRefreshActivity.kt

@@ -0,0 +1,236 @@
+package com.rc.core.ui.activity
+
+import android.os.Bundle
+import android.view.View
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
+import androidx.viewbinding.ViewBinding
+import com.chad.library.adapter.base.BaseQuickAdapter
+import com.chad.library.adapter.base.listener.OnItemChildClickListener
+import com.chad.library.adapter.base.listener.OnItemClickListener
+import com.chad.library.adapter.base.listener.OnLoadMoreListener
+import com.chad.library.adapter.base.viewholder.BaseViewHolder
+import com.rc.core.event.RefreshEvent
+import com.rc.core.ui.widget.MultipleStatusView
+import com.rc.core.util.net.NetConnectedListener
+import com.rc.core.util.net.NetWatchdog
+import io.reactivex.rxjava3.disposables.Disposable
+import org.greenrobot.eventbus.EventBus
+import org.greenrobot.eventbus.Subscribe
+import org.greenrobot.eventbus.ThreadMode
+import xn.hxp.R
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+abstract class RcRefreshActivity<T, VB : ViewBinding>
+constructor(val mAdapter: BaseQuickAdapter<T, BaseViewHolder>) : RcBaseActivity<VB>(),
+    OnLoadMoreListener,
+    OnItemClickListener,
+    OnItemChildClickListener {
+
+    companion object {
+        const val FIRST_PAGE = 1
+        const val PAGE_SIZE = 15
+    }
+
+    protected abstract val mMultipleStatusView: MultipleStatusView?
+    protected abstract val mSrlRefresh: SwipeRefreshLayout
+    protected abstract val mRvContent: RecyclerView
+
+    protected var mCurrentPage = FIRST_PAGE
+    protected var mRefresh = false
+
+    private val mNetWatchdog: NetWatchdog by lazy { NetWatchdog(this) }
+    private var mLoadingData = false
+
+    override fun initListener() {
+        mMultipleStatusView?.setOnRetryClickListener { loadData(true) }
+        mSrlRefresh.setOnRefreshListener { loadData(true) }
+
+        initNetWatchdog()
+    }
+
+    override fun initViews(savedInstanceState: Bundle?) {
+        initRecyclerView()
+    }
+
+    private fun initRecyclerView() {
+        mAdapter.setOnItemClickListener(this)
+        mAdapter.setOnItemChildClickListener(this)
+
+        mRvContent.layoutManager = createLayoutManager()
+//        mAdapter.loadMoreModule.loadMoreView = ItLoadMoreView()
+        mAdapter.loadMoreModule.setOnLoadMoreListener(this)
+        mAdapter.loadMoreModule.isAutoLoadMore = true
+        // 当自动加载开启,同时数据不满一屏时,是否继续执行自动加载更多(默认为true)
+        mAdapter.loadMoreModule.isEnableLoadMoreIfNotFullPage = true
+        mAdapter.animationEnable = true
+        mAdapter.setAnimationWithDefault(BaseQuickAdapter.AnimationType.SlideInBottom)
+
+        createItemDecoration()?.let {
+            mRvContent.addItemDecoration(it)
+        }
+
+        mRvContent.adapter = mAdapter
+    }
+
+    protected open fun createLayoutManager(): RecyclerView.LayoutManager {
+        return LinearLayoutManager(this)
+    }
+
+    /**
+     * 初始化网络监听
+     */
+    private fun initNetWatchdog() {
+        mNetWatchdog.startWatch()
+        mNetWatchdog.setNetConnectedListener(object : NetConnectedListener {
+            override fun onReNetConnected(isReconnect: Boolean) {
+                mMultipleStatusView?.let {
+                    if (isReconnect) {
+                        it.showContent()
+                    }
+                }
+                if (mAdapter.data.isEmpty() && !mLoadingData && isReconnect) {
+                    loadData(true)
+                }
+            }
+
+            override fun onNetUnConnected() {
+                if (mAdapter.data.isEmpty()) {
+                    mMultipleStatusView?.showNoNetwork()
+                }
+            }
+        })
+    }
+
+    override fun onDestroy() {
+        super.onDestroy()
+
+        EventBus.getDefault().unregister(this)
+        mNetWatchdog.stopWatch()
+    }
+
+    /**
+     * 数据加载成功的处理逻辑
+     *
+     * @param data 从服务端查询的数据
+     */
+    protected open fun dispatchLoadDataSuccess(data: List<T>?) {
+        mLoadingData = false
+        if (isDestroyed) return
+
+        mMultipleStatusView?.showContent()
+        if (mRefresh) {
+            if (null == data || data.isEmpty()) {
+                if (null == mMultipleStatusView) {
+                    mAdapter.setEmptyView(R.layout.view_list_empty)
+                } else {
+                    mMultipleStatusView!!.showEmpty()
+                }
+            }
+            mAdapter.setNewInstance(data?.toMutableList())
+            mAdapter.loadMoreModule.loadMoreComplete()
+            if (null == data || data.size < PAGE_SIZE) {
+                mAdapter.loadMoreModule.loadMoreEnd(goneLoadMoreView())
+            }
+            mSrlRefresh.isRefreshing = false
+        } else {
+            if (null == data || data.size < PAGE_SIZE) {
+                if (null != data) {
+                    mAdapter.addData(data)
+                }
+                mAdapter.loadMoreModule.loadMoreEnd(goneLoadMoreView())
+            } else {
+                mAdapter.addData(data)
+                mAdapter.loadMoreModule.loadMoreComplete()
+            }
+        }
+    }
+
+    /**
+     * 数据加载失败的处理逻辑
+     */
+    protected open fun dispatchLoadDataFailure(throwable: Throwable) {
+        mLoadingData = false
+        if (isDestroyed) return
+
+        if (mRefresh) {
+            if (null == mMultipleStatusView) {
+                mAdapter.setEmptyView(R.layout.view_list_empty)
+            } else {
+                mMultipleStatusView!!.showError()
+            }
+            mSrlRefresh.isRefreshing = false
+        } else {
+            mCurrentPage = if (mCurrentPage-- < FIRST_PAGE) FIRST_PAGE else mCurrentPage
+            mAdapter.loadMoreModule.loadMoreEnd(goneLoadMoreView())
+        }
+        throwable.printStackTrace()
+        showNetError(throwable)
+    }
+
+    protected open fun goneLoadMoreView() = false
+
+    private fun showLoadingView() {
+        mMultipleStatusView?.let {
+            if (mRefresh) {
+                it.showLoading()
+            }
+        }
+    }
+
+    protected fun loadData(refresh: Boolean) {
+        mLoadingData = true
+        if (refresh) {
+            mCurrentPage = FIRST_PAGE
+        }
+        mRefresh = refresh
+        showLoadingView()
+        queryData()?.let { addDisposable(it) }
+    }
+
+    /**
+     * 查询数据
+     */
+    abstract fun queryData(): Disposable?
+
+    override fun onLoadMore() {
+        val dataSize = mAdapter.data.size
+        if (dataSize < PAGE_SIZE) {
+            mAdapter.loadMoreModule.loadMoreEnd(false)
+        } else {
+            if (dataSize % PAGE_SIZE != 0) {
+                mAdapter.loadMoreModule.loadMoreEnd(false)
+            } else {
+                mCurrentPage++
+                loadData(false)
+            }
+        }
+    }
+
+    override fun onItemClick(adapter: BaseQuickAdapter<*, *>, view: View, position: Int) {
+    }
+
+    override fun onItemChildClick(adapter: BaseQuickAdapter<*, *>, view: View, position: Int) {
+    }
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        EventBus.getDefault().register(this)
+    }
+
+    @Subscribe(threadMode = ThreadMode.MAIN)
+    fun onMessageEvent(event: RefreshEvent) {
+        if (event.refresh) {
+            dispatchRefreshEvent(event)
+        }
+    }
+
+    protected open fun dispatchRefreshEvent(event: RefreshEvent) {
+    }
+
+}

+ 39 - 0
app/src/main/java/com/rc/core/ui/common/AbsUIDelegate.kt

@@ -0,0 +1,39 @@
+package com.rc.core.ui.common
+
+import android.content.Context
+import androidx.fragment.app.FragmentManager
+import androidx.recyclerview.widget.RecyclerView
+import io.reactivex.rxjava3.disposables.Disposable
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+abstract class AbsUIDelegate {
+
+    companion object {
+        fun create() = UIDelegateImpl()
+    }
+
+    abstract fun showLoading(
+        context: Context,
+        message: String? = null,
+        cancelable: Boolean = false
+    )
+
+    abstract fun dismissLoading()
+
+    abstract fun showToast(context: Context?, message: String)
+
+    abstract fun showNetError(context: Context?, throwable: Throwable)
+
+    abstract fun addDisposable(disposable: Disposable)
+
+    abstract fun delDisposable(disposable: Disposable)
+
+    abstract fun clearDisposable()
+
+    abstract fun createItemDecoration(context: Context?): RecyclerView.ItemDecoration?
+
+}

+ 27 - 0
app/src/main/java/com/rc/core/ui/common/IUIListener.kt

@@ -0,0 +1,27 @@
+package com.rc.core.ui.common
+
+import androidx.recyclerview.widget.RecyclerView
+import io.reactivex.rxjava3.disposables.Disposable
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+interface IUIListener {
+
+    fun showLoading(message: String? = null, cancelable: Boolean = false)
+
+    fun dismissLoading()
+
+    fun showToast(message: String)
+
+    fun showNetError(throwable: Throwable)
+
+    fun addDisposable(disposable: Disposable)
+
+    fun delDisposable(disposable: Disposable)
+
+    fun createItemDecoration(): RecyclerView.ItemDecoration?
+
+}

+ 104 - 0
app/src/main/java/com/rc/core/ui/common/UIDelegateImpl.kt

@@ -0,0 +1,104 @@
+package com.rc.core.ui.common
+
+import android.content.Context
+import android.widget.Toast
+import androidx.core.content.ContextCompat
+import androidx.recyclerview.widget.DividerItemDecoration
+import androidx.recyclerview.widget.RecyclerView
+import xn.hxp.R
+import com.rc.core.ui.dialog.LoadingDialog
+import com.rc.core.ui.widget.decoration.NoLastLineItemDecoration
+import com.rc.httpcore.exception.NetException
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import io.reactivex.rxjava3.disposables.Disposable
+import retrofit2.HttpException
+import java.net.ConnectException
+import java.net.SocketTimeoutException
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+class UIDelegateImpl : AbsUIDelegate() {
+
+    private var mPdLoading: LoadingDialog? = null
+
+    override fun showLoading(
+        context: Context,
+        message: String?,
+        cancelable: Boolean,
+    ) {
+        if (null == mPdLoading) {
+            mPdLoading = LoadingDialog(context, message)
+        }
+        mPdLoading?.let {
+            it.setMessage(message)
+            it.setCancelable(cancelable)
+            if (it != null && !it.isShowing) {
+                it.show()
+            }
+        }
+    }
+
+    override fun dismissLoading() {
+        mPdLoading?.let {
+            if (it.isShowing) {
+                it.dismiss()
+                mPdLoading = null
+            }
+        }
+    }
+
+    override fun showToast(context: Context?, message: String) {
+        context?.let {
+            Toast.makeText(it, message, Toast.LENGTH_SHORT).show()
+        }
+    }
+
+    override fun showNetError(context: Context?, throwable: Throwable) {
+        when (throwable) {
+            is NetException -> {
+                if (throwable.message.isNullOrEmpty()) {
+                    "接口请求失败(${throwable.code})"
+                } else {
+                    throwable.message!!
+                }
+            }
+
+            is SocketTimeoutException -> "请求超时,请稍后重试"
+            is ConnectException -> "无法连接服务器,请检查网络"
+            is HttpException -> "服务器繁忙,请稍后重试"
+            else -> null
+        }?.let { showToast(context, it) }
+    }
+
+    private var mCompositeDisposable: CompositeDisposable? = null
+
+    override fun addDisposable(disposable: Disposable) {
+        mCompositeDisposable = (mCompositeDisposable ?: CompositeDisposable()).apply {
+            if (!isDisposed) add(disposable)
+        }
+    }
+
+    override fun delDisposable(disposable: Disposable) {
+        mCompositeDisposable = (mCompositeDisposable ?: CompositeDisposable()).apply {
+            if (!isDisposed) remove(disposable)
+        }
+    }
+
+    override fun clearDisposable() {
+        mCompositeDisposable = mCompositeDisposable?.let {
+            it.clear()
+            null
+        }
+    }
+
+    override fun createItemDecoration(context: Context?): RecyclerView.ItemDecoration? {
+        return context?.let {
+            NoLastLineItemDecoration(it, DividerItemDecoration.VERTICAL).apply {
+                setDrawable(ContextCompat.getDrawable(it, R.drawable.shape_item_divider)!!)
+            }
+        }
+    }
+}

+ 104 - 0
app/src/main/java/com/rc/core/ui/dialog/LoadingDialog.kt

@@ -0,0 +1,104 @@
+package com.rc.core.ui.dialog
+
+import android.app.Dialog
+import android.content.Context
+import android.os.Bundle
+import android.os.Handler
+import android.view.*
+import androidx.fragment.app.DialogFragment
+import androidx.fragment.app.FragmentManager
+import xn.hxp.R
+import xn.hxp.databinding.DialogLoadingBinding
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+class LoadingDialog(context: Context, private val message: String? = null) :
+    Dialog(context, R.style.LoadingDialog) {
+
+    fun setMessage(message: String?) {
+        viewBinding.message.visibility = if (message.isNullOrEmpty()) View.GONE else View.VISIBLE
+        viewBinding.message.text = message
+    }
+
+    private val viewBinding: DialogLoadingBinding by lazy {
+        DialogLoadingBinding.inflate(LayoutInflater.from(context))
+    }
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        setContentView(viewBinding.root)
+
+        setMessage(message)
+    }
+
+}
+
+//class LoadingDialog : DialogFragment() {
+//
+//    override fun onCreate(savedInstanceState: Bundle?) {
+//        super.onCreate(savedInstanceState)
+//        setStyle(STYLE_NO_TITLE, R.style.LoadingDialog)
+//    }
+//
+//    private lateinit var mViewBinding: DialogLoadingBinding
+//
+//    override fun onCreateView(
+//        inflater: LayoutInflater,
+//        container: ViewGroup?,
+//        savedInstanceState: Bundle?
+//    ): View {
+//        mViewBinding = DialogLoadingBinding.inflate(inflater, container, false)
+//        return mViewBinding.root
+//    }
+//
+//    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+//        super.onViewCreated(view, savedInstanceState)
+//
+//        mViewBinding.message.visibility = if (mMessage.isNullOrEmpty()) View.GONE else View.VISIBLE
+//        mViewBinding.message.text = mMessage
+//    }
+//
+//    override fun onStart() {
+//        super.onStart()
+//        initWindowConfig()
+//    }
+//
+//    private fun initWindowConfig() {
+//        dialog?.window?.apply {
+//            setWindowAnimations(R.style.PopWindowAnimStyle)
+//            setGravity(Gravity.CENTER)
+//            attributes.width = WindowManager.LayoutParams.MATCH_PARENT
+//            attributes.height = WindowManager.LayoutParams.WRAP_CONTENT
+//        }
+//    }
+//
+//    private var mMessage: CharSequence? = null
+//
+//    fun setMessage(message: CharSequence?): LoadingDialog {
+//        this.mMessage = message
+//        return this
+//    }
+//
+//    private var addTag = false
+//
+//    fun show(manager: FragmentManager): LoadingDialog {
+//        if (addTag) return this
+//        addTag = true
+//
+//        if (this.isAdded || null != manager.findFragmentByTag(LoadingDialog::class.simpleName)) {
+//            manager.beginTransaction().remove(this).commit()
+//        }
+//        show(manager, LoadingDialog::class.simpleName)
+//
+//        Handler().post {
+//            addTag = false
+//        }
+//
+//        return this
+//    }
+//
+//}

+ 113 - 0
app/src/main/java/com/rc/core/ui/dialog/RcBaseDialog.kt

@@ -0,0 +1,113 @@
+package com.rc.core.ui.dialog
+
+import android.os.Bundle
+import android.view.*
+import androidx.fragment.app.DialogFragment
+import androidx.recyclerview.widget.RecyclerView
+import androidx.viewbinding.ViewBinding
+import xn.hxp.R
+import com.rc.core.ui.common.AbsUIDelegate
+import com.rc.core.ui.common.IUIListener
+import io.reactivex.rxjava3.disposables.Disposable
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+abstract class RcBaseDialog<VB : ViewBinding> :
+    DialogFragment(), IUIListener {
+
+    private lateinit var _viewBinding: VB
+
+    protected val viewBinding get() = _viewBinding
+
+    private lateinit var mUIDelegate: AbsUIDelegate
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        mUIDelegate = AbsUIDelegate.create()
+
+        setStyle(STYLE_NO_TITLE, R.style.LoadingDialog)
+    }
+
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        initWindowConfig()
+        _viewBinding = createViewBinding(inflater, container)
+        return _viewBinding.root
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+        initViews(savedInstanceState)
+    }
+
+    override fun onResume() {
+        super.onResume()
+        initDialogSize()
+    }
+
+    protected open fun initDialogSize() {
+        dialog?.window?.apply {
+            attributes.width = WindowManager.LayoutParams.MATCH_PARENT
+            attributes.height = WindowManager.LayoutParams.WRAP_CONTENT
+        }
+    }
+
+    protected open fun initWindowConfig() {
+//        dialog?.window?.apply {
+//            setWindowAnimations(R.style.BottomDialog_Animation)
+//            setGravity(Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL)
+//        }
+//
+//        dialog?.window?.apply {
+//            attributes.gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL
+//            attributes.windowAnimations = R.style.BottomDialog_Animation
+//            requestFeature(Window.FEATURE_NO_TITLE)
+//            setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
+//        }
+//        dialog?.setCanceledOnTouchOutside(true)
+    }
+
+    protected abstract fun createViewBinding(
+        inflater: LayoutInflater,
+        container: ViewGroup?
+    ): VB
+
+    protected abstract fun initViews(savedInstanceState: Bundle?)
+
+    override fun showLoading(message: String?, cancelable: Boolean) {
+//        mUIDelegate.showLoading(requireContext(), message, cancelable)
+        mUIDelegate.showLoading(requireActivity(), message, cancelable)
+    }
+
+    override fun dismissLoading() {
+        mUIDelegate.dismissLoading()
+    }
+
+    override fun showToast(message: String) {
+        mUIDelegate.showToast(context, message)
+    }
+
+    override fun showNetError(throwable: Throwable) {
+        mUIDelegate.showNetError(context, throwable)
+    }
+
+    override fun addDisposable(disposable: Disposable) {
+        mUIDelegate.addDisposable(disposable)
+    }
+
+    override fun onDestroyView() {
+        mUIDelegate.clearDisposable()
+        super.onDestroyView()
+    }
+
+    override fun createItemDecoration(): RecyclerView.ItemDecoration? {
+        return mUIDelegate.createItemDecoration(context)
+    }
+
+}

+ 88 - 0
app/src/main/java/com/rc/core/ui/fragment/RcBaseFragment.kt

@@ -0,0 +1,88 @@
+package com.rc.core.ui.fragment
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.Fragment
+import androidx.recyclerview.widget.RecyclerView
+import androidx.viewbinding.ViewBinding
+import com.rc.core.ui.common.AbsUIDelegate
+import com.rc.core.ui.common.IUIListener
+import io.reactivex.rxjava3.disposables.Disposable
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+abstract class RcBaseFragment<VB : ViewBinding> :
+    Fragment(), IUIListener {
+
+    private lateinit var _viewBinding: VB
+
+    protected val viewBinding get() = _viewBinding
+
+    private lateinit var mUIDelegate: AbsUIDelegate
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        mUIDelegate = AbsUIDelegate.create()
+    }
+
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        _viewBinding = createViewBinding(inflater, container)
+        return _viewBinding.root
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+        initViews(savedInstanceState)
+    }
+
+    protected abstract fun createViewBinding(
+        inflater: LayoutInflater,
+        container: ViewGroup?
+    ): VB
+
+    protected abstract fun initViews(savedInstanceState: Bundle?)
+
+    override fun showLoading(message: String?, cancelable: Boolean) {
+//        mUIDelegate.showLoading(requireContext(), message, cancelable)
+        mUIDelegate.showLoading(requireActivity(), message, cancelable)
+    }
+
+    override fun dismissLoading() {
+        mUIDelegate.dismissLoading()
+    }
+
+    override fun showToast(message: String) {
+        mUIDelegate.showToast(context, message)
+    }
+
+    override fun showNetError(throwable: Throwable) {
+        mUIDelegate.showNetError(context, throwable)
+    }
+
+    override fun addDisposable(disposable: Disposable) {
+        mUIDelegate.addDisposable(disposable)
+    }
+
+    override fun delDisposable(disposable: Disposable) {
+        mUIDelegate.delDisposable(disposable)
+    }
+
+    override fun onDestroyView() {
+        mUIDelegate.clearDisposable()
+        super.onDestroyView()
+    }
+
+    override fun createItemDecoration(): RecyclerView.ItemDecoration? {
+        return mUIDelegate.createItemDecoration(context)
+    }
+
+}

+ 37 - 0
app/src/main/java/com/rc/core/ui/fragment/RcLazyFragment.kt

@@ -0,0 +1,37 @@
+package com.rc.core.ui.fragment
+
+import androidx.viewbinding.ViewBinding
+
+/**
+ * 懒加载
+ *
+ * @author ReiChin_
+ */
+abstract class RcLazyFragment<VB : ViewBinding> : RcBaseFragment<VB>() {
+
+    override fun onResume() {
+        super.onResume()
+        lazyLoad()
+    }
+
+    private fun lazyLoad() {
+        if (!loadDateCompleted()) {
+            onLoadData()
+        }
+    }
+
+    /**
+     * 加载数据
+     */
+    protected abstract fun onLoadData()
+
+    /**
+     * 数据是否加载完毕,子类可覆写此方法,根据需要返回。
+     * 1) false,每次画面可见时,都会回调[.onLoadData]。
+     * 2) true,每次画面可见时,不会回调[.onLoadData]。
+     *
+     * @return 数据是否加载完成。
+     */
+    protected open fun loadDateCompleted() = false
+
+}

+ 225 - 0
app/src/main/java/com/rc/core/ui/fragment/RcRefreshFragment.kt

@@ -0,0 +1,225 @@
+package com.rc.core.ui.fragment
+
+import android.os.Bundle
+import android.view.View
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
+import androidx.viewbinding.ViewBinding
+import com.chad.library.adapter.base.BaseQuickAdapter
+import com.chad.library.adapter.base.listener.OnItemChildClickListener
+import com.chad.library.adapter.base.listener.OnItemClickListener
+import com.chad.library.adapter.base.listener.OnLoadMoreListener
+import com.chad.library.adapter.base.viewholder.BaseViewHolder
+import com.rc.core.event.RefreshEvent
+import com.rc.core.ui.widget.MultipleStatusView
+import com.rc.core.util.net.NetConnectedListener
+import com.rc.core.util.net.NetWatchdog
+import io.reactivex.rxjava3.disposables.Disposable
+import org.greenrobot.eventbus.EventBus
+import org.greenrobot.eventbus.Subscribe
+import org.greenrobot.eventbus.ThreadMode
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+abstract class RcRefreshFragment<T, VB : ViewBinding>
+constructor(val mAdapter: BaseQuickAdapter<T, BaseViewHolder>) : RcLazyFragment<VB>(),
+    OnLoadMoreListener,
+    OnItemClickListener,
+    OnItemChildClickListener {
+
+    companion object {
+        protected const val FIRST_PAGE = 1
+        protected const val PAGE_SIZE = 15
+    }
+
+    protected abstract val mMultipleStatusView: MultipleStatusView?
+    protected abstract val mSrlRefresh: SwipeRefreshLayout
+    protected abstract val mRvContent: RecyclerView
+
+    protected var mCurrentPage = FIRST_PAGE
+    protected var mRefresh = false
+
+    private val mNetWatchdog: NetWatchdog by lazy { NetWatchdog(requireContext()) }
+    private var mLoadingData = false
+
+    override fun initViews(savedInstanceState: Bundle?) {
+        initNetWatchdog()
+        initSwipeRecyclerView()
+    }
+
+    private fun initSwipeRecyclerView() {
+        mMultipleStatusView?.setOnRetryClickListener { loadData(true) }
+        mSrlRefresh.setOnRefreshListener { loadData(true) }
+
+        mAdapter.setOnItemClickListener(this)
+        mAdapter.setOnItemChildClickListener(this)
+
+        mRvContent.layoutManager = LinearLayoutManager(context)
+//        mAdapter.loadMoreModule.loadMoreView = ItLoadMoreView()
+        mAdapter.loadMoreModule.setOnLoadMoreListener(this)
+        mAdapter.loadMoreModule.isAutoLoadMore = true
+        // 当自动加载开启,同时数据不满一屏时,是否继续执行自动加载更多(默认为true)
+        mAdapter.loadMoreModule.isEnableLoadMoreIfNotFullPage = true
+        mAdapter.animationEnable = true
+        mAdapter.setAnimationWithDefault(BaseQuickAdapter.AnimationType.SlideInBottom)
+
+        createItemDecoration()?.let {
+            mRvContent.addItemDecoration(it)
+        }
+
+        mRvContent.adapter = mAdapter
+    }
+
+    /**
+     * 初始化网络监听
+     */
+    private fun initNetWatchdog() {
+        mNetWatchdog.startWatch()
+        mNetWatchdog.setNetConnectedListener(object : NetConnectedListener {
+            override fun onReNetConnected(isReconnect: Boolean) {
+                mMultipleStatusView?.let {
+                    if (isReconnect) {
+                        it.showContent()
+                    }
+                }
+                if (mAdapter.data.isEmpty() && !mLoadingData && isReconnect) {
+                    loadData(true)
+                }
+            }
+
+            override fun onNetUnConnected() {
+                if (mAdapter.data.isEmpty()) {
+                    mMultipleStatusView?.showNoNetwork()
+                }
+            }
+        })
+    }
+
+    override fun onDestroyView() {
+        mNetWatchdog.stopWatch()
+        super.onDestroyView()
+    }
+
+    protected fun loadData(refresh: Boolean) {
+        mLoadingData = true
+        if (refresh) {
+            mCurrentPage = FIRST_PAGE
+        }
+        mRefresh = refresh
+        showLoadingView()
+        val disposable = queryData()
+        addDisposable(disposable)
+    }
+
+    abstract fun queryData(): Disposable
+
+    override fun onLoadData() {
+        loadData(true)
+    }
+
+    override fun onLoadMore() {
+        val dataSize = mAdapter.data.size
+        if (dataSize < PAGE_SIZE) {
+            mAdapter.loadMoreModule.loadMoreEnd(false)
+        } else {
+            if (dataSize % PAGE_SIZE != 0) {
+                mAdapter.loadMoreModule.loadMoreEnd(false)
+            } else {
+                mCurrentPage++
+                loadData(false)
+            }
+        }
+    }
+
+    /**
+     * 数据加载成功的处理逻辑
+     *
+     * @param data 从服务端查询的数据
+     */
+    protected open fun dispatchLoadDataSuccess(data: List<T>?) {
+        mLoadingData = false
+        if (isDetached) return
+
+        mMultipleStatusView?.showContent()
+        if (mRefresh) {
+            if (null == data || data.isEmpty()) {
+                mMultipleStatusView?.showEmpty()
+            }
+            mAdapter.setNewInstance(data?.toMutableList())
+            mAdapter.loadMoreModule.loadMoreComplete()
+            if (null == data || data.size < PAGE_SIZE) {
+                mAdapter.loadMoreModule.loadMoreEnd(goneLoadMoreView())
+            }
+            mSrlRefresh.isRefreshing = false
+        } else {
+            if (null == data || data.size < PAGE_SIZE) {
+                if (null != data) {
+                    mAdapter.addData(data)
+                }
+                mAdapter.loadMoreModule.loadMoreEnd(goneLoadMoreView())
+            } else {
+                mAdapter.addData(data)
+                mAdapter.loadMoreModule.loadMoreComplete()
+            }
+        }
+    }
+
+    /**
+     * 数据加载失败的处理逻辑
+     */
+    protected open fun dispatchLoadDataFailure() {
+        mLoadingData = false
+        if (isDetached) return
+
+        if (mRefresh) {
+            mMultipleStatusView?.showError()
+            mSrlRefresh.isRefreshing = false
+        } else {
+            mCurrentPage = if (mCurrentPage-- < FIRST_PAGE) FIRST_PAGE else mCurrentPage
+            mAdapter.loadMoreModule.loadMoreEnd(goneLoadMoreView())
+        }
+    }
+
+    protected open fun goneLoadMoreView() = false
+
+    private fun showLoadingView() {
+        mMultipleStatusView?.let {
+            if (mRefresh) {
+                it.showLoading()
+            }
+        }
+    }
+
+    override fun loadDateCompleted() = mAdapter.data.isNotEmpty()
+
+    override fun onItemClick(adapter: BaseQuickAdapter<*, *>, view: View, position: Int) {
+    }
+
+    override fun onItemChildClick(adapter: BaseQuickAdapter<*, *>, view: View, position: Int) {
+    }
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        EventBus.getDefault().register(this)
+    }
+
+    override fun onDestroy() {
+        super.onDestroy()
+        EventBus.getDefault().unregister(this)
+    }
+
+    @Subscribe(threadMode = ThreadMode.MAIN)
+    fun onMessageEvent(event: RefreshEvent) {
+        if (event.refresh) {
+            dispatchRefreshEvent(event)
+        }
+    }
+
+    protected open fun dispatchRefreshEvent(event: RefreshEvent) {
+    }
+
+}

+ 301 - 0
app/src/main/java/com/rc/core/ui/widget/MultipleStatusView.kt

@@ -0,0 +1,301 @@
+package com.rc.core.ui.widget
+
+import android.content.Context
+import android.content.res.TypedArray
+import android.util.AttributeSet
+import android.view.View
+import android.view.ViewGroup
+import android.widget.RelativeLayout
+import androidx.annotation.LayoutRes
+import xn.hxp.R
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+class MultipleStatusView @JvmOverloads constructor(
+    context: Context,
+    attrs: AttributeSet? = null,
+    defStyleAttr: Int = 0
+) :
+    RelativeLayout(context, attrs, defStyleAttr) {
+
+    companion object {
+        private const val STATUS_CONTENT = 0x00
+        private const val STATUS_LOADING = 0x01
+        private const val STATUS_EMPTY = 0x02
+        private const val STATUS_ERROR = 0x03
+        private const val STATUS_NO_NETWORK = 0x04
+
+        private val DEFAULT_LAYOUT_PARAMS =
+            LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
+
+        private const val NULL_RESOURCE_ID = -1
+    }
+
+    private var mEmptyView: View? = null
+    private var mErrorView: View? = null
+    private var mLoadingView: View? = null
+    private var mNoNetworkView: View? = null
+    private var mContentView: View? = null
+    private var mEmptyViewResId = NULL_RESOURCE_ID
+    private var mErrorViewResId = NULL_RESOURCE_ID
+    private var mLoadingViewResId = NULL_RESOURCE_ID
+    private var mNoNetworkViewResId = NULL_RESOURCE_ID
+    private var mContentViewResId = NULL_RESOURCE_ID
+
+    private var mViewStatus = -1
+
+    private val mOtherIds = ArrayList<Int>()
+
+    init {
+        val a: TypedArray =
+            context.obtainStyledAttributes(attrs, R.styleable.MultipleStatusView, defStyleAttr, 0)
+        mEmptyViewResId = a.getResourceId(
+            R.styleable.MultipleStatusView_emptyView,
+            R.layout.view_multiple_empty_view
+        )
+        mErrorViewResId = a.getResourceId(
+            R.styleable.MultipleStatusView_errorView,
+            R.layout.view_multiple_error_view
+        )
+        mLoadingViewResId = a.getResourceId(
+            R.styleable.MultipleStatusView_loadingView,
+            R.layout.view_multiple_loading_view
+        )
+        mNoNetworkViewResId = a.getResourceId(
+            R.styleable.MultipleStatusView_noNetworkView,
+            R.layout.view_multiple_no_network_view
+        )
+        mContentViewResId =
+            a.getResourceId(R.styleable.MultipleStatusView_contentView, NULL_RESOURCE_ID)
+        a.recycle()
+    }
+
+    override fun onFinishInflate() {
+        super.onFinishInflate()
+        showContent()
+    }
+
+    override fun onDetachedFromWindow() {
+        super.onDetachedFromWindow()
+        clear(mEmptyView, mLoadingView, mErrorView, mNoNetworkView)
+        if (mOtherIds.isNotEmpty()) {
+            mOtherIds.clear()
+        }
+        if (null != mOnRetryClickListener) {
+            mOnRetryClickListener = null
+        }
+        if (null != mViewStatusListener) {
+            mViewStatusListener = null
+        }
+    }
+
+    /**
+     * 获取当前状态
+     *
+     * @return 视图状态
+     */
+    fun getViewStatus() = mViewStatus
+
+    private var mOnRetryClickListener: ((view: View) -> Unit)? = null
+
+    /**
+     * 设置重试点击事件
+     *
+     * @param listener 重试点击事件
+     */
+    fun setOnRetryClickListener(listener: (view: View) -> Unit) {
+        this.mOnRetryClickListener = listener
+    }
+
+    /**
+     * 显示空视图
+     *
+     * @param view         自定义视图
+     * @param layoutParams 布局参数
+     */
+    fun showEmpty(
+        @LayoutRes layoutId: Int = mEmptyViewResId,
+        view: View = mEmptyView ?: View.inflate(context, layoutId, null),
+        layoutParams: ViewGroup.LayoutParams = DEFAULT_LAYOUT_PARAMS
+    ) {
+        changeViewStatus(STATUS_EMPTY)
+        if (null == mEmptyView) {
+            mEmptyView = view
+            val emptyRetryView: View? = mEmptyView!!.findViewById(R.id.empty_retry_view)
+            if (null != mOnRetryClickListener && null != emptyRetryView) {
+                emptyRetryView.setOnClickListener(mOnRetryClickListener)
+            }
+            mOtherIds.add(mEmptyView!!.id)
+            addView(mEmptyView, 0, layoutParams)
+        }
+        showViewById(mEmptyView!!.id)
+    }
+
+    /**
+     * 显示错误视图
+     *
+     * @param view         自定义视图
+     * @param layoutParams 布局参数
+     */
+    fun showError(
+        @LayoutRes layoutId: Int = mErrorViewResId,
+        view: View = mErrorView ?: View.inflate(context, layoutId, null),
+        layoutParams: ViewGroup.LayoutParams = DEFAULT_LAYOUT_PARAMS
+    ) {
+        changeViewStatus(STATUS_ERROR)
+        if (null == mErrorView) {
+            mErrorView = view
+            val errorRetryView: View? = mErrorView!!.findViewById(R.id.error_retry_view)
+            if (null != mOnRetryClickListener && null != errorRetryView) {
+                errorRetryView.setOnClickListener(mOnRetryClickListener)
+            }
+            mOtherIds.add(mErrorView!!.id)
+            addView(mErrorView, 0, layoutParams)
+        }
+        showViewById(mErrorView!!.id)
+    }
+
+    /**
+     * 显示加载中视图
+     *
+     * @param view         自定义视图
+     * @param layoutParams 布局参数
+     */
+    fun showLoading(
+        @LayoutRes layoutId: Int = mLoadingViewResId,
+        view: View = mLoadingView ?: View.inflate(context, layoutId, null),
+        layoutParams: ViewGroup.LayoutParams = DEFAULT_LAYOUT_PARAMS
+    ) {
+        changeViewStatus(STATUS_LOADING)
+        if (null == mLoadingView) {
+            mLoadingView = view
+            mOtherIds.add(mLoadingView!!.id)
+            addView(mLoadingView, 0, layoutParams)
+        }
+        showViewById(mLoadingView!!.id)
+    }
+
+    /**
+     * 显示无网络视图
+     *
+     * @param view         自定义视图
+     * @param layoutParams 布局参数
+     */
+    fun showNoNetwork(
+        @LayoutRes layoutId: Int = mNoNetworkViewResId,
+        view: View = mNoNetworkView ?: View.inflate(context, layoutId, null),
+        layoutParams: ViewGroup.LayoutParams = DEFAULT_LAYOUT_PARAMS
+    ) {
+        changeViewStatus(STATUS_NO_NETWORK)
+        if (null == mNoNetworkView) {
+            mNoNetworkView = view
+            val noNetworkRetryView: View? =
+                mNoNetworkView!!.findViewById(R.id.no_network_retry_view)
+            if (null != mOnRetryClickListener && null != noNetworkRetryView) {
+                noNetworkRetryView.setOnClickListener(mOnRetryClickListener)
+            }
+            mOtherIds.add(mNoNetworkView!!.id)
+            addView(mNoNetworkView, 0, layoutParams)
+        }
+        showViewById(mNoNetworkView!!.id)
+    }
+
+    /**
+     * 显示内容视图
+     *
+     * @param view         自定义视图
+     * @param layoutParams 布局参数
+     */
+    fun showContent(
+        @LayoutRes layoutId: Int = mContentViewResId,
+        view: View? = mContentView,
+        layoutParams: ViewGroup.LayoutParams = DEFAULT_LAYOUT_PARAMS
+    ) {
+        // ?: View.inflate(context, layoutId, null)
+
+        changeViewStatus(STATUS_CONTENT)
+
+        if (null == mContentView) {
+            if (NULL_RESOURCE_ID != layoutId) {
+                mContentView = View.inflate(context, layoutId, null)
+                addView(mContentView, 0, layoutParams)
+            } else {
+                showContentView()
+                return
+            }
+        }
+
+
+//        if (NULL_RESOURCE_ID == layoutId && null == mContentView) {
+////            mContentView = View.inflate(context, layoutId, null)
+////            addView(mContentView, 0, layoutParams)
+//        }
+
+
+//        if (NULL_RESOURCE_ID == layoutId && mContentView == null) {
+//            showContentView()
+//        } else {
+        clear(mContentView)
+        mContentView = view
+        addView(mContentView, 0, layoutParams)
+        showViewById(mContentView!!.id)
+//        }
+    }
+
+    private fun showViewById(viewId: Int) {
+        for (i in 0 until childCount) {
+            val view = getChildAt(i)
+            view.visibility = if (view.id == viewId) View.VISIBLE else View.GONE
+        }
+    }
+
+    private fun showContentView() {
+        for (i in 0 until childCount) {
+            val view = getChildAt(i)
+            view.visibility = if (mOtherIds.contains(view.id)) View.GONE else View.VISIBLE
+        }
+    }
+
+    private fun checkNull(obj: Any?, hint: String) {
+        if (null == obj) {
+            throw NullPointerException(hint)
+        }
+    }
+
+    private fun clear(vararg views: View?) {
+        try {
+            for (view in views) {
+                view?.let { removeView(it) }
+            }
+        } catch (e: Exception) {
+            e.printStackTrace()
+        }
+    }
+
+    // 视图状态改变接口
+    private var mViewStatusListener: ((oldViewStatus: Int, newViewStatus: Int) -> Unit)? = null
+
+    /**
+     * 设置视图状态改变监听事件
+     */
+    fun setOnViewStatusChangeListener(listener: (oldViewStatus: Int, newViewStatus: Int) -> Unit) {
+        this.mViewStatusListener = listener
+    }
+
+    /**
+     * 改变视图状态
+     *
+     * @param newViewStatus 新的视图状态
+     */
+    private fun changeViewStatus(newViewStatus: Int) {
+        if (mViewStatus == newViewStatus) {
+            return
+        }
+        mViewStatusListener?.invoke(mViewStatus, newViewStatus)
+        mViewStatus = newViewStatus
+    }
+
+}

+ 53 - 0
app/src/main/java/com/rc/core/ui/widget/decoration/GridSpacingItemDecoration.java

@@ -0,0 +1,53 @@
+package com.rc.core.ui.widget.decoration;
+
+import android.graphics.Rect;
+import android.view.View;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+/**
+ * FileName: GridSpacingItemDecoration
+ * Description:
+ */
+public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration {
+
+    private final int spanCount; //列数
+    private final int horizontalSpacing; // 水平间隔
+    private final int verticalSpacing; // 垂直间隔
+    private final boolean includeEdge; //是否包含边缘
+
+    public GridSpacingItemDecoration(int spanCount, int spacing, boolean includeEdge) {
+        this(spanCount, spacing, spacing, includeEdge);
+    }
+
+    public GridSpacingItemDecoration(int spanCount, int horizontalSpacing, int verticalSpacing, boolean includeEdge) {
+        this.spanCount = spanCount;
+        this.horizontalSpacing = horizontalSpacing;
+        this.verticalSpacing = verticalSpacing;
+        this.includeEdge = includeEdge;
+    }
+
+    @Override
+    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
+
+        //这里是关键,需要根据你有几列来判断
+        int position = parent.getChildAdapterPosition(view); // item position
+        int column = position % spanCount; // item column
+
+        if (includeEdge) {
+            outRect.left = horizontalSpacing - column * horizontalSpacing / spanCount; // spacing - column * ((1f / spanCount) * spacing)
+            outRect.right = (column + 1) * horizontalSpacing / spanCount; // (column + 1) * ((1f / spanCount) * spacing)
+
+            if (position < spanCount) { // top edge
+                outRect.top = verticalSpacing;
+            }
+            outRect.bottom = verticalSpacing; // item bottom
+        } else {
+            outRect.left = column * horizontalSpacing / spanCount; // column * ((1f / spanCount) * spacing)
+            outRect.right = horizontalSpacing - (column + 1) * horizontalSpacing / spanCount; // spacing - (column + 1) * ((1f /    spanCount) * spacing)
+            if (position >= spanCount) {
+                outRect.top = verticalSpacing; // item top
+            }
+        }
+    }
+}

+ 148 - 0
app/src/main/java/com/rc/core/ui/widget/decoration/NoLastLineItemDecoration.java

@@ -0,0 +1,148 @@
+package com.rc.core.ui.widget.decoration;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.Log;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+public class NoLastLineItemDecoration extends RecyclerView.ItemDecoration {
+    public static final int HORIZONTAL = LinearLayout.HORIZONTAL;
+    public static final int VERTICAL = LinearLayout.VERTICAL;
+
+    private static final String TAG = "DividerItem";
+    private static final int[] ATTRS = new int[]{android.R.attr.listDivider};
+
+    private Drawable mDivider;
+
+    /**
+     * Current orientation. Either {@link #HORIZONTAL} or {@link #VERTICAL}.
+     */
+    private int mOrientation;
+
+    private final Rect mBounds = new Rect();
+
+    public NoLastLineItemDecoration(Context context, int orientation) {
+        final TypedArray a = context.obtainStyledAttributes(ATTRS);
+        mDivider = a.getDrawable(0);
+        if (mDivider == null) {
+            Log.w(TAG, "@android:attr/listDivider was not set in the theme used for this "
+                    + "DividerItemDecoration. Please set that attribute all call setDrawable()");
+        }
+        a.recycle();
+        setOrientation(orientation);
+    }
+
+    /**
+     * Sets the orientation for this divider. This should be called if
+     * {@link RecyclerView.LayoutManager} changes orientation.
+     *
+     * @param orientation {@link #HORIZONTAL} or {@link #VERTICAL}
+     */
+    public void setOrientation(int orientation) {
+        if (orientation != HORIZONTAL && orientation != VERTICAL) {
+            throw new IllegalArgumentException(
+                    "Invalid orientation. It should be either HORIZONTAL or VERTICAL");
+        }
+        mOrientation = orientation;
+    }
+
+    /**
+     * Sets the {@link Drawable} for this divider.
+     *
+     * @param drawable Drawable that should be used as a divider.
+     */
+    public void setDrawable(@NonNull Drawable drawable) {
+        if (drawable == null) {
+            throw new IllegalArgumentException("Drawable cannot be null.");
+        }
+        mDivider = drawable;
+    }
+
+    @Override
+    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
+        if (parent.getLayoutManager() == null || mDivider == null) {
+            return;
+        }
+        if (mOrientation == VERTICAL) {
+            drawVertical(c, parent);
+        } else {
+            drawHorizontal(c, parent);
+        }
+    }
+
+    private void drawVertical(Canvas canvas, RecyclerView parent) {
+        canvas.save();
+        final int left;
+        final int right;
+        //noinspection AndroidLintNewApi - NewApi lint fails to handle overrides.
+        if (parent.getClipToPadding()) {
+            left = parent.getPaddingLeft();
+            right = parent.getWidth() - parent.getPaddingRight();
+            canvas.clipRect(left, parent.getPaddingTop(), right,
+                    parent.getHeight() - parent.getPaddingBottom());
+        } else {
+            left = 0;
+            right = parent.getWidth();
+        }
+
+        final int childCount = parent.getChildCount();
+        for (int i = 0; i < childCount - 1; i++) {
+            final View child = parent.getChildAt(i);
+            parent.getDecoratedBoundsWithMargins(child, mBounds);
+            final int bottom = mBounds.bottom + Math.round(child.getTranslationY());
+            final int top = bottom - mDivider.getIntrinsicHeight();
+            mDivider.setBounds(left, top, right, bottom);
+            mDivider.draw(canvas);
+        }
+        canvas.restore();
+    }
+
+    private void drawHorizontal(Canvas canvas, RecyclerView parent) {
+        canvas.save();
+        final int top;
+        final int bottom;
+        //noinspection AndroidLintNewApi - NewApi lint fails to handle overrides.
+        if (parent.getClipToPadding()) {
+            top = parent.getPaddingTop();
+            bottom = parent.getHeight() - parent.getPaddingBottom();
+            canvas.clipRect(parent.getPaddingLeft(), top,
+                    parent.getWidth() - parent.getPaddingRight(), bottom);
+        } else {
+            top = 0;
+            bottom = parent.getHeight();
+        }
+
+        final int childCount = parent.getChildCount();
+        for (int i = 0; i < childCount - 1; i++) {
+            final View child = parent.getChildAt(i);
+            parent.getLayoutManager().getDecoratedBoundsWithMargins(child, mBounds);
+            final int right = mBounds.right + Math.round(child.getTranslationX());
+            final int left = right - mDivider.getIntrinsicWidth();
+            mDivider.setBounds(left, top, right, bottom);
+            mDivider.draw(canvas);
+        }
+        canvas.restore();
+    }
+
+    @Override
+    public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
+                               RecyclerView.State state) {
+        if (mDivider == null) {
+            outRect.set(0, 0, 0, 0);
+            return;
+        }
+        if (mOrientation == VERTICAL) {
+            outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
+        } else {
+            outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
+        }
+    }
+}
+

+ 195 - 0
app/src/main/java/com/rc/core/util/ApkController.kt

@@ -0,0 +1,195 @@
+package com.rc.core.util
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import android.os.Build
+import android.os.Process
+import androidx.core.content.FileProvider
+import com.rc.core.ui.ActivityCollector
+import java.io.*
+import java.nio.charset.Charset
+import kotlin.system.exitProcess
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+object ApkController {
+
+    fun installAPK(
+        apkFile: File,
+        appContext: Context,
+        applicationId: String
+    ) {
+        if (isRootPermission()) {
+            install(apkFile.path)
+//            startApp("com.dlc.chemical", "com.dlc.chemical.ui.SplashActivity")
+            startApp(appContext, "com.dlc.chemical", "com.dlc.chemical.ui.SplashActivity")
+            // silentInstall(appContext, apkFile.path)
+        } else {
+            installOnTask(apkFile, appContext, applicationId)
+        }
+    }
+
+    /**
+     * 判定app是否获取root权限
+     */
+    fun isRootPermission(): Boolean {
+        try {
+            val process = Runtime.getRuntime().exec("su")
+            val os = DataOutputStream(process.outputStream)
+            os.writeBytes("ls /data/data/\n")
+            os.writeBytes("exit\n")
+            os.flush()
+            val reader = BufferedReader(InputStreamReader(process.inputStream))
+            val builder = StringBuilder()
+            var line: String? = null
+            while (reader.readLine().also { line = it } != null) {
+                builder.append(line)
+                builder.append(System.getProperty("line.separator"))
+            }
+            val result = builder.toString()
+            if (result.contains("com.android.phone")) {
+                return true
+            }
+        } catch (e: IOException) {
+            return false
+        }
+        return false
+    }
+
+    /**
+     * 安装apk
+     */
+    fun install(apkPath: String): Boolean {
+        var result = false
+        var dataOutputStream: DataOutputStream? = null
+        var errorStream: BufferedReader? = null
+        try {
+            val process = Runtime.getRuntime().exec("su")
+            dataOutputStream = DataOutputStream(process.outputStream)
+            val command = "pm install -r $apkPath\n"
+            dataOutputStream.write(command.toByteArray(Charset.forName("utf-8")))
+            dataOutputStream.flush()
+            dataOutputStream.writeBytes("exit\n")
+            dataOutputStream.flush()
+            process.waitFor()
+            errorStream = BufferedReader(InputStreamReader(process.errorStream))
+            var msg = ""
+            var line: String
+            while (errorStream.readLine().also { line = it } != null) {
+                msg += line
+            }
+            if (!msg.contains("Failure")) {
+                result = true
+            }
+        } catch (e: Exception) {
+            e.printStackTrace()
+        } finally {
+            try {
+                dataOutputStream?.close()
+                errorStream?.close()
+            } catch (e: IOException) {
+            }
+        }
+        return result
+    }
+
+    fun startApp(appContext: Context, pkg: String, cls: String) {
+        val intent = Intent.makeRestartActivityTask(ComponentName(pkg, cls))
+        appContext.startActivity(intent)
+        exitProcess(0)
+    }
+
+    /**
+     * 启动app
+     */
+    fun startApp(packageName: String, activityName: String): Boolean {
+        var result = false
+        var dataOutputStream: DataOutputStream? = null
+        var errorStream: BufferedReader? = null
+        try {
+            val process = Runtime.getRuntime().exec("su")
+            dataOutputStream = DataOutputStream(process.outputStream)
+            val command = "sleep 120; am start -n $packageName/$activityName\n"
+            dataOutputStream.write(command.toByteArray(Charset.forName("utf-8")))
+            dataOutputStream.flush()
+            dataOutputStream.writeBytes("exit\n")
+            dataOutputStream.flush()
+            process.waitFor()
+            errorStream = BufferedReader(InputStreamReader(process.errorStream))
+            var msg = ""
+            var line: String
+            while (errorStream.readLine().also { line = it } != null) {
+                msg += line
+            }
+            if (!msg.contains("Failure")) {
+                result = true
+            }
+        } catch (e: Exception) {
+            e.printStackTrace()
+        } finally {
+            try {
+                dataOutputStream?.close()
+                errorStream?.close()
+            } catch (e: IOException) {
+            }
+        }
+        return result
+    }
+
+    /**
+     * 静默安装
+     *
+     * @param context
+     * @param apkPath
+     * @param autoStart 安装后是否自动启动
+     */
+    fun silentInstall(context: Context, apkPath: String, autoStart: Boolean = true) {
+        val intent = Intent().apply {
+            action = "android.intent.action.installslient"
+            putExtra("uri", apkPath)
+            putExtra("component", "com.dlc.chemical/com.dlc.chemical.ui.SplashActivity")
+            putExtra("enable", autoStart)
+        }
+        context.sendBroadcast(intent)
+    }
+
+    fun installOnTask(
+        apkFile: File,
+        appContext: Context,
+        applicationId: String
+    ) {
+        val intent = Intent()
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+        intent.action = Intent.ACTION_VIEW
+
+        if (apkFile.exists()) {
+            // 提升目录读写权限,否则可能出现解析异常
+            DeviceUtils.promotePermission(apkFile.parentFile!!.path)
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+                // 7.0以上的版本,特殊处理
+                intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+                // 参数1 上下文, 参数2 Provider主机地址 和配置文件中保持一致   参数3  共享的文件
+                val uri = FileProvider.getUriForFile(
+                    appContext,
+                    "${applicationId}.fileprovider",
+                    apkFile
+                )
+                intent.setDataAndType(uri, "application/vnd.android.package-archive")
+            } else {
+                intent.setDataAndType(
+                    Uri.fromFile(apkFile),
+                    "application/vnd.android.package-archive"
+                )
+            }
+            appContext.startActivity(intent)
+            ActivityCollector.finishAll()
+            Process.killProcess(Process.myPid())
+        }
+    }
+
+}

+ 61 - 0
app/src/main/java/com/rc/core/util/ApkUpdater.kt

@@ -0,0 +1,61 @@
+package com.rc.core.util
+
+import android.content.Context
+import com.blankj.utilcode.util.LogUtils
+import com.rc.httpcore.net.DownloadListener
+import com.rc.httpcore.net.DownloadTask
+import java.io.File
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+class ApkUpdater(
+    private val appContext: Context,
+    private val applicationId: String,
+    private val callback: DownloadCallback? = null
+) {
+
+    fun downloadApk(downloadUrl: String) {
+        val fileName = "chemical-release.apk"
+        val apkFile = File(appContext.cacheDir, fileName)
+        if (apkFile.exists()) {
+            apkFile.delete()
+        }
+        val downloadTask = DownloadTask(
+            listener = object : DownloadListener {
+                override fun onProgress(progress: Int) {
+                    LogUtils.i("download apk, progress:$progress")
+                    callback?.onProgress(progress)
+                }
+
+                override fun onSuccess() {
+                    callback?.onSuccess(apkFile.path)
+                    ApkController.installAPK(apkFile, appContext, applicationId)
+                }
+
+                override fun onFailed(errMsg: String?) {
+                    callback?.onFailed(errMsg)
+                }
+
+                override fun onPaused() {
+                }
+
+                override fun onCanceled() {
+                }
+
+            }, dlPath = appContext.cacheDir.path,
+            fileName = fileName,
+            headers = mapOf("Accept-Encoding" to "identity")
+        )
+        downloadTask.execute(downloadUrl)
+    }
+
+    interface DownloadCallback {
+        fun onProgress(progress: Int)
+        fun onFailed(errMsg: String?)
+        fun onSuccess(apkFile: String)
+    }
+
+}

+ 208 - 0
app/src/main/java/com/rc/core/util/CrashHandler.kt

@@ -0,0 +1,208 @@
+package com.rc.core.util
+
+import android.content.Context
+import android.content.pm.PackageManager
+import android.os.Build
+import android.os.Environment
+import android.util.Log
+import java.io.*
+import java.text.SimpleDateFormat
+import java.util.*
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+object CrashHandler : Thread.UncaughtExceptionHandler {
+
+    private const val TAG = "LAB_Crash"
+
+    private var mContext: Context? = null
+
+    // 系统默认的 UncaughtException 处理类
+    private var mDefaultHandler: Thread.UncaughtExceptionHandler? = null
+
+    // 用来存储设备信息和异常信息
+    private val infos = HashMap<String, String>()
+
+    // 用于格式化日期,作为日志文件名的一部分
+    private val formatter = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
+
+    /**
+     * 初始化
+     */
+    fun initCrash(context: Context/*,  dstClass: Class<? out Activity>*/) {
+        mContext = context
+//        this.dstClass = dstClass;
+        // 获取系统默认的 UncaughtException 处理器
+        mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler()
+        // 设置该 CrashHandler 为程序的默认处理器
+        Thread.setDefaultUncaughtExceptionHandler(this)
+    }
+
+    override fun uncaughtException(thread: Thread, ex: Throwable) {
+        if (!handleException(ex) && mDefaultHandler != null) {
+            // 如果用户没有处理则让系统默认的异常处理器来处理
+            mDefaultHandler!!.uncaughtException(thread, ex)
+        } else {
+            try {
+                Thread.sleep(3000)
+            } catch (e: InterruptedException) {
+            }
+
+            // 退出程序,注释下面的重启启动程序代码
+//            SystemUtil.restartThroughIntentCompatMakeRestartActivityTask(
+//                ContextUtil.getContext(),
+//                dstClass
+//            );
+        }
+    }
+
+    /**
+     * 自定义错误处理,收集错误信息,发送错误报告等操作均在此完成
+     *
+     * @param ex
+     * @return true:如果处理了该异常信息;否则返回 false
+     */
+    private fun handleException(ex: Throwable?): Boolean {
+        if (ex == null) return false
+        Log.e(TAG, "", ex)
+
+        // 收集设备参数信息
+        mContext?.let {
+            collectDeviceInfo(it)
+        }
+
+        // 读取Error信息
+        readThrowableInfo(ex)?.let { error ->
+            mContext?.let {
+                // 保存日志文件
+                saveCrashInfo2File(error, it.cacheDir)
+            }
+        }
+        return true
+    }
+
+    /**
+     * 收集设备参数信息
+     * @param ctx
+     */
+    private fun collectDeviceInfo(ctx: Context) {
+        try {
+            ctx.packageManager.getPackageInfo(ctx.packageName, PackageManager.GET_ACTIVITIES)
+                .let { packageInfo ->
+                    infos["versionName"] = packageInfo.versionName ?: "null"
+                    infos["versionCode"] = packageInfo.versionCode.toString()
+                }
+        } catch (e: PackageManager.NameNotFoundException) {
+            Log.e(TAG, "an error occured when collect package info", e)
+        }
+
+        Build::class.java.declaredFields.forEach { field ->
+            try {
+                field.isAccessible = true
+                infos[field.name] = field.get(null).toString()
+                Log.d(TAG, field.name + " : " + field.get(null))
+            } catch (e: Exception) {
+                Log.e(TAG, "an error occured when collect crash info", e)
+            }
+        }
+    }
+
+    /**
+     * 保存错误信息到文件中
+     *
+     * @param error
+     * @return 返回文件名称, 便于将文件传送到服务器
+     */
+    private fun saveCrashInfo2File(error: String, cacheDir: File): String? {
+        val time = formatter.format(Date())
+        val sb = StringBuffer()
+        sb.append(time + "\n")
+        infos.entries.forEach { entry ->
+            sb.append("${entry.key} = ${entry.value} \n")
+        }
+
+        var fos: FileOutputStream? = null
+        try {
+            sb.append(error)
+            sb.append("\n\n\n")
+
+            saveErrorLogToSp(sb.toString())
+
+            val fileName = "error.log"
+            if (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED) {
+                val errorFile = File(cacheDir, fileName)
+                if (!errorFile.exists()) {
+                    errorFile.createNewFile()
+                }
+                fos = FileOutputStream(errorFile)
+                fos.write(sb.toString().toByteArray())
+                fos.close()
+            }
+
+            return fileName
+        } catch (e: Exception) {
+            Log.e(TAG, "an error occured while writing file...", e)
+        } finally {
+            try {
+                fos?.let {
+                    it.close()
+                    fos = null
+                }
+            } catch (e: IOException) {
+                e.printStackTrace()
+            }
+        }
+
+        return null
+    }
+
+    /**
+     * 读取Throwable中的错误日志
+     *
+     * @param ex
+     * @return
+     */
+    private fun readThrowableInfo(ex: Throwable): String? {
+        var result: String? = null
+        var writer: Writer? = null
+        var printWriter: PrintWriter? = null
+        try {
+            writer = StringWriter()
+            printWriter = PrintWriter(writer)
+            ex.printStackTrace(printWriter)
+            var cause = ex.cause
+            while (cause != null) {
+                cause.printStackTrace(printWriter)
+                cause = cause.cause
+            }
+            result = writer.toString()
+        } catch (e: Exception) {
+            Log.e(TAG, "read throwable error....", e)
+        } finally {
+            printWriter?.let {
+                it.close()
+                printWriter = null
+            }
+            try {
+                writer?.let {
+                    it.close()
+                    writer = null
+                }
+            } catch (e: IOException) {
+                e.printStackTrace()
+            }
+        }
+        return result
+    }
+
+    private fun saveErrorLogToSp(errorInfo: String) {
+        mContext?.let { context ->
+            val sp = context.getSharedPreferences("error-log", Context.MODE_PRIVATE)
+            sp.edit().putString("error", errorInfo).apply()
+        }
+    }
+
+}

+ 120 - 0
app/src/main/java/com/rc/core/util/CrashHelper.kt

@@ -0,0 +1,120 @@
+package com.rc.core.util
+
+import android.os.Environment
+import android.util.Log
+import java.io.*
+import java.text.SimpleDateFormat
+import java.util.*
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+object CrashHelper {
+
+    private const val TAG = "LAB_Crash"
+
+    /**
+     * 自定义错误处理,收集错误信息,发送错误报告等操作均在此完成
+     *
+     * @param ex
+     * @return true:如果处理了该异常信息;否则返回 false
+     */
+    fun handleException(ex: Throwable?): Boolean {
+        if (ex == null) return false
+        Log.e(TAG, "", ex)
+
+        // 读取Error信息
+        readThrowableInfo(ex)?.let { error ->
+            // 保存日志文件
+            saveCrashInfo2File(error, Environment.getExternalStorageDirectory())
+        }
+        return true
+    }
+
+    /**
+     * 读取Throwable中的错误日志
+     *
+     * @param ex
+     * @return
+     */
+    private fun readThrowableInfo(ex: Throwable): String? {
+        var result: String? = null
+        var writer: Writer? = null
+        var printWriter: PrintWriter? = null
+        try {
+            writer = StringWriter()
+            printWriter = PrintWriter(writer)
+            ex.printStackTrace(printWriter)
+            var cause = ex.cause
+            while (cause != null) {
+                cause.printStackTrace(printWriter)
+                cause = cause.cause
+            }
+            result = writer.toString()
+        } catch (e: Exception) {
+            Log.e(TAG, "read throwable error....", e)
+        } finally {
+            printWriter?.let {
+                it.close()
+                printWriter = null
+            }
+            try {
+                writer?.let {
+                    it.close()
+                    writer = null
+                }
+            } catch (e: IOException) {
+                e.printStackTrace()
+            }
+        }
+        return result
+    }
+
+    /**
+     * 保存错误信息到文件中
+     *
+     * @param error
+     * @return 返回文件名称, 便于将文件传送到服务器
+     */
+    fun saveCrashInfo2File(error: String, cacheDir: File): String? {
+        val formatter = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
+        val time = formatter.format(Calendar.getInstance().time)
+        val sb = StringBuffer()
+        sb.append(time + "\n")
+
+        var fos: FileOutputStream? = null
+        try {
+            sb.append(error)
+            sb.append("\n\n\n")
+
+            val fileName = "error-${time}.log"
+            if (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED) {
+                val errorFile = File(cacheDir, fileName)
+                if (!errorFile.exists()) {
+                    errorFile.createNewFile()
+                }
+                fos = FileOutputStream(errorFile)
+                fos.write(sb.toString().toByteArray())
+                fos.close()
+            }
+
+            return fileName
+        } catch (e: Exception) {
+            Log.e(TAG, "an error occured while writing file...", e)
+        } finally {
+            try {
+                fos?.let {
+                    it.close()
+                    fos = null
+                }
+            } catch (e: IOException) {
+                e.printStackTrace()
+            }
+        }
+
+        return null
+    }
+
+}

+ 102 - 0
app/src/main/java/com/rc/core/util/DateUtils.kt

@@ -0,0 +1,102 @@
+package com.rc.core.util
+
+import android.text.TextUtils
+import java.text.DateFormat
+import java.text.ParseException
+import java.text.SimpleDateFormat
+import java.util.*
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+object DateUtils {
+
+    /**
+     * 比较两个日期的年月日
+     * @param c1
+     * @param c2
+     * @return c1 > c2 返回 1;
+     * c1 = c2 返回 0;
+     * c1 < c2 返回 -1
+     */
+    fun compareToYMD(c1: Calendar?, c2: Calendar?): Int {
+        if (null == c1 || null == c2) {
+            return 0
+        }
+        clearTime(c1)
+        clearTime(c2)
+        return c1.compareTo(c2)
+    }
+
+    /**
+     * 重置时、分、秒、毫秒
+     */
+    private fun clearTime(calendar: Calendar) {
+        calendar[Calendar.HOUR_OF_DAY] = 0
+        calendar[Calendar.MINUTE] = 0
+        calendar[Calendar.SECOND] = 0
+        calendar[Calendar.MILLISECOND] = 0
+    }
+
+    /**
+     * yyyy-MM-dd 格式
+     */
+    fun formatDate(calendar: Calendar): String {
+        val format = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
+        return format.format(calendar.time)
+    }
+
+    /**
+     * 发生错误时,默认返回当前时间
+     *
+     * @param dateStr yyyy-MM-dd 格式
+     */
+    fun parseDate(dateStr: String?): Calendar {
+        return parseDate(dateStr, Calendar.getInstance())
+    }
+
+    /**
+     * @param dateStr yyyy-MM-dd 格式
+     * @param defDate 发生错误时,返回的时间
+     */
+    fun parseDate(dateStr: String?, defDate: Calendar): Calendar {
+        if (TextUtils.isEmpty(dateStr)) {
+            return defDate
+        }
+        try {
+            val calendar = Calendar.getInstance()
+            val format: DateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
+            val date = format.parse(dateStr)
+            if (null != date) {
+                calendar.time = date
+                return calendar
+            }
+        } catch (e: ParseException) {
+            e.printStackTrace()
+        }
+        return defDate
+    }
+
+    /**
+     * 格式化时间戳
+     *
+     * @param timestamp 时间戳,单位秒
+     * @return yyyy-MM-dd格式
+     */
+    fun formatTimestamp(timestamp: String?): String? {
+        if (null == timestamp) return null
+
+        return try {
+            val format: DateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
+            val calendar = Calendar.getInstance()
+            calendar.timeInMillis = timestamp.toLong() * 1000
+            format.format(calendar.time)
+        } catch (e: Exception) {
+            e.printStackTrace()
+            null
+        }
+    }
+
+}

+ 75 - 0
app/src/main/java/com/rc/core/util/DeviceUtils.kt

@@ -0,0 +1,75 @@
+package com.rc.core.util
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.content.pm.PackageManager
+import java.io.IOException
+import kotlin.math.roundToInt
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+object DeviceUtils {
+
+    /**
+     * 获取系统SN序列号
+     */
+    @SuppressLint("PrivateApi")
+    fun getAndroidSN(defSN: String = "NULL"): String {
+        return try {
+            val sysClass = Class.forName("android.os.SystemProperties")
+            sysClass.getMethod("get", String::class.java, String::class.java)
+                .invoke(sysClass, "ro.serialno", defSN) as? String ?: defSN
+        } catch (e: Exception) {
+            e.printStackTrace()
+            defSN
+        }
+    }
+
+    /**
+     * 获取当前apk版本号(versionCode)
+     */
+    fun getVersionCode(context: Context): Int {
+        return try {
+            context.packageManager.getPackageInfo(context.packageName, 0).versionCode
+        } catch (e: PackageManager.NameNotFoundException) {
+            -1
+        }
+    }
+
+    /**
+     * 提升读写权限
+     *
+     * @param filePath 文件路径
+     */
+    fun promotePermission(filePath: String) {
+        val command = "chmod 777 $filePath"
+        val runtime = Runtime.getRuntime()
+        try {
+            runtime.exec(command)
+        } catch (e: IOException) {
+            e.printStackTrace()
+        }
+    }
+
+    /**
+     * 根据手机的分辨率从 dp 的单位 转成为 px(像素)
+     */
+    fun dip2px(context: Context, dpValue: Float): Int {
+        val scale = context.resources.displayMetrics.density
+        return (dpValue * scale + 0.5f).toInt()
+    }
+
+    fun spanCount(context: Context, gridExpectedSize: Int, offset: Int, horizontalSpacing: Int): Int {
+        val screenWidth = context.resources.displayMetrics.widthPixels - offset + horizontalSpacing
+        val expected = screenWidth.toFloat() / (gridExpectedSize.toFloat() + horizontalSpacing)
+        var spanCount = expected.roundToInt()
+        if (spanCount == 0) {
+            spanCount = 1
+        }
+        return spanCount
+    }
+
+}

+ 68 - 0
app/src/main/java/com/rc/core/util/EscapeUnescape.java

@@ -0,0 +1,68 @@
+package com.rc.core.util;
+
+public class EscapeUnescape {
+
+    public static String escape(String src) {
+        int i;
+        char j;
+        StringBuffer tmp = new StringBuffer();
+        tmp.ensureCapacity(src.length() * 6);
+
+        for (i = 0; i < src.length(); i++) {
+
+            j = src.charAt(i);
+
+            if (Character.isDigit(j) || Character.isLowerCase(j) || Character.isUpperCase(j))
+                tmp.append(j);
+            else if (j < 256) {
+                tmp.append("%");
+                if (j < 16)
+                    tmp.append("0");
+                tmp.append(Integer.toString(j, 16));
+            } else {
+                tmp.append("%u");
+                tmp.append(Integer.toString(j, 16));
+            }
+        }
+        return tmp.toString();
+    }
+
+    public static String unescape(String src) {
+        StringBuffer tmp = new StringBuffer();
+        tmp.ensureCapacity(src.length());
+        int lastPos = 0, pos = 0;
+        char ch;
+        while (lastPos < src.length()) {
+            pos = src.indexOf("%", lastPos);
+            if (pos == lastPos) {
+                if (src.charAt(pos + 1) == 'u') {
+                    ch = (char) Integer.parseInt(src.substring(pos + 2, pos + 6), 16);
+                    tmp.append(ch);
+                    lastPos = pos + 6;
+                } else {
+                    ch = (char) Integer.parseInt(src.substring(pos + 1, pos + 3), 16);
+                    tmp.append(ch);
+                    lastPos = pos + 3;
+                }
+            } else {
+                if (pos == -1) {
+                    tmp.append(src.substring(lastPos));
+                    lastPos = src.length();
+                } else {
+                    tmp.append(src.substring(lastPos, pos));
+                    lastPos = pos;
+                }
+            }
+        }
+        return tmp.toString();
+    }
+
+//    public static void main(String[] args) {
+//        String tmp = "~!@#$%^&*()_+|\\=-,./?><;'][{}\"";
+//        System.out.println("testing escape : " + tmp);
+//        tmp = escape(tmp);
+//        System.out.println(tmp);
+//        System.out.println("testing unescape :" + tmp);
+//        System.out.println(unescape(tmp));
+//    }
+}

+ 18 - 0
app/src/main/java/com/rc/core/util/Extension.kt

@@ -0,0 +1,18 @@
+package com.rc.core.util
+
+import android.os.Build
+import android.text.Html
+import android.text.Spanned
+
+fun String.fromHtml(): Spanned {
+    return if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N) {
+        Html.fromHtml(this, Html.FROM_HTML_MODE_LEGACY)
+    } else {
+        Html.fromHtml(this)
+    }
+}
+
+fun String?.trimEndZero(): String {
+    if (null == this) return ""
+    return if (-1 != indexOf('.')) trimEnd('0').trimEnd('.') else this
+}

+ 32 - 0
app/src/main/java/com/rc/core/util/FastClickDelegate.kt

@@ -0,0 +1,32 @@
+package com.rc.core.util
+
+import android.view.View
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+class FastClickDelegate(private val listener: View.OnClickListener) : View.OnClickListener {
+
+    companion object {
+        private const val FAST_CLICK_DELAY_TIME = 1000
+    }
+
+    private var mLastClickTime = 1L
+
+    override fun onClick(v: View?) {
+        if (!isFastClick()) listener.onClick(v)
+    }
+
+    private fun isFastClick(): Boolean {
+        var isFastClick = true
+        val currentTimeMillis = System.currentTimeMillis()
+        if (currentTimeMillis - mLastClickTime > FAST_CLICK_DELAY_TIME) {
+            isFastClick = false
+        }
+        mLastClickTime = currentTimeMillis
+        return isFastClick
+    }
+
+}

+ 69 - 0
app/src/main/java/com/rc/core/util/Format.kt

@@ -0,0 +1,69 @@
+package com.rc.core.util
+
+import java.text.NumberFormat
+import java.text.SimpleDateFormat
+import java.util.*
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+object Format {
+
+    /**
+     * 将时区时间格式化成日期字符串
+     *
+     * @param zonedDateTime 2021-03-30T14:18:41.000+08:00
+     */
+    fun formatZonedDateTime(
+        zonedDateTime: String?,
+        pattern: String = "yyyy-MM-dd HH:mm:ss"
+    ): String {
+        val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX", Locale.getDefault())
+        val data: Date? = try {
+            dateFormat.parse(zonedDateTime)
+        } catch (e: Exception) {
+            null
+        }
+
+        return formatDate(data, pattern)
+    }
+
+    /**
+     * 将时间戳格式化成yyyy-MM-dd HH:mm:ss格式的日期字符串
+     *
+     * @param dateLong
+     * @return
+     */
+    fun formatTimestamp(dateLong: String?, pattern: String = "yyyy-MM-dd HH:mm:ss"): String {
+        val date: Date? = try {
+            dateLong?.let { Date(it.toLong()) }
+        } catch (e: Exception) {
+            null
+        }
+
+        return formatDate(date, pattern)
+    }
+
+    /**
+     * 按给定的格式格式化日期.
+     *
+     * @param date
+     * @param pattern : 格式化格式
+     * @return
+     */
+    fun formatDate(date: Date?, pattern: String = "yyyy-MM-dd"): String {
+        return date?.let {
+            SimpleDateFormat(pattern, Locale.getDefault()).format(it)
+        } ?: ""
+    }
+
+    fun formatFractionDigits(value: Float?, fd: Int = 2): String {
+        if (null == value) return "0.00"
+        val numberFormat = NumberFormat.getNumberInstance()
+        numberFormat.maximumFractionDigits = fd
+        return numberFormat.format(value)
+    }
+
+}

+ 162 - 0
app/src/main/java/com/rc/core/util/MediaUtils.kt

@@ -0,0 +1,162 @@
+package com.rc.core.util
+
+import android.content.ContentValues
+import android.content.Context
+import android.net.Uri
+import android.os.Build
+import android.os.Environment
+import android.provider.MediaStore
+import java.io.File
+import java.util.*
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+object MediaUtils {
+
+    /**
+     * 创建一条图片地址uri,用于保存拍照后的照片
+     *
+     * @param context
+     * @param cameraFileName
+     * @param mimeType
+     * @return 图片的uri
+     */
+    fun createImageUri(
+        context: Context,
+        cameraFileName: String,
+        mimeType: String? = "image/jpeg"
+    ): Uri? {
+        // ContentValues是我们希望这条记录被创建时包含的数据信息
+        val values = ContentValues()
+
+        val suffix = cameraFileName.substring(cameraFileName.lastIndexOf("."))
+        val fileName = cameraFileName.replace(suffix, "")
+        values.put(MediaStore.Images.Media.DISPLAY_NAME, fileName)
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+            values.put(MediaStore.Images.Media.DATE_TAKEN, "${System.currentTimeMillis()}")
+        }
+        values.put(MediaStore.Images.Media.MIME_TYPE, mimeType)
+
+        // 判断是否有SD卡,优先使用SD卡存储,当没有SD卡时使用手机存储
+        return if (Environment.MEDIA_MOUNTED == Environment.getExternalStorageState()) {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+                values.put(MediaStore.Images.Media.RELATIVE_PATH, "DCIM/Camera")
+            }
+            context.applicationContext.contentResolver
+                .insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
+        } else {
+            context.applicationContext.contentResolver
+                .insert(MediaStore.Images.Media.INTERNAL_CONTENT_URI, values)
+        }
+    }
+
+    /**
+     * 创建一条视频地址uri,用于保存录制的视频
+     *
+     * @param context
+     * @param cameraFileName
+     * @param mimeType
+     * @return 视频的uri
+     */
+    fun createVideoUri(
+        context: Context,
+        cameraFileName: String,
+        mimeType: String? = "video/mp4"
+    ): Uri? {
+        // ContentValues是我们希望这条记录被创建时包含的数据信息
+        val values = ContentValues()
+
+        val suffix = cameraFileName.substring(cameraFileName.lastIndexOf("."))
+        val fileName = cameraFileName.replace(suffix, "")
+        values.put(MediaStore.Video.Media.DISPLAY_NAME, fileName)
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+            values.put(MediaStore.Video.Media.DATE_TAKEN, "${System.currentTimeMillis()}")
+        }
+        values.put(MediaStore.Video.Media.MIME_TYPE, mimeType)
+
+        // 判断是否有SD卡,优先使用SD卡存储,当没有SD卡时使用手机存储
+        return if (Environment.MEDIA_MOUNTED == Environment.getExternalStorageState()) {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+                values.put(MediaStore.Video.Media.RELATIVE_PATH, Environment.DIRECTORY_MOVIES)
+            }
+            context.applicationContext.contentResolver
+                .insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values)
+        } else {
+            context.applicationContext.contentResolver
+                .insert(MediaStore.Video.Media.INTERNAL_CONTENT_URI, values)
+        }
+    }
+
+    /**
+     * delete camera PATH
+     *
+     * @param context Context
+     * @param cameraPath Camera url
+     */
+    fun deleteCamera(context: Context, cameraPath: String?) {
+        try {
+            if (null != cameraPath && cameraPath.startsWith("content://")) {
+                context.contentResolver.delete(Uri.parse(cameraPath), null, null)
+            }
+        } catch (e: Exception) {
+            e.printStackTrace()
+        }
+    }
+
+    fun deleteFile(filepath: String?) {
+        if (!filepath.isNullOrEmpty()) {
+            try {
+                File(filepath).delete()
+            } catch (e: Exception) {
+                e.printStackTrace()
+            }
+        }
+    }
+
+    /**
+     * 创建文件
+     *
+     * @param context 上下文
+     * @param fileName 文件名
+     * @param outCameraDirectory 输出目录
+     * @return
+     */
+    fun createCameraFile(
+        context: Context,
+        fileName: String,
+        outCameraDirectory: String? = null
+    ): File {
+        val folderDir: File
+        if (outCameraDirectory.isNullOrEmpty()) {
+            // 外部没有自定义拍照存储路径使用默认
+            val rootDir: File
+            if (Environment.MEDIA_MOUNTED == Environment.getExternalStorageState()) {
+                rootDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)
+                folderDir = File(rootDir.absolutePath + File.separator + "Camera" + File.separator)
+            } else {
+                rootDir =
+                    context.applicationContext.getExternalFilesDir(Environment.DIRECTORY_PICTURES)!!
+                folderDir = File(rootDir.absolutePath + File.separator)
+            }
+            if (!rootDir.exists()) {
+                rootDir.mkdirs()
+            }
+        } else {
+            // 自定义存储路径
+            folderDir = File(outCameraDirectory)
+            if (!Objects.requireNonNull(folderDir.parentFile).exists()) {
+                folderDir.parentFile?.mkdirs()
+            }
+        }
+        if (!folderDir.exists()) {
+            folderDir.mkdirs()
+        }
+
+        return File(folderDir, fileName)
+    }
+}

+ 131 - 0
app/src/main/java/com/rc/core/util/ScreenAdapter.kt

@@ -0,0 +1,131 @@
+package com.rc.core.util
+
+import android.annotation.SuppressLint
+import android.app.Activity
+import android.app.Application
+import android.content.ComponentCallbacks
+import android.content.Context
+import android.content.res.Configuration
+import android.util.DisplayMetrics
+
+/**
+ * 屏幕适配
+ *
+ * @author ReiChin_
+ */
+object ScreenAdapter {
+
+    private const val DEFAULT_WIDTH = 1080 // 默认宽度
+
+    private var appDensity = 0f
+
+    /**
+     * 字体的缩放因子,正常情况下和density相等,但是调节系统字体大小后会改变这个值
+     */
+    private var appScaledDensity = 0f
+
+    /**
+     * 状态栏高度
+     */
+    private var barHeight: Int = 0
+    private var appDisplayMetrics: DisplayMetrics? = null
+    private var densityScale = 1.0f
+
+    /**
+     * application 层调用,存储默认屏幕密度
+     */
+    fun initAppDensity(application: Application) {
+        // 获取application的DisplayMetrics
+        appDisplayMetrics = application.resources.displayMetrics
+        // 获取状态栏高度
+        barHeight = getStatusBarHeight(application)
+        if (appDensity == 0f) {
+            // 初始化的时候赋值
+            appDensity = appDisplayMetrics!!.density
+            appScaledDensity = appDisplayMetrics!!.scaledDensity
+            // 添加字体变化的监听
+            application.registerComponentCallbacks(object : ComponentCallbacks {
+
+                override fun onConfigurationChanged(newConfig: Configuration) {
+                    // 字体改变后,将appScaledDensity重新赋值
+                    if (newConfig.fontScale > 0) {
+                        appScaledDensity = application.resources.displayMetrics.scaledDensity
+                    }
+                }
+
+                override fun onLowMemory() {
+                }
+            })
+        }
+    }
+
+    /**
+     * 设置自定义的屏幕密度
+     */
+    fun setCustomDensity(activity: Activity) {
+        val targetDensity = appDisplayMetrics!!.widthPixels * 1.0f / DEFAULT_WIDTH
+
+        // 最后在这里将修改过后的值赋给系统参数,只修改Activity的density值
+        val activityDisplayMetrics = activity.resources.displayMetrics.apply {
+            density = targetDensity
+            scaledDensity = targetDensity * (appScaledDensity / appDensity)
+            densityDpi = (160 * targetDensity).toInt()
+        }
+
+        densityScale = appDensity / targetDensity
+        setBitmapDefaultDensity(activityDisplayMetrics.densityDpi)
+    }
+
+    /**
+     * 重置屏幕密度
+     */
+    fun resetAppDensity(activity: Activity) {
+        val activityDisplayMetrics = activity.resources.displayMetrics
+        activityDisplayMetrics.density = appDensity
+        activityDisplayMetrics.scaledDensity = appScaledDensity
+        activityDisplayMetrics.densityDpi = (appDensity * 160).toInt()
+
+        densityScale = 1.0f
+        setBitmapDefaultDensity(activityDisplayMetrics.densityDpi)
+    }
+
+    /**
+     * 获取状态栏高度
+     *
+     * @param context context
+     * @return 状态栏高度
+     */
+    private fun getStatusBarHeight(context: Context): Int {
+        val resourceId = context.resources.getIdentifier("status_bar_height", "dimen", "android")
+        return if (resourceId > 0) {
+            context.resources.getDimensionPixelSize(resourceId)
+        } else 0
+    }
+
+    /**
+     * 设置 Bitmap 的默认屏幕密度
+     * 由于 Bitmap 的屏幕密度是读取配置的,导致修改未被启用
+     * 所有,放射方式强行修改
+     * @param defaultDensity 屏幕密度
+     */
+
+    @SuppressLint("SoonBlockedPrivateApi")
+    private fun setBitmapDefaultDensity(defaultDensity: Int) {
+        try {
+            // 获取单个变量的值
+            val clazz = Class.forName("android.graphics.Bitmap")
+            val field = clazz.getDeclaredField("sDefaultDensity")
+            field.isAccessible = true
+            field.set(null, defaultDensity)
+            field.isAccessible = false
+        } catch (e: Exception) {
+            e.printStackTrace()
+        }
+    }
+
+    /**
+     * 屏幕密度缩放系数
+     */
+    fun getDensityScale() = densityScale
+
+}

+ 50 - 0
app/src/main/java/com/rc/core/util/VideoFullScreenWebChromeClient.kt

@@ -0,0 +1,50 @@
+package com.rc.core.util
+
+import android.view.View
+import android.widget.FrameLayout
+import com.tencent.smtt.export.external.interfaces.IX5WebChromeClient
+import com.tencent.smtt.sdk.WebChromeClient
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+class VideoFullScreenWebChromeClient(
+    private val fullScreen: FrameLayout
+) : WebChromeClient() {
+
+    // 用于保存系统提供的全屏视频所在的view
+    // 可将view添加到我们自己的视频播放锚点布局中
+    private var mCustomView: View? = null
+    private var mCustomViewCallback: IX5WebChromeClient.CustomViewCallback? = null
+
+    override fun onShowCustomView(view: View?, callback: IX5WebChromeClient.CustomViewCallback?) {
+        super.onShowCustomView(view, callback)
+        // view为全屏时,系统提供的视频展示窗口
+        // 如果view已经存在,则隐藏
+        if (mCustomView != null) {
+            callback?.onCustomViewHidden()
+            return
+        }
+        mCustomView = view
+        mCustomView?.visibility = View.VISIBLE
+        mCustomViewCallback = callback
+        fullScreen.addView(mCustomView)
+        // 仅显示视频锚点布局
+        fullScreen.visibility = View.VISIBLE
+    }
+
+    override fun onHideCustomView() {
+        super.onHideCustomView()
+        if (mCustomView == null) {
+            return
+        }
+        mCustomView?.visibility = View.GONE
+        fullScreen.removeView(mCustomView)
+        mCustomView = null
+
+        mCustomViewCallback?.onCustomViewHidden()
+    }
+
+}

+ 27 - 0
app/src/main/java/com/rc/core/util/Weak.kt

@@ -0,0 +1,27 @@
+package com.rc.core.util
+
+import java.lang.ref.WeakReference
+import kotlin.reflect.KProperty
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+class Weak<T : Any>(initializer: () -> T?) {
+
+    var weakReference = WeakReference<T?>(initializer())
+
+    constructor() : this({
+        null
+    })
+
+    operator fun getValue(thisRef: Any?, property: KProperty<*>): T? {
+        return weakReference.get()
+    }
+
+    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) {
+        weakReference = WeakReference(value)
+    }
+
+}

+ 155 - 0
app/src/main/java/com/rc/core/util/WebViewHelper.kt

@@ -0,0 +1,155 @@
+package com.rc.core.util
+
+import android.animation.ValueAnimator
+import android.annotation.SuppressLint
+import android.graphics.Bitmap
+import android.net.Uri
+import android.os.Build
+import android.view.View
+import android.view.animation.AccelerateDecelerateInterpolator
+import android.widget.ProgressBar
+import com.tencent.smtt.export.external.interfaces.IX5WebChromeClient
+import com.tencent.smtt.export.external.interfaces.SslError
+import com.tencent.smtt.export.external.interfaces.SslErrorHandler
+import com.tencent.smtt.export.external.interfaces.WebResourceRequest
+import com.tencent.smtt.sdk.*
+
+/**
+ * WebView + ProgressBar帮助类
+ *
+ * @author ReiChin_
+ */
+class WebViewHelper(private val webView: WebView,
+                    private val progressBar: ProgressBar) {
+
+    private val mProgressHelper by lazy { ProgressHelper(progressBar) }
+
+    @SuppressLint("SetJavaScriptEnabled")
+    fun initWebView(webViewClientProxy: WebViewClient? = null,
+                    webChromeClientProxy: WebChromeClient? = null) {
+        webView.settings.apply {
+            // 为拍照而加
+            allowContentAccess = true
+            allowFileAccess = true
+            setAllowFileAccessFromFileURLs(true)
+            setAllowUniversalAccessFromFileURLs(true)
+            loadWithOverviewMode = true
+
+            defaultTextEncodingName = "utf-8"
+
+            setSupportZoom(true)
+
+            // 设置是否支持执行JS,如果设置为true会存在XSS攻击风险
+            javaScriptEnabled = true
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+                safeBrowsingEnabled = false
+            }
+
+            useWideViewPort = true
+            // 安全考虑,防止密码泄漏,尤其是root过的手机
+            savePassword = false
+            userAgentString = "${userAgentString}; MYAPP"
+            layoutAlgorithm = WebSettings.LayoutAlgorithm.NORMAL
+            // 最重要的方法,一定要设置,这就是出不来的主要原因
+            domStorageEnabled = true
+
+//            databaseEnabled = true
+//            setGeolocationEnabled(true)
+//            javaScriptCanOpenWindowsAutomatically = true
+            cacheMode = WebSettings.LOAD_NO_CACHE
+        }
+
+        webView.apply {
+            scrollBarStyle = View.SCROLLBARS_INSIDE_OVERLAY
+            // 水平不显示
+            isHorizontalScrollBarEnabled = false
+            // 垂直不显示
+            isVerticalScrollBarEnabled = false
+            webViewClient = MyWebViewClient(progressBar, webView, webViewClientProxy)
+            webChromeClient = MyWebChromeClient(mProgressHelper, webChromeClientProxy)
+        }
+    }
+
+    private class ProgressHelper(val progressBar: ProgressBar) {
+
+        private val animator: ValueAnimator = ValueAnimator()
+
+        init {
+            animator.duration = 500
+            animator.interpolator = AccelerateDecelerateInterpolator()
+            animator.addUpdateListener { animation ->
+                val value = animation.animatedValue as Int
+                progressBar.progress = value
+                if (value >= 99)
+                    progressBar.visibility = View.GONE
+                else
+                    progressBar.visibility = View.VISIBLE
+            }
+        }
+
+        fun setProgress(progress: Int) {
+            if (progressBar.progress > progress) {
+                animator.cancel()
+                progressBar.progress = 0
+            }
+            if (!animator.isRunning) {
+                animator.setIntValues(progressBar.progress, progress)
+                animator.start()
+            } else {
+                val value = animator.animatedValue as Int
+                animator.cancel()
+                animator.setIntValues(value, progress)
+                animator.start()
+            }
+        }
+    }
+
+    private class MyWebViewClient(val progressBar: ProgressBar,
+                                  val webView: WebView,
+                                  val proxy: WebViewClient? = null) : WebViewClient() {
+
+        override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
+            super.onPageStarted(view, url, favicon)
+            progressBar.progress = 0
+        }
+
+        override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
+            return proxy?.shouldOverrideUrlLoading(view, request?.url.toString())
+                    ?: super.shouldOverrideUrlLoading(view, request)
+        }
+
+        override fun onReceivedSslError(view: WebView?, handler: SslErrorHandler?, error: SslError?) {
+            handler?.proceed()
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+                webView.settings.mixedContentMode = 0/*android.webkit.WebSettings.MIXED_CONTENT_ALWAYS_ALLOW*/
+            }
+        }
+
+    }
+
+    private class MyWebChromeClient(val progressHelper: ProgressHelper,
+                                    val proxy: WebChromeClient? = null) : WebChromeClient() {
+
+        override fun onProgressChanged(view: WebView?, newProgress: Int) {
+            progressHelper.setProgress(newProgress)
+        }
+
+        override fun onShowCustomView(view: View?, callback: IX5WebChromeClient.CustomViewCallback?) {
+            super.onShowCustomView(view, callback)
+            proxy?.onShowCustomView(view, callback)
+        }
+
+        override fun onHideCustomView() {
+            super.onHideCustomView()
+            proxy?.onHideCustomView()
+        }
+
+        override fun onShowFileChooser(webView: WebView?,
+                                       filePathCallback: ValueCallback<Array<Uri>>?,
+                                       fileChooserParams: FileChooserParams?): Boolean {
+            return proxy?.onShowFileChooser(webView, filePathCallback, fileChooserParams)
+                    ?: super.onShowFileChooser(webView, filePathCallback, fileChooserParams)
+        }
+    }
+
+}

+ 35 - 0
app/src/main/java/com/rc/core/util/net/NetChangeListener.kt

@@ -0,0 +1,35 @@
+package com.rc.core.util.net
+
+/**
+ * Function : 网络变化监听事件
+ * Date:  2020-10-15 10:29
+ * Author: ReiChin_
+ * Version:
+ */
+interface NetChangeListener {
+
+    /**
+     * wifi变为4G
+     */
+    fun onWifiTo4G()
+
+    /**
+     * 4G变为wifi
+     */
+    fun on4GToWifi()
+
+    /**
+     * 网络断开
+     */
+    fun onNetDisconnected()
+
+//    //实现接口内回调方法,调用对应函数变量的invoke(param)方法实现回调
+//    override fun nextFun(data: T?) {
+//        //函数类型的值可以通过其 invoke(……) 操作符调用:f.invoke(x) 或者直接 f(x)。
+//        this.next.invoke(data)
+//        //this.next(data)
+//    }
+//    override fun errorFun(code: Int, message: String) {
+//        this.error.invoke(code, message)
+//    }
+}

+ 20 - 0
app/src/main/java/com/rc/core/util/net/NetConnectedListener.kt

@@ -0,0 +1,20 @@
+package com.rc.core.util.net
+
+/**
+ * Function : 判断是否有网络的监听
+ * Date:  2020-10-15 10:30
+ * Author: ReiChin_
+ * Version:
+ */
+interface NetConnectedListener {
+
+    /**
+     * 网络已连接
+     */
+    fun onReNetConnected(isReconnect: Boolean)
+
+    /**
+     * 网络未连接
+     */
+    fun onNetUnConnected()
+}

+ 145 - 0
app/src/main/java/com/rc/core/util/net/NetWatchdog.kt

@@ -0,0 +1,145 @@
+package com.rc.core.util.net
+
+import android.annotation.SuppressLint
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.net.ConnectivityManager
+import android.net.NetworkInfo
+import java.lang.Exception
+
+/**
+ * Function :
+ * Date:  2020-10-15 10:02
+ * Author: ReiChin_
+ * Version:
+ */
+class NetWatchdog(context: Context) {
+
+    private val mAppContext = context.applicationContext
+
+    private var isReconnect = false
+
+    // 广播过滤器,监听网络变化
+    private val mNetIntentFilter = IntentFilter()
+
+    init {
+        mNetIntentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION)
+    }
+
+    private val mReceiver = object : BroadcastReceiver() {
+        override fun onReceive(context: Context?, intent: Intent?) {
+            // 获取手机的连接服务管理器,这里是连接管理器类
+            val cm = context?.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
+
+            val wifiNetworkInfo: NetworkInfo? = cm.getNetworkInfo(ConnectivityManager.TYPE_WIFI)
+            val mobileNetworkInfo: NetworkInfo? = cm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE)
+            val activeNetworkInfo: NetworkInfo? = cm.getActiveNetworkInfo()
+
+            val wifiState = wifiNetworkInfo?.state ?: NetworkInfo.State.UNKNOWN
+            val mobileState = mobileNetworkInfo?.state ?: NetworkInfo.State.UNKNOWN
+
+            if (activeNetworkInfo != null && activeNetworkInfo.isConnectedOrConnecting) {
+                mNetConnectedListener?.run {
+                    isReconnect = false
+                    onReNetConnected(isReconnect)
+                }
+            } else if (activeNetworkInfo == null){
+                mNetConnectedListener?.run {
+                    isReconnect = true
+                    onNetUnConnected()
+                }
+            }
+
+            if (NetworkInfo.State.CONNECTED != wifiState && NetworkInfo.State.CONNECTED == mobileState) {
+                mNetChangeListener?.onWifiTo4G()
+            } else if (NetworkInfo.State.CONNECTED == wifiState && NetworkInfo.State.CONNECTED != mobileState) {
+                mNetChangeListener?.on4GToWifi()
+            } else if (NetworkInfo.State.CONNECTED != wifiState && NetworkInfo.State.CONNECTED != mobileState) {
+                mNetChangeListener?.onNetDisconnected()
+            }
+        }
+    }
+
+    private var mNetChangeListener: NetChangeListener? = null
+
+    fun setNetChangeListener(listener: NetChangeListener) {
+        mNetChangeListener = listener
+    }
+
+    private var mNetConnectedListener: NetConnectedListener? = null
+
+    fun setNetConnectedListener(listener: NetConnectedListener) {
+        mNetConnectedListener = listener
+    }
+
+    /**
+     * 开始监听
+     */
+    fun startWatch() {
+        try {
+            mAppContext.registerReceiver(mReceiver, mNetIntentFilter)
+        } catch (e: Exception) {
+        }
+    }
+
+    /**
+     * 结束监听
+     */
+    fun stopWatch() {
+        try {
+            mAppContext.unregisterReceiver(mReceiver)
+        } catch (e: Exception) {
+        }
+    }
+
+    companion object {
+        /**
+         * 静态方法获取是否有网络连接
+         *
+         * @param context 上下文
+         * @return 是否连接
+         */
+        fun hasNet(context: Context): Boolean {
+            // 获取手机的连接服务管理器,这里是连接管理器类
+            val cm = context.applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
+
+            val wifiNetworkInfo: NetworkInfo? = cm.getNetworkInfo(ConnectivityManager.TYPE_WIFI)
+            val mobileNetworkInfo: NetworkInfo? = cm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE)
+            val activeNetworkInfo: NetworkInfo? = cm.activeNetworkInfo
+
+            val wifiState = wifiNetworkInfo?.state ?: NetworkInfo.State.UNKNOWN
+            val mobileState = mobileNetworkInfo?.state ?: NetworkInfo.State.UNKNOWN
+
+            if (NetworkInfo.State.CONNECTED != wifiState && NetworkInfo.State.CONNECTED != mobileState) {
+                return false
+            }
+            if (activeNetworkInfo == null || !activeNetworkInfo.isConnectedOrConnecting) {
+                return false
+            }
+
+            return true
+        }
+
+        /**
+         * 静态判断是不是4G网络
+         *
+         * @param context 上下文
+         * @return 是否是4G
+         */
+        @SuppressLint("MissingPermission")
+        fun is4GConnected(context: Context): Boolean {
+            // 获取手机的连接服务管理器,这里是连接管理器类
+            val cm = context.applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
+
+            val mobileNetworkInfo: NetworkInfo ? = cm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE)
+
+            val mobileState = mobileNetworkInfo?.state ?: NetworkInfo.State.UNKNOWN
+
+            return NetworkInfo.State.CONNECTED == mobileState
+        }
+    }
+
+
+}

+ 120 - 0
app/src/main/java/com/rc/httpcore/HttpClient.kt

@@ -0,0 +1,120 @@
+package com.rc.httpcore
+
+import android.content.Context
+import com.blankj.utilcode.util.AppUtils
+import com.blankj.utilcode.util.LogUtils
+import com.rc.httpcore.client.factory.ClientFactory
+import com.rc.httpcore.client.factory.RetrofitFactory
+import com.rc.httpcore.config.OkHttpDNS
+import com.rc.httpcore.converter.NullOnEmptyConverterFactory
+import com.rc.httpcore.interceptor.ParameterValidationInterceptor
+import com.rc.httpcore.interceptor.ResponseInterceptor
+import com.rc.httpcore.interceptor.TokenHeaderInterceptor
+import okhttp3.OkHttpClient
+import retrofit2.Retrofit
+import retrofit2.adapter.rxjava3.RxJava3CallAdapterFactory
+import retrofit2.converter.gson.GsonConverterFactory
+import java.util.concurrent.TimeUnit
+
+
+object HttpClient {
+
+    //    const val TIMEOUT_DEFAULT = 3 * 60L
+    const val TIMEOUT_DEFAULT = 10L
+
+    private var mAppContext: Context? = null
+    private var okHttpClient: OkHttpClient? = null
+
+    @Volatile
+    var token: String? = null
+
+    var vName: String = "1.0.0"
+
+    fun init(appContext: Context) {
+        this.mAppContext = appContext
+    }
+
+    fun getAppContext() = mAppContext
+
+    fun createClientFactory(): ClientFactory {
+        return when (HttpConfig.HTTP_STRATEGY_Retrofit) {
+            HttpConfig.HTTP_STRATEGY -> RetrofitFactory()
+            else -> RetrofitFactory()
+        }
+    }
+
+    fun <T> createRetrofitApi(
+        apiClass: Class<T>,
+        baseUrl: String = HttpConfig.API_BASE_URL,
+    ): T {
+        return buildRetrofit(baseUrl).create(apiClass)
+    }
+
+    private fun buildRetrofit(
+        baseUrl: String = HttpConfig.API_BASE_URL,
+        okHttpClient: OkHttpClient = buildHttpClient()
+    ): Retrofit {
+        return Retrofit.Builder()
+            .client(okHttpClient)
+            .baseUrl(baseUrl)
+            .addCallAdapterFactory(RxJava3CallAdapterFactory.create())
+            .addConverterFactory(NullOnEmptyConverterFactory())
+            .addConverterFactory(GsonConverterFactory.create())
+            .build()
+    }
+
+    public fun buildHttpClient(): OkHttpClient {
+        if (null != okHttpClient) {
+            return okHttpClient!!
+        }
+        val logPath = "/sdcard/logs/"
+        val config = LogUtils.getConfig()
+        // log开关控制
+        config.setLogSwitch(true)
+        // log控制台开关
+        config.setConsoleSwitch(true)
+        // logTag
+        config.setGlobalTag("Jayce")
+        // log头部信息开关
+        config.setLogHeadSwitch(true)
+        // log文件开关
+        config.setLog2FileSwitch(true)
+        config.setDir(logPath)
+        // log文件前缀
+        config.setFilePrefix(AppUtils.getAppName())
+        // log边框开关
+        config.setBorderSwitch(true)
+        // log文件保存天数
+        config.setSaveDays(7)
+//        val httpLoggingInterceptor = HttpLoggingInterceptor(HttpLoggingInterceptorLog())//创建拦截对象
+//        httpLoggingInterceptor.level = HttpLoggingInterceptor.Level.BODY//这一句一定要记得写,否则没有数据输出
+
+        okHttpClient = OkHttpClient.Builder()
+            .readTimeout(TIMEOUT_DEFAULT, TimeUnit.SECONDS) // 设置读取超时时间
+            .connectTimeout(TIMEOUT_DEFAULT, TimeUnit.SECONDS) // 设置请求超时时间
+            .writeTimeout(TIMEOUT_DEFAULT, TimeUnit.SECONDS) // 设置写入超时时间
+            .addNetworkInterceptor(TokenHeaderInterceptor())
+//            .addInterceptor(RequestInterceptor())
+            .addInterceptor(ParameterValidationInterceptor())
+            .addInterceptor(ResponseInterceptor())
+//            .addInterceptor(HttpLoggingInterceptorLog())//添加请求日志
+//            .addInterceptor(NullToEmptyInterceptor())//添加请求日志
+            .retryOnConnectionFailure(true) // 设置出现错误进行重新连接
+            .dns(OkHttpDNS())
+            .build()
+
+        return okHttpClient!!
+//        if (!BuildConfig.DEBUG) {//debug模式下打印
+//        }else{
+//            return OkHttpClient.Builder()
+//                .readTimeout(TIMEOUT_DEFAULT, TimeUnit.SECONDS) // 设置读取超时时间
+//                .connectTimeout(TIMEOUT_DEFAULT, TimeUnit.SECONDS) // 设置请求超时时间
+//                .writeTimeout(TIMEOUT_DEFAULT, TimeUnit.SECONDS) // 设置写入超时时间
+//                .addNetworkInterceptor(TokenHeaderInterceptor())
+//                .retryOnConnectionFailure(true) // 设置出现错误进行重新连接
+//                .build()
+//        }
+    }
+
+
+}

+ 39 - 0
app/src/main/java/com/rc/httpcore/HttpConfig.kt

@@ -0,0 +1,39 @@
+package com.rc.httpcore
+
+class HttpConfig {
+
+    companion object {
+        //        var API_BASE_URL = "http://lab.zjznai.com/nwsuaf/api/"  //公网域名
+        var API_BASE_URL = "http://172.16.0.65/api/"
+        var API_BASE_IMG_URL = "http://172.16.0.65/api/"  //图片资源
+
+//        var API_BASE_QC_URL = "http://lab.zjznai.com/labTest"  //二维码地址  用于打印出的二维码路径跳转到小程序
+        var API_BASE_QC_URL = "https://labcontrol.nwafu.edu.cn/api/"  //二维码地址  用于打印出的二维码路径跳转到小程序
+//        http://lab.zjznai.com/labAppTest?code=1760903781269671937&type=1   MSDS 路径生成
+        //http://192.168.1.43/api?code=1760903781269671937&type=1   MSDS 路径生成
+
+        var BASE_PATH_FACE = BasePathV1.FACE
+        var BASE_PATH_BASE = BasePathV1.BASE
+
+        const val HTTP_STRATEGY_Retrofit = 1
+
+        var HTTP_STRATEGY = HTTP_STRATEGY_Retrofit
+    }
+
+
+    enum class ApiVersion(val code: Int, val desc: String) {
+        V1(0, "V1"),
+        V2(1, "V2")
+    }
+
+    object BasePathV1 {
+        const val FACE = "face"
+        const val BASE = ""
+    }
+
+    object BasePathV2 {
+        const val ALGORITHM = "algorithm"
+        const val BASE = "base/"
+    }
+
+}

+ 91 - 0
app/src/main/java/com/rc/httpcore/OkHttpUtils.java

@@ -0,0 +1,91 @@
+package com.rc.httpcore;
+
+import com.rc.httpcore.vo.request.FaceCompare1Req;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+import okhttp3.Call;
+import okhttp3.Callback;
+import okhttp3.MediaType;
+import okhttp3.MultipartBody;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+import okhttp3.Response;
+
+public class OkHttpUtils {
+
+    private static final OkHttpClient client = HttpClient.INSTANCE.buildHttpClient();
+    private static final MediaType JSON = MediaType.get("application/json; charset=utf-8");
+
+    private OkHttpUtils() {
+    }
+
+    // 同步 GET 请求
+    public static Response getSync(String url) throws IOException {
+        Request request = new Request.Builder()
+                .url(url)
+                .build();
+        Call call = client.newCall(request);
+        return call.execute();
+    }
+
+    // 异步 GET 请求
+    public static Call get(String url, Callback callback) throws IOException {
+        Request request = new Request.Builder()
+                .url(url)
+                .build();
+        Call call = client.newCall(request);
+        call.enqueue(callback);
+        return call;
+    }
+
+    // 同步 POST 请求
+    public static Response postSync(String url, String json) throws IOException {
+        RequestBody body = RequestBody.create(JSON, json);
+        Request request = new Request.Builder()
+                .url(url)
+                .post(body)
+                .build();
+        Call call = client.newCall(request);
+        return call.execute();
+    }
+
+    // 异步 POST 请求
+    public static Call post(String url, String json, Callback callback) throws IOException {
+        RequestBody body = RequestBody.create(JSON, json);
+        Request request = new Request.Builder()
+                .url(url)
+                .post(body)
+                .build();
+        Call call = client.newCall(request);
+        call.enqueue(callback);
+        return call;
+    }
+
+    public static Response postFileAndJson(String url, File file, String subjectId, FaceCompare1Req faceCompare1Req) throws IOException {
+        RequestBody fileRequestBody = RequestBody.create(MultipartBody.FORM, file);
+        MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", file.getName(), fileRequestBody);
+        List<String> doorIds = faceCompare1Req.getDoorIds();
+        StringBuilder doorIdsSb = new StringBuilder();
+        for (int i = 0; i < doorIds.size(); i++) {
+            if (i > 0) doorIdsSb.append(",");
+            doorIdsSb.append(doorIds.get(i));
+        }
+        String doorIdsSbs = doorIdsSb.toString();
+        RequestBody requestBody = new MultipartBody.Builder()
+                .setType(MultipartBody.FORM)
+                .addPart(filePart)
+                .addFormDataPart("doorIds", doorIdsSbs)
+                .addFormDataPart("subId", subjectId)
+                .build();
+
+        Request request = new Request.Builder()
+                .url(url)
+                .post(requestBody)
+                .build();
+        return client.newCall(request).execute();
+    }
+}

+ 41 - 0
app/src/main/java/com/rc/httpcore/apk/AdminInfo.kt

@@ -0,0 +1,41 @@
+package com.rc.httpcore.apk
+
+data class AdminInfo(
+    val account: Any,
+    val accountType: Any,
+    val avatar: Any,
+    val birthday: Any,
+    val cardNum: Any,
+    val cardNumSimple: Any,
+    val category: Any,
+    val createTime: Any,
+    val deptId: Any,
+    val deptName: Any,
+    val education: Any,
+    val email: Any,
+    val grade: Any,
+    val gradeName: Any,
+    val identity: Any,
+    val isCheck: Any,
+    val major: Any,
+    val majorName: Any,
+    val mobile: String,
+    val nickName: Any,
+    val position: Any,
+    val positionName: Any,
+    val postCode: Any,
+    val roleIds: Any,
+    val sex: Any,
+    val signature: Any,
+    val source: Any,
+    val state: Any,
+    val topicName: Any,
+    val tutorUserId: Any,
+    val tutorUserMobile: Any,
+    val tutorUserName: Any,
+    val updateTime: Any,
+    val userId: String,
+    val userName: String,
+    val userState: Any,
+    val userType: Any
+)

+ 9 - 0
app/src/main/java/com/rc/httpcore/apk/AppInfo.kt

@@ -0,0 +1,9 @@
+package com.rc.httpcore.apk
+
+import com.rc.httpcore.vo.response.ApkInfoResp
+
+data class AppInfo(
+    val labInfo: LabInfo,
+    val needUpgrade: Boolean,
+    val appInfo: ApkInfoResp?  //版本升级得内容
+)

+ 12 - 0
app/src/main/java/com/rc/httpcore/apk/ChemicalExpire.kt

@@ -0,0 +1,12 @@
+package com.rc.httpcore.apk
+
+data class ChemicalExpire(
+    val belongName: String,
+    val chemicalName: String,
+    val expireTime: String,
+    val normsNum: String,
+    val position: String,
+    val tagCode: String,
+    val stockDetailId: Long,
+    val surplus: String
+)

+ 9 - 0
app/src/main/java/com/rc/httpcore/apk/LabInfo.kt

@@ -0,0 +1,9 @@
+package com.rc.httpcore.apk
+
+data class LabInfo(
+    val floorId: String,
+    val floorName: String,
+    val room: String,
+    val subjectId: String,
+    val subjectName: String
+)

+ 43 - 0
app/src/main/java/com/rc/httpcore/apk/ProcessedBean.kt

@@ -0,0 +1,43 @@
+package com.rc.httpcore.apk
+
+import com.rc.httpcore.bean.ChemicalTimeoutListBean
+import com.rc.httpcore.bean.ChemicalWarnInfoBean
+
+data class ProcessedBean(
+    val adminInfoList: List<AdminInfo>,
+    val buildName: String,
+    val chemicalExpireList: List<ChemicalExpire>, //化学品信息显示
+    val chemicalOverdueStockList: List<ChemicalWarnInfoBean>?, //超期未入库
+    val chemicalTimeoutList: List<ChemicalTimeoutListBean>?, //超期未入库
+    val chemicalWarnType: Int,  //1化学品违规带离。2超时未归还。3化学品过期。4化学品即将过期 5 超期未入库 6业务超时
+    val deptName: String,
+    val floorName: String,
+    val handleNotes: String?,
+    val handlePerson: String?,
+    val handlePersonPhone: Any,
+    val handleTime: String?,
+    val handleType: Int,
+    val isHandlePeople: Boolean,
+    val noticeId: String,
+    val noticeStatus: Int,
+    val roomNum: String,
+    val safeInfoList: List<SafeInfo>,
+    val safeInfoStr: Any,
+    val subName: String,
+    val warnContent: String,
+    val warnTime: String,
+    val belongName: String?,
+    val chemicalName: String?,
+    val rfidCode: String?,
+    val position: String?,
+    val collectUserName: String?,
+    val surplus: String?,
+    val normsNum: String?,
+    val collectTime: String?,
+    val collectTimeAging: Int,  //小时 需要自己添加
+    val warnType: Int,
+    val alarmType: String?,
+    val operationPerson: String?,
+    val snapImg: List<String>?,
+    val warningNoticeLogVoList: List<WarningNoticeLogVo>  //通知记录
+)

+ 41 - 0
app/src/main/java/com/rc/httpcore/apk/SafeInfo.kt

@@ -0,0 +1,41 @@
+package com.rc.httpcore.apk
+
+data class SafeInfo(
+    val account: Any,
+    val accountType: Any,
+    val avatar: Any,
+    val birthday: Any,
+    val cardNum: Any,
+    val cardNumSimple: Any,
+    val category: Any,
+    val createTime: Any,
+    val deptId: Any,
+    val deptName: Any,
+    val education: Any,
+    val email: Any,
+    val grade: Any,
+    val gradeName: Any,
+    val identity: Any,
+    val isCheck: Any,
+    val major: Any,
+    val majorName: Any,
+    val mobile: String,
+    val nickName: Any,
+    val position: Any,
+    val positionName: Any,
+    val postCode: Any,
+    val roleIds: Any,
+    val sex: Any,
+    val signature: Any,
+    val source: Any,
+    val state: Any,
+    val topicName: Any,
+    val tutorUserId: Any,
+    val tutorUserMobile: Any,
+    val tutorUserName: Any,
+    val updateTime: Any,
+    val userId: String,
+    val userName: String,
+    val userState: Any,
+    val userType: Any
+)

+ 8 - 0
app/src/main/java/com/rc/httpcore/apk/WarningNoticeLogVo.kt

@@ -0,0 +1,8 @@
+package com.rc.httpcore.apk
+
+data class WarningNoticeLogVo(
+    val noticePreson: String,
+    val noticeTime: String,
+    val noticeType: String,
+    val warningLevel: String
+)

+ 22 - 0
app/src/main/java/com/rc/httpcore/bean/AddChemical.java

@@ -0,0 +1,22 @@
+package com.rc.httpcore.bean;
+
+import java.util.List;
+
+/**
+ * 后台数据格式要求才写成这样
+ */
+public class AddChemical {
+    private List<HxpChemicalVo> stockModelList;
+
+    public AddChemical(List<HxpChemicalVo> stockModelList) {
+        this.stockModelList = stockModelList;
+    }
+
+    public List<HxpChemicalVo> getStockModelList() {
+        return stockModelList;
+    }
+
+    public void setStockModelList(List<HxpChemicalVo> stockModelList) {
+        this.stockModelList = stockModelList;
+    }
+}

+ 3 - 0
app/src/main/java/com/rc/httpcore/bean/AdminVoListBean.kt

@@ -0,0 +1,3 @@
+package com.rc.httpcore.bean
+
+data class AdminVoListBean(var userName:String,var doorId:String,var userId:String)

+ 12 - 0
app/src/main/java/com/rc/httpcore/bean/BelongingPersonBean.kt

@@ -0,0 +1,12 @@
+package com.rc.httpcore.bean
+
+import java.io.Serializable
+
+//归属人信息
+data class BelongingPersonBean(
+    val userId: String,
+    val userName: String,
+    val account: String,
+    val mobile: String,
+    val deptName: String
+) : Serializable

+ 17 - 0
app/src/main/java/com/rc/httpcore/bean/CabinetDoorVo.kt

@@ -0,0 +1,17 @@
+package com.rc.httpcore.bean
+
+data class CabinetDoorVo(
+    val cabinetAdminVoList: List<AdminVoListBean>? = null,
+    val cabinetId: String,
+    val cabinetLockVoList: List<LockVoListBean>? = null,  //柜锁有几个
+    val doorId: String,
+    val doorLayers: Int,  //当前一共几层
+    val doorName: String,
+    val doorUniqueId: String,
+    val stockNum: Int,
+    val unlockingMethod: Int,
+    val useNum: Any,
+    val isControl: Boolean = false,  //非管控  否则就是管控
+    var verify: Boolean = true, //true 需要进行双人认证
+    var isType: Boolean = false  //不选中 true选中
+)

+ 6 - 0
app/src/main/java/com/rc/httpcore/bean/CertitudeBean.java

@@ -0,0 +1,6 @@
+package com.rc.httpcore.bean;
+
+public class CertitudeBean {
+    String casNum;
+
+}

+ 26 - 0
app/src/main/java/com/rc/httpcore/bean/ChemicalABean.kt

@@ -0,0 +1,26 @@
+package com.rc.httpcore.bean
+
+import java.math.BigDecimal
+
+//新增-点击确定 需要的参数
+class ChemicalABean(
+    val belongId: String? = null,   //归属人id
+    val belongName: String? = null, //归属人名称
+    val casNum: String? = null,
+    val chemicalCategory: String? = null,
+    val chemicalDensity: Double, // 密度
+    val chemicalLevel: Int,   //管控 1     非管控  2
+    val chemicalName: String? = null,
+    val chemicalNumber: Int, //数量
+    val chemicalPurity: String? = null,
+    val factory: String? = null,
+    val isNew: Boolean,
+    val packNum: BigDecimal = BigDecimal("0"),
+    val packUnit: String? = null, //包装单位
+    val specNum: BigDecimal,  //规格
+    val specUnit: String? = null, //规格单位
+    var chemicalId: String? = null,
+    var belongType: Int = 2,  //1课题组 2人员
+    var qrCodePrint: Boolean = true,  //是否打印二维码
+    var status: Int? = 2,   //默认非双人认证
+)

+ 21 - 0
app/src/main/java/com/rc/httpcore/bean/ChemicalConfsBean.kt

@@ -0,0 +1,21 @@
+package com.rc.httpcore.bean
+
+import java.math.BigDecimal
+
+//化学品基础参数配置  是否打印二维码
+data class ChemicalConfsBean(
+    val controlType: Boolean,
+    val createBy: String,
+    val createTime: String,
+    val fluidMax: BigDecimal,
+    val id: String,
+    val inputWeight: Boolean,
+    val metage: Boolean,
+    val outRange: Boolean,
+    val qrCodePrint: Boolean,  //是否打印
+    val remark: String?,
+    val solidMax: BigDecimal,
+    val updateBy: String,
+    val updateTime: String,
+    val verify: Boolean
+)

+ 13 - 0
app/src/main/java/com/rc/httpcore/bean/ChemicalDataBean.kt

@@ -0,0 +1,13 @@
+package com.rc.httpcore.bean
+
+data class ChemicalDataBean (
+    val countId: Any,
+    val current: Int,
+    val maxLimit: Any,
+    val optimizeCountSql: Boolean,
+    val pages: Int,
+    val records: List<ChemicalSearchBean>,
+    val searchCount: Boolean,
+    val size: Int,
+    val total: Int
+    )

+ 20 - 0
app/src/main/java/com/rc/httpcore/bean/ChemicalInfoBean.kt

@@ -0,0 +1,20 @@
+package com.rc.httpcore.bean
+
+data class ChemicalInfoBean(
+    val adminName: String,
+    val adminPhone: String,
+    val buildId: String,
+    val buildName: String,
+    val deptId: String,
+    val deptName: String,
+    val floorId: String,
+    val floorName: String,
+    val infoId: String,
+    val oneSafeName: String,
+    val oneSafePhone: String,
+    val subId: String,
+    val twoSafeName: String,
+    val twoSafePhone: String,
+    val subName:String,
+    val roomNum:String,
+)

+ 33 - 0
app/src/main/java/com/rc/httpcore/bean/ChemicalSearchBean.kt

@@ -0,0 +1,33 @@
+package com.rc.httpcore.bean
+
+import java.math.BigDecimal
+
+
+//化学品搜索信息
+data class ChemicalSearchBean(
+    val anotherName: String,  //化学品别名
+    val anotherNameChar: String,
+    val anotherNamePinyin: String,
+    val casNum: String, //cas号
+    val chemicalCategory: String?,
+    val chemicalDensity: String?,
+    val chemicalId: String,
+    val chemicalLevel: Int, //1是管控,2是非管控
+    val chemicalName: String, //化学品名称
+    val chemicalNameChar: String,
+    val chemicalNamePinyin: String,
+    val chemicalNum: Any,
+    val chemicalPurity: String, //纯度
+    val createBy: Long,
+    val createName: String,
+    val createTime: String,
+    val depositRequire: Int,
+    val depositUnit: String?,
+    val msdsId: Any,
+    val updateBy: Long,
+    val updateName: String,
+    val packNum: BigDecimal?,  //包装值
+    val specNum: BigDecimal?, //规格值
+    val updateTime: String,
+    var chemicalCategoryName: String?
+)

+ 14 - 0
app/src/main/java/com/rc/httpcore/bean/ChemicalTimeoutListBean.kt

@@ -0,0 +1,14 @@
+package com.rc.httpcore.bean
+
+data class ChemicalTimeoutListBean(
+    val belongName: String,
+    val casNum: String,
+    val chemicalCategoryName: String,
+    val chemicalName: String,
+    val chemicalPurity: String,
+    val deptName: String,
+    val normsNum: String,
+    val position: String, //位置
+    val surplus: String,
+    val tagCode: String
+)

+ 11 - 0
app/src/main/java/com/rc/httpcore/bean/ChemicalTypeBean.kt

@@ -0,0 +1,11 @@
+package com.rc.httpcore.bean
+
+data class ChemicalTypeBean(
+    val isDefault: Boolean,
+    val itemId: String,
+    val label: String,
+    val listClass: Any,
+    val remark: Any,
+    val sort: Int,
+    val value: String  //类别编码
+)

+ 21 - 0
app/src/main/java/com/rc/httpcore/bean/ChemicalWarnInfoBean.kt

@@ -0,0 +1,21 @@
+package com.rc.httpcore.bean
+
+import java.math.BigDecimal
+
+//超期未归还
+data class ChemicalWarnInfoBean(
+    val applyNum: BigDecimal,
+    val belongName: String,
+    val casNum: String?,
+    val chemicalCategory: String,
+    val chemicalName: String,
+    val chemicalPurity: String,
+    val createTime: String,
+    val deptName: String,
+    val inStockNum: BigDecimal,
+    val isStock: Boolean,
+    val normsNum: String,
+    val source: String,
+    val subName: String,
+    val waitId: String
+)

+ 26 - 0
app/src/main/java/com/rc/httpcore/bean/ChemistryBean.kt

@@ -0,0 +1,26 @@
+package com.rc.httpcore.bean
+
+import java.math.BigDecimal
+
+//领用时需要传得参数
+data class ChemistryBean(
+    val id: String,
+    val stockId: String, //库存ID
+    val weigh: BigDecimal? = null,   //称重重量  or 零用量
+    var outOneUser: String? = null,  //领用第一验证人id
+    var outOneUserName: String? = null,  //领用第一验证人姓名
+    var outTwoUser: String? = null,  //领用第二验证人id
+    var outTwoUserName: String? = null,  //领用第二验证人
+    var outType: Int = 1,  //领用方式(1是称重,2是录入)
+    var allBottle: Boolean = false,  //是否整瓶领用
+    //=====================临时参数 需要显示====================== 领用时可以不传
+    var chemicalName: String,
+    var code: String,//rfid  or tagCode
+    var spec: String,//规格   specNum specUnit/packUnit
+    var duration: String,//领用时效
+    val stockDetailsId: String, //后端需要得id
+    val surplus: BigDecimal,//当前剩余量
+    val packNum: BigDecimal,//包装值
+    val chemicalDensity: BigDecimal,//密度
+    val specUnit: String?,
+)

+ 33 - 0
app/src/main/java/com/rc/httpcore/bean/ConfigBean.kt

@@ -0,0 +1,33 @@
+package com.rc.httpcore.bean
+
+import java.math.BigDecimal
+
+data class ConfigBean(
+    val createBy: Long,
+    val createTime: String,
+    val id: String,
+    val loginType: String,  //登陆方式
+    val circularLogo: String,
+    val subName: String,
+    val levelName: String?,
+    val levelColor: String,
+    val roomNum: String,
+    val deptName: String,
+    val buildName: String, //楼栋3 buildName+floorName
+    val floorName: String,
+    val operateTimeout: Int,
+    val remark: Any,
+    val subscribe: Boolean,
+    val timeoutHour: Int,
+    val timeoutMinute: Any,
+    val updateBy: Long,
+    val updateTime: String,
+    val useHour: Int,
+    val useMinute: Any,
+    val weighHintPicture: String? = null,
+    var backTime: Int = 40,//自动返回时间
+    var signOutTime: Int = 50,//自动注销时间   注销时间必须大于自动返回时间
+    var offTime: Int = 30,//弹窗关闭时间       //必须小于自动返回时间
+    val verifyType: String, //双人验证方式
+    val vinVex: BigDecimal = BigDecimal(5.00)  //称重入库  容差率
+)

+ 14 - 0
app/src/main/java/com/rc/httpcore/bean/ConfigModel.kt

@@ -0,0 +1,14 @@
+package com.rc.httpcore.bean
+
+data class ConfigModel(
+    val controlType: Boolean,
+    val fluidMax: Int,
+    val id: String,
+    val inputWeight: Boolean,
+    val metage: Boolean,
+    val outRange: Boolean,
+    var qrCodePrint: Boolean,
+    val remark: Any,
+    val solidMax: Int,
+    val verify: Boolean
+)

+ 14 - 0
app/src/main/java/com/rc/httpcore/bean/EarlyWarningBean.kt

@@ -0,0 +1,14 @@
+package com.rc.httpcore.bean
+
+data class EarlyWarningBean(
+    val countId: Any,
+    val current: Int,
+    val maxLimit: Any,
+    val optimizeCountSql: Boolean,
+    val orders: List<Any>,
+    val pages: Int,
+    val records: List<EarlyWarningListBean>,
+    val searchCount: Boolean,
+    val size: Int,
+    val total: Int
+)

+ 10 - 0
app/src/main/java/com/rc/httpcore/bean/EarlyWarningListBean.kt

@@ -0,0 +1,10 @@
+package com.rc.httpcore.bean
+
+data class EarlyWarningListBean(
+    val noticeId: String,
+    val warnTime: String,
+    val warnContent: String,
+    val noticeStatus: Int,   //1  已处理     0 待处理(可以手动处理)  2 待处理(不可手动处理  显示详情)
+    val isHandlePeople: Boolean,
+    var solve: Boolean = false
+)

+ 27 - 0
app/src/main/java/com/rc/httpcore/bean/GiveBackBean.kt

@@ -0,0 +1,27 @@
+package com.rc.httpcore.bean
+
+//归还化学品上传信息
+data class GiveBackBean(
+    var backOneUser: String? = null,
+    var backOneUserName: String? = null,
+    var backTwoUser: String? = null,
+    var backTwoUserName: String? = null,
+    var outOneUser: String? = null,
+    var outOneUserName: String? = null,
+    var outTwoUser: String? = null,
+    var outTwoUserName: String? = null,
+    var outType: Int = 1,  //领用方式(1是称重,2是录入)
+    val cabinetId: String,
+    val cabinetName: String,
+    val doorId: String,
+    val doorName: String,
+    val id: String,
+    val layers: Int,
+    val returnType: Int,  //归还方式(1是称重,2是录入)
+    val stockDetailsId: String,
+    val stockId: String,
+    val useStatus: Int, //0归还  3空瓶出库,4作废出库
+    val remark: String? = null, //repeal
+    val weigh: Double?,
+    val disuseReason: String? = null  //废弃时需要填写的备注信息
+)

+ 448 - 0
app/src/main/java/com/rc/httpcore/bean/HxpChemicalVo.java

@@ -0,0 +1,448 @@
+package com.rc.httpcore.bean;
+
+
+import java.io.Serializable;
+import java.util.List;
+
+public class HxpChemicalVo implements Serializable {
+
+    // 实验室id
+    private long subId;
+    // ("化学平id")
+    private long chemicalId;
+    // ("化学品类别(参考字典表)")
+    private String chemicalCategory;
+    // ("化学品级别:1是管控,2是非管控")
+    private int chemicalLevel;
+    // ("创建时间")
+    private String createTime;
+    // ("化学品名称")
+    private String chemicalName;
+    // ("化学品类别")
+    private String chemicalCategoryName;
+    // ("化学品级别:1是管控,2是非管控")
+    private String chemicalLevelName;
+    // ("cas编号")
+    private String casNum;
+    // ("纯度")
+    private String chemicalPurity;
+    // ("密度")
+    private double chemicalDensity;
+    // ("化学品别名")
+    private String anotherName;
+    // ("存放规定单位")
+    private String depositUnit;
+    // ("规格值")
+    private double specNum;
+    // ("规格单位")
+    private String specUnit;
+    // ("包装值")
+    private double packNum;
+    // ("包装单位")
+    private String packUnit;
+    // 厂家
+    private String factory;
+    // 净含量
+    private double netContent;
+    // 重量
+    private double weigh;
+
+    // 归属人id
+    private long belongId;
+    // 归属人名字
+    private String belongName;
+    // 归属人归属类型,1课题组 2人员
+    private int belongType;
+    // 归属人工号
+    private String belongAccount;
+
+    // 标签信息
+    private List<StockDetailsModel> stockDetailsModelList;
+
+    // "马克思2:15222222220,熊学生:15200000001"
+    private String safeUser;
+    // "高安全:18700001202"
+    private String adminUser;
+
+    // ("第一认证人id")
+    private long oneUserId;
+    // ("第一认证人姓名")
+    private String oneUserName;
+    // ("第一认证人电话")
+    private String oneUserMobile;
+
+    // ("第二认证人id")
+    private long twoUserId;
+    // ("第二认证人姓名")
+    private String twoUserName;
+    // ("第二认证人电话")
+    private String twoUserMobile;
+    // 化学品数量
+    private int chemicalNumber;
+    // 机柜Id
+    private long cabinetId;
+    // ("机柜名称")
+    private String cabinetName;
+    // ("柜门id")
+    private Long doorId;
+    // ("柜门名称")
+    private String doorName;
+    // 存储到哪层层数
+    private String layers;
+
+    /**
+     * 是否全新
+     * 1.扫码国瑞码调用接口会返回
+     * 2.手动录入都是旧的 false
+     */
+    private boolean isNew = false;
+
+    /**
+     * 1系统申购 2 待入库添加(待入库2和3) 4采购系统 5危化品系统 6终端新增
+     * 国瑞扫码是4,手动录入是6
+     */
+    private int stockType = 6;
+
+    /**
+     * 入库方式1 称重,2 录入
+     * 如果称重了为1
+     * 没有称重为2
+     */
+    private int joinType = 2;
+
+    public int getStockType() {
+        return stockType;
+    }
+
+    public void setStockType(int stockType) {
+        this.stockType = stockType;
+    }
+
+    public int getJoinType() {
+        return joinType;
+    }
+
+    public void setJoinType(int joinType) {
+        this.joinType = joinType;
+    }
+
+    public boolean isNew() {
+        return isNew;
+    }
+
+    public void setNew(boolean aNew) {
+        isNew = aNew;
+    }
+
+    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 getDoorId() {
+        return doorId;
+    }
+
+    public void setDoorId(Long doorId) {
+        this.doorId = doorId;
+    }
+
+    public String getDoorName() {
+        return doorName;
+    }
+
+    public void setDoorName(String doorName) {
+        this.doorName = doorName;
+    }
+
+    public String getLayers() {
+        return layers;
+    }
+
+    public void setLayers(String layers) {
+        this.layers = layers;
+    }
+
+    public int getChemicalNumber() {
+        return chemicalNumber;
+    }
+
+    public void setChemicalNumber(int chemicalNumber) {
+        this.chemicalNumber = chemicalNumber;
+    }
+
+    public String getBelongAccount() {
+        return belongAccount;
+    }
+
+    public void setBelongAccount(String belongAccount) {
+        this.belongAccount = belongAccount;
+    }
+
+    public long getBelongId() {
+        return belongId;
+    }
+
+    public void setBelongId(long belongId) {
+        this.belongId = belongId;
+    }
+
+    public String getBelongName() {
+        return belongName;
+    }
+
+    public void setBelongName(String belongName) {
+        this.belongName = belongName;
+    }
+
+    public int getBelongType() {
+        return belongType;
+    }
+
+    public void setBelongType(int belongType) {
+        this.belongType = belongType;
+    }
+
+    public String getSafeUser() {
+        return safeUser;
+    }
+
+    public void setSafeUser(String safeUser) {
+        this.safeUser = safeUser;
+    }
+
+    public String getAdminUser() {
+        return adminUser;
+    }
+
+    public void setAdminUser(String adminUser) {
+        this.adminUser = adminUser;
+    }
+
+    public double getWeigh() {
+        return weigh;
+    }
+
+    public void setWeigh(double weigh) {
+        this.weigh = weigh;
+    }
+
+    public double getNetContent() {
+        return netContent;
+    }
+
+    public void setNetContent(double netContent) {
+        this.netContent = netContent;
+    }
+
+    public long getChemicalId() {
+        return chemicalId;
+    }
+
+    public void setChemicalId(long chemicalId) {
+        this.chemicalId = chemicalId;
+    }
+
+    public String getChemicalCategory() {
+        return chemicalCategory;
+    }
+
+    public void setChemicalCategory(String chemicalCategory) {
+        this.chemicalCategory = chemicalCategory;
+    }
+
+    public int getChemicalLevel() {
+        return chemicalLevel;
+    }
+
+    public void setChemicalLevel(int chemicalLevel) {
+        this.chemicalLevel = chemicalLevel;
+    }
+
+    public String getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(String createTime) {
+        this.createTime = createTime;
+    }
+
+    public String getChemicalName() {
+        return chemicalName;
+    }
+
+    public void setChemicalName(String chemicalName) {
+        this.chemicalName = chemicalName;
+    }
+
+    public String getChemicalCategoryName() {
+        return chemicalCategoryName;
+    }
+
+    public void setChemicalCategoryName(String chemicalCategoryName) {
+        this.chemicalCategoryName = chemicalCategoryName;
+    }
+
+    public String getChemicalLevelName() {
+        return chemicalLevelName;
+    }
+
+    public void setChemicalLevelName(String chemicalLevelName) {
+        this.chemicalLevelName = chemicalLevelName;
+    }
+
+    public String getCasNum() {
+        return casNum;
+    }
+
+    public void setCasNum(String casNum) {
+        this.casNum = casNum;
+    }
+
+    public String getChemicalPurity() {
+        return chemicalPurity;
+    }
+
+    public void setChemicalPurity(String chemicalPurity) {
+        this.chemicalPurity = chemicalPurity;
+    }
+
+    public double getChemicalDensity() {
+        return chemicalDensity;
+    }
+
+    public void setChemicalDensity(double chemicalDensity) {
+        this.chemicalDensity = chemicalDensity;
+    }
+
+    public String getAnotherName() {
+        return anotherName;
+    }
+
+    public void setAnotherName(String anotherName) {
+        this.anotherName = anotherName;
+    }
+
+    public String getDepositUnit() {
+        return depositUnit;
+    }
+
+    public void setDepositUnit(String depositUnit) {
+        this.depositUnit = depositUnit;
+    }
+
+    public double getSpecNum() {
+        return specNum;
+    }
+
+    public void setSpecNum(double specNum) {
+        this.specNum = specNum;
+    }
+
+    public String getSpecUnit() {
+        return specUnit;
+    }
+
+    public void setSpecUnit(String specUnit) {
+        this.specUnit = specUnit;
+    }
+
+    public double getPackNum() {
+        return packNum;
+    }
+
+    public void setPackNum(double packNum) {
+        this.packNum = packNum;
+    }
+
+    public String getPackUnit() {
+        return packUnit;
+    }
+
+    public void setPackUnit(String packUnit) {
+        this.packUnit = packUnit;
+    }
+
+    public String getFactory() {
+        return factory;
+    }
+
+    public void setFactory(String factory) {
+        this.factory = factory;
+    }
+
+    public List<StockDetailsModel> getStockDetailsModelList() {
+        return stockDetailsModelList;
+    }
+
+    public void setStockDetailsModelList(List<StockDetailsModel> stockDetailsModelList) {
+        this.stockDetailsModelList = stockDetailsModelList;
+    }
+
+    public long getSubId() {
+        return subId;
+    }
+
+    public void setSubId(long subId) {
+        this.subId = subId;
+    }
+
+    public long getOneUserId() {
+        return oneUserId;
+    }
+
+    public void setOneUserId(long oneUserId) {
+        this.oneUserId = oneUserId;
+    }
+
+    public String getOneUserName() {
+        return oneUserName;
+    }
+
+    public void setOneUserName(String oneUserName) {
+        this.oneUserName = oneUserName;
+    }
+
+    public String getOneUserMobile() {
+        return oneUserMobile;
+    }
+
+    public void setOneUserMobile(String oneUserMobile) {
+        this.oneUserMobile = oneUserMobile;
+    }
+
+    public long getTwoUserId() {
+        return twoUserId;
+    }
+
+    public void setTwoUserId(long twoUserId) {
+        this.twoUserId = twoUserId;
+    }
+
+    public String getTwoUserName() {
+        return twoUserName;
+    }
+
+    public void setTwoUserName(String twoUserName) {
+        this.twoUserName = twoUserName;
+    }
+
+    public String getTwoUserMobile() {
+        return twoUserMobile;
+    }
+
+    public void setTwoUserMobile(String twoUserMobile) {
+        this.twoUserMobile = twoUserMobile;
+    }
+}

+ 164 - 0
app/src/main/java/com/rc/httpcore/bean/HxpControlConfig.java

@@ -0,0 +1,164 @@
+package com.rc.httpcore.bean;
+
+/**
+ * <p>
+ * 化学品管控配置
+ * </p>
+ *
+ * @author hzw
+ * @since 2024-01-23
+ */
+public class HxpControlConfig {
+
+    private long id;
+
+    // ("是否管控 ,0否1是")
+    private boolean controlType;
+
+    // ("是否开启双人认证,0否1是")
+    private boolean verify;
+
+    // ("是否开启二维码打印,0否1是")
+    private boolean qrCodePrint;
+
+    // ("是否开启领用称重,0否1是")
+    private boolean metage;
+
+    // ("是否开启手动录入重量,0否1是")
+    private boolean inputWeight;
+
+    // ("是否启用量程外 ,0否1是")
+    private boolean outRange;
+
+    // ("最大液体存储量(L)")
+    private int fluidMax;
+
+    // ("最大固体存储量(KG)")
+    private int solidMax;
+
+    // ("创建时间")
+    private String createTime;
+
+    // ("创建人id")
+    private long createBy;
+
+    // ("修改人姓名")
+    private long updateBy;
+
+    // ("修改时间")
+    private String updateTime;
+
+    private String remark;
+
+    public long getId() {
+        return id;
+    }
+
+    public void setId(long id) {
+        this.id = id;
+    }
+
+    public boolean isControlType() {
+        return controlType;
+    }
+
+    public void setControlType(boolean controlType) {
+        this.controlType = controlType;
+    }
+
+    public boolean isVerify() {
+        return verify;
+    }
+
+    public void setVerify(boolean verify) {
+        this.verify = verify;
+    }
+
+    public boolean isQrCodePrint() {
+        return qrCodePrint;
+    }
+
+    public void setQrCodePrint(boolean qrCodePrint) {
+        this.qrCodePrint = qrCodePrint;
+    }
+
+    public boolean isMetage() {
+        return metage;
+    }
+
+    public void setMetage(boolean metage) {
+        this.metage = metage;
+    }
+
+    public boolean isInputWeight() {
+        return inputWeight;
+    }
+
+    public void setInputWeight(boolean inputWeight) {
+        this.inputWeight = inputWeight;
+    }
+
+    public boolean isOutRange() {
+        return outRange;
+    }
+
+    public void setOutRange(boolean outRange) {
+        this.outRange = outRange;
+    }
+
+    public int getFluidMax() {
+        return fluidMax;
+    }
+
+    public void setFluidMax(int fluidMax) {
+        this.fluidMax = fluidMax;
+    }
+
+    public int getSolidMax() {
+        return solidMax;
+    }
+
+    public void setSolidMax(int solidMax) {
+        this.solidMax = solidMax;
+    }
+
+    public String getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(String createTime) {
+        this.createTime = createTime;
+    }
+
+    public long getCreateBy() {
+        return createBy;
+    }
+
+    public void setCreateBy(long createBy) {
+        this.createBy = createBy;
+    }
+
+    public long getUpdateBy() {
+        return updateBy;
+    }
+
+    public void setUpdateBy(long updateBy) {
+        this.updateBy = updateBy;
+    }
+
+    public String getUpdateTime() {
+        return updateTime;
+    }
+
+    public void setUpdateTime(String updateTime) {
+        this.updateTime = updateTime;
+    }
+
+    public String getRemark() {
+        return remark;
+    }
+
+    public void setRemark(String remark) {
+        this.remark = remark;
+    }
+}

+ 66 - 0
app/src/main/java/com/rc/httpcore/bean/HxpStockWaitListBean.kt

@@ -0,0 +1,66 @@
+package com.rc.httpcore.bean
+
+import java.math.BigDecimal
+
+data class HxpStockWaitListBean(
+    var id:String?,
+    val adminName: String,
+    val adminPhone: String,
+    val anotherName: Any,
+    val applyNum: BigDecimal,
+    val applyReason: String,
+    val applyUnit: String,  //包装
+    val applyUserId: String,
+    val applyUserName: String,
+    val applyUserPhone: String,
+    val buildId: String,
+    val buildName: String,
+    val casNum: String?,
+    val chemicalCategory: String,
+    val chemicalCategoryName: String?,
+    val chemicalDensity: Double,  //密度
+    val chemicalId: String,
+    val chemicalLevel: Int,    //管控 1     非管控  2
+    val chemicalLevelName: Any,
+    val chemicalName: String,
+    val chemicalNum: Any,
+    val chemicalPurity: String?,
+    val deptId: String,
+    val deptName: String,
+    val expireTime: String?=null,
+    val factory: String,
+    val floorId: String,
+    val floorName: String,
+    var layers:Int,//层数
+    val isDel: Boolean,
+    val isOverdue: Any,
+    val mentorId: Any,
+    val mentorName: Any,
+    val mentorPhone: Any,
+    val normsNum: Double, //规格
+    val normsUnit: String,
+    val oneSafeName: String,
+    val oneSafePhone: String,
+    val remarks: Any,
+    val roomNum: String,
+    val shape: Any,
+    val source: Int,
+    val stockPeriod: String,
+    val subId: String,
+    val subName: String,
+    val topicGroup: Boolean,  //是否是 课题组
+    val topicGroupId: String,
+    val topicGroupName: String,
+    val topicUserName: Any,
+    val topicUserPhone: Any,
+    val twoSafeName: String,
+    val twoSafePhone: String,
+    val waitId: String,
+    val cabinetName:String?,
+    val doorName:String?,
+    val surplus:BigDecimal?,
+    val chemicalNumber:Int?,
+    val stockNum:Int?,  //已入库数量
+    var isType: Boolean = false,  //不选中 true选中
+    val specUnit:String?,
+)

+ 14 - 0
app/src/main/java/com/rc/httpcore/bean/HxpStockWaitModeBean.kt

@@ -0,0 +1,14 @@
+package com.rc.httpcore.bean
+
+data class HxpStockWaitModeBean(
+    val countId: Any,
+    val current: Int,  //当前页
+    val maxLimit: Any,
+    val optimizeCountSql: Boolean,
+    val orders: List<Any>,
+    val pages: Int,   //一共几页
+    val records: List<HxpStockWaitListBean>,
+    val searchCount: Boolean,
+    val size: Int,
+    val total: Int
+)

+ 65 - 0
app/src/main/java/com/rc/httpcore/bean/InventoryBean.kt

@@ -0,0 +1,65 @@
+package com.rc.httpcore.bean
+
+import java.math.BigDecimal
+
+data class InventoryBean(
+    val adminUser: String,
+    val belongName: String,
+    val buildName: String,
+    val cabinetId: Long,
+    val cabinetName: String,
+    val casNum: String,
+    val chemicalCategory: String,
+    val chemicalCategoryName: Any,
+    val chemicalLevel: Int,
+    val chemicalLevelName: Any,
+    val chemicalName: String,
+    val chemicalNumber: Int,
+    val chemicalShape: Any,
+    val createBy: Long,
+    val createByName: String,
+    val createTime: String,
+    val deptName: String,
+    val disuseReason: Any,
+    val doorId: Long,
+    val doorName: String,
+    val expireStatus: Int,
+    val expireTime: String,
+    val floorName: String,
+    val id: String,
+    val joinTime: String,
+    val joinType: Any,
+    val joinUserId: Long,
+    val joinUserMobile: String,
+    val joinUserName: String,
+    val joinVideo: Any,
+    val layers: String,
+    val oneUserId: Any,
+    val oneUserMobile: Any,
+    val oneUserName: Any,
+    val `operator`: Any,
+    val outTime: String,
+    val outUserId: Any,
+    val outUserMobile: Any,
+    val outUserName: Any,
+    val outVideo: Any,
+    val packNum: BigDecimal,
+    val packUnit: String,
+    val position: Any,
+    val rfidCode: String,
+    val safeUser: String,
+    val specNum: Int,
+    val specUnit: String,
+    val status: Int,
+    val statusStr: String,
+    val stockId: String,
+    val subName: String,
+    val subRoom: String,
+    val surplus: Double,
+    val tagCode: String,
+    val twoUserId: Any,
+    val twoUserMobile: Any,
+    val twoUserName: Any,
+    val usages: Double,
+    val useStatus: Int
+)

+ 14 - 0
app/src/main/java/com/rc/httpcore/bean/InventoryListBean.kt

@@ -0,0 +1,14 @@
+package com.rc.httpcore.bean
+
+data class InventoryListBean(
+    val countId: Any,
+    val current: Int,
+    val maxLimit: Any,
+    val optimizeCountSql: Boolean,
+    val orders: List<Any>,
+    val pages: Int,
+    val records: List<InventoryBean>,
+    val searchCount: Boolean,
+    val size: Int,
+    val total: Int
+)

+ 12 - 0
app/src/main/java/com/rc/httpcore/bean/LockVoListBean.kt

@@ -0,0 +1,12 @@
+package com.rc.httpcore.bean
+
+//柜锁数量
+data class LockVoListBean(
+    val doorId: String,
+    val lockId: String,
+    val unlockingMethod: Int,
+    val lockName: String,
+    val lockNum: String,
+    val cabinetLattice: String?,
+    var isOk: Boolean = false
+)

+ 14 - 0
app/src/main/java/com/rc/httpcore/bean/MsdsBean.kt

@@ -0,0 +1,14 @@
+package com.rc.httpcore.bean
+
+data class MsdsBean(
+    val countId: Any,
+    val current: Int,
+    val maxLimit: Any,
+    val optimizeCountSql: Boolean,
+    val orders: List<Any>,
+    val pages: Int,
+    val records: List<MsdsListBean>,
+    val searchCount: Boolean,
+    val size: Int,
+    val total: Int
+)

+ 21 - 0
app/src/main/java/com/rc/httpcore/bean/MsdsListBean.kt

@@ -0,0 +1,21 @@
+package com.rc.httpcore.bean
+
+data class MsdsListBean(
+    val alias: Any,
+    val casNum: Any,
+    val code: String,
+    val content: String,
+    val createBy: Long,
+    val createByName: String,
+    val createTime: String,
+    val id: String,
+    val name: String,
+    val pinYin: String,
+    val pinYinChar: String,
+    val qrCodeUrl: Any,
+    val remark: Any,
+    val scanCount: Int,
+    val updateBy: Long,
+    var isType: Boolean = false,  //是否点击
+    val updateTime: String
+)

+ 11 - 0
app/src/main/java/com/rc/httpcore/bean/PageHxpStockBean.kt

@@ -0,0 +1,11 @@
+package com.rc.httpcore.bean
+
+data class PageHxpStockBean(
+    val current: Int,   //当前页
+    val optimizeCountSql: Boolean,
+    val pages: Int,    //共计多少页
+    val records: List<HxpStockWaitListBean>,
+    val searchCount: Boolean,
+    val size: Int,   //当前页多少条
+    val total: Int  //总数:total
+)

+ 14 - 0
app/src/main/java/com/rc/httpcore/bean/QueryTwoBean.kt

@@ -0,0 +1,14 @@
+package com.rc.httpcore.bean
+
+data class QueryTwoBean(
+    val countId: Any,
+    val current: Int,
+    val maxLimit: Any,
+    val optimizeCountSql: Boolean,
+    val orders: List<Any>,
+    val pages: Int,
+    val records: List<QueryTwoListBean>,
+    val searchCount: Boolean,
+    val size: Int,
+    val total: Int
+)

+ 63 - 0
app/src/main/java/com/rc/httpcore/bean/QueryTwoListBean.kt

@@ -0,0 +1,63 @@
+package com.rc.httpcore.bean
+
+import java.math.BigDecimal
+
+data class QueryTwoListBean(
+    val adminUser: String,
+    val belongName: String,
+    val buildName: String,
+    val cabinetId: String,
+    val cabinetName: String,
+    val casNum: String,
+    val chemicalCategory: String,
+    val chemicalCategoryName: String?,
+    val chemicalLevel: Int,
+    val chemicalLevelName: String?,
+    val chemicalName: String,
+    val createBy: String,
+    val createByName: String,
+    val createTime: String,
+    val deptName: String,
+    val disuseReason: String?,
+    val doorId: String,
+    val doorName: String,
+    val expireStatus: Int,
+    val expireTime: String,
+    val floorName: String,
+    val id: String,
+    val joinTime: String,
+    val joinUserId: String,
+    val joinUserMobile: String,
+    val joinUserName: String,
+    val joinVideo: String?,
+    val layers: String,
+    val oneUserId: String?,
+    val oneUserMobile: String?,
+    val oneUserName: String?,
+    val `operator`: String?,
+    val outTime: String?,
+    val outUserId: String?,
+    val outUserMobile: String?,
+    val outUserName: String?,
+    val outVideo: String?,
+    val packNum: BigDecimal?,
+    val packUnit: String,
+    val position: String?,
+    var rfidCode: String?,
+    val safeUser: String,
+    val specNum: BigDecimal,
+    val specUnit: String,
+    val status: Int,
+    val statusStr: String?,
+    val stockId: String,
+    val subName: String,
+    val subRoom: String,
+    val surplus: BigDecimal,
+    val tagCode: String,
+    val twoUserId: String?,
+    val twoUserMobile: String?,
+    val twoUserName: String?,
+    val usages: BigDecimal,
+    var isType: Boolean = false,  //不选中 true选中
+    val useStatus: Int
+)

+ 81 - 0
app/src/main/java/com/rc/httpcore/bean/ReturnDetailsBean.kt

@@ -0,0 +1,81 @@
+package com.rc.httpcore.bean
+
+data class ReturnDetailsBean(
+    val anotherName: Any,
+    val belongId: String,
+    val belongMobile: Any,
+    val belongName: String,
+    val belongType: Int,
+    val buildId: String,
+    val buildName: String,
+    var cabinetId: String,
+    var cabinetName: String,
+    val casNum: String,
+    val chemicalCategory: String,
+    val chemicalCategoryName: String,
+    val chemicalDensity: Double,
+    val chemicalId: String,
+    val chemicalLevel: Int,
+    val chemicalLevelName: String,
+    val chemicalName: String,
+    val chemicalNum: Any,
+    val chemicalNumber: Int,
+    val chemicalPurity: String,
+    val chemicalShape: Int,//管控 1     非管控  2
+    val collectStockNum: Double,
+    val collectTime: String,
+    val createByName: String,
+    val createTime: String,
+    val depositRequire: Any,
+    val depositUnit: Any,
+    val deptId: String,
+    val deptName: String,
+    var doorId: String,
+    var doorName: String,
+    val expireStatus: Int,
+    val expireTime: String?,
+    val factory: String,
+    val floorId: String,
+    val floorName: String,
+    val id: String,
+    val isNew: Boolean,
+    val joinTime: String,
+    val joinType: Int,
+    val joinVideo: String,
+    var layers: String,
+    val oneUserId: Any,
+    val oneUserMobile: Any,
+    val oneUserName: Any,
+    val outTime: Any,
+    val outUserName: Any,
+    val outVideo: Any,
+    val packNum: Double,
+    val packUnit: String,
+    val position: Any,
+    var rfidCode: String?,
+    val safeUser: Any,
+    val specNum: Double,
+    val specUnit: String,
+    val status: Int,
+    val statusStr: Any,
+    val stockDetailsId: String,
+    val subId: String,
+    val subName: String,
+    val subRoom: String,
+    val surplus: Double,
+    val surplusDouble: Any,
+    val surplusTime: String,
+    val tagCode: String,
+    val twoUserId: Any,
+    val twoUserMobile: Any,
+    val twoUserName: Any,
+    val usages: Double,
+    val useStatus: Int,
+    val useStatusName: Any,
+    val userecordId: String,
+    val wxCode: String?="",
+    val stockTypeStr: String = "领用",
+    var isType: Boolean = false,  //不选中 true选中
+    var verify: Boolean = true,  //是否需要双人认证
+    var cabinetLockVoList: List<LockVoListBean>? = null,  //柜锁有几个
+)

+ 6 - 0
app/src/main/java/com/rc/httpcore/bean/ReturnGiveBackBean.kt

@@ -0,0 +1,6 @@
+package com.rc.httpcore.bean
+
+import java.math.BigDecimal
+
+//归还返回得参数    使用量   剩余量
+data class ReturnGiveBackBean(val useAmount:BigDecimal?,val remark:BigDecimal?,val specUnit:String)

+ 14 - 0
app/src/main/java/com/rc/httpcore/bean/ReturningChemicalsBean.kt

@@ -0,0 +1,14 @@
+package com.rc.httpcore.bean
+
+//归还列表信息返回
+data class ReturningChemicalsBean(
+    val countId: Any,
+    val current: Int,
+    val maxLimit: Any,
+    val optimizeCountSql: Boolean,
+    val pages: Int,
+    val records: List<ReturningList>,
+    val searchCount: Boolean,
+    val size: Int,
+    val total: Int
+)

+ 63 - 0
app/src/main/java/com/rc/httpcore/bean/ReturningList.kt

@@ -0,0 +1,63 @@
+package com.rc.httpcore.bean
+
+import java.math.BigDecimal
+
+data class ReturningList(
+    val adventWarnDay: Any,
+    val allBottle: Any,
+    val backOneUser: Any,
+    val backOneUserName: Any,
+    val backTwoUser: Any,
+    val backTwoUserName: Any,
+    val belongName: String,
+    val cabinetName: String,
+    val casNum: String,
+    val chemicalCategory: String,
+    val chemicalId: Any,
+    val chemicalLevel: Int,
+    val chemicalName: String,
+    val chemicalPurity: String,
+    val chemicalShape: Int,
+    val collectNowNum: BigDecimal,
+    val collectStockNum: BigDecimal,
+    val collectTime: String,
+    val doorName: String,
+    val duration: String,
+    val expireTime: String?, //过期时间
+    val expireStatus: Int,  //过期状态
+    val id: String,
+    val joinType: Int,
+    val layers: String,
+    val lossAmount: Any,
+    val outOneUser: Any,
+    val outOneUserName: Any,
+    val outTwoUser: Any,
+    val outTwoUserName: Any,
+    val outType: Int,
+    val outUserName: Any,
+    val outVideo: Any,
+    val packNum: BigDecimal,
+    val packUnit: String,
+    val remark: Any,
+    val returnStockNum: Any,
+    val returnTime: Any,
+    val returnType: Any,
+    val returnVideo: Any,
+    val rfidCode: String,
+    val safeInfo: Any,
+    val securityPhone: Any,
+    val securityUserName: Any,
+    val specNum: BigDecimal,
+    val specUnit: String,
+    val status: Int,
+    val stockDetailsId: String,
+    val stockId: String,
+    val subId: Long,
+    val subName: String,
+    val subRoom: String,
+    val surplus: BigDecimal,
+    val tagCode: String,
+    val useAmount: Any,
+    val useStatus: Int,
+    val useName: String
+)

+ 29 - 0
app/src/main/java/com/rc/httpcore/bean/RuleBean.kt

@@ -0,0 +1,29 @@
+package com.rc.httpcore.bean
+
+data class RuleBean(
+    val buildId: String,
+    val buildName: String,
+    val cabinetDoorVoList: List<CabinetDoorVo>,//柜门
+    val cabinetId: String,
+    val doorUniqueId: String,
+    val cabinetName: String,
+    val cameraId: String,
+    val chemicalNum: Any,
+    val collectorId: String,
+    val createBy: String,
+    val createName: String,
+    val createTime: String,
+    val deptId: String,
+    val deptName: String,
+    val doorNum: Int,
+    val floorId: String,
+    val floorName: String,
+    val lockNum: String,  //柜锁编号
+    val safeInfo: Any,
+    val subId: String,
+    val subName: String,
+    val updateBy: String,
+    val updateName: Any,
+    var isType: Boolean = false,  //不选中 true选中
+    val updateTime: String
+)

+ 3 - 0
app/src/main/java/com/rc/httpcore/bean/SearchBean.kt

@@ -0,0 +1,3 @@
+package com.rc.httpcore.bean
+
+data class SearchBean(val producerName:String)

+ 3 - 0
app/src/main/java/com/rc/httpcore/bean/SearchSpecBean.kt

@@ -0,0 +1,3 @@
+package com.rc.httpcore.bean
+
+data class SearchSpecBean(val packUnit:String?,val packNum:String?)

+ 9 - 0
app/src/main/java/com/rc/httpcore/bean/StockDetailsModel.kt

@@ -0,0 +1,9 @@
+package com.rc.httpcore.bean
+
+data class StockDetailsModel(
+    var rfidCode: String? = null,  //扫码获得
+    val stockId: String? = null,
+    val tagCode: String?, //二维码编号
+    var no: Int,
+    var wxCode: String? = null
+)

+ 0 - 0
app/src/main/java/com/rc/httpcore/bean/StockModeBean.kt


이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.