sunqiang 1 anno fa
commit
44d56aff49
100 ha cambiato i file con 6504 aggiunte e 0 eliminazioni
  1. 10 0
      .gitignore
  2. 1 0
      HttpCoreLibrary/.gitignore
  3. 51 0
      HttpCoreLibrary/build.gradle
  4. 0 0
      HttpCoreLibrary/consumer-rules.pro
  5. 21 0
      HttpCoreLibrary/proguard-rules.pro
  6. 5 0
      HttpCoreLibrary/src/main/AndroidManifest.xml
  7. 87 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/HttpClient.kt
  8. 35 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/HttpConfig.kt
  9. 353 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/client/ApiRepository.kt
  10. 172 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/client/ChemicalClient.kt
  11. 9 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/client/factory/ClientFactory.kt
  12. 9 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/client/factory/RetrofitFactory.kt
  13. 247 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/client/retrofit/ApiService.java
  14. 464 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/client/retrofit/ChemicalRetrofit.kt
  15. 25 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/config/ConfigCore.kt
  16. 30 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/config/ConfigFactory.kt
  17. 68 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/config/ConfigParam.kt
  18. 30 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/config/OkHttpDNS.kt
  19. 22 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/converter/NullOnEmptyConverterFactory.kt
  20. 9 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/exception/NetException.kt
  21. 43 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/interceptor/HttpLoggingInterceptorLog.kt
  22. 62 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/interceptor/TokenHeaderInterceptor.kt
  23. 26 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/interceptor/formatter/GsonFormatter.kt
  24. 37 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/interceptor/formatter/JSONFormatter.kt
  25. 26 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/interceptor/formatter/OrgJsonFormatter.kt
  26. 14 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/net/DownloadListener.kt
  27. 156 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/net/DownloadTask.kt
  28. 140 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/net/ProgressMultipartBody.java
  29. 121 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/net/UploadTask.kt
  30. 5 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/CommonDataResponse.kt
  31. 6 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/CommonListResponse.kt
  32. 17 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/CommonResponse.kt
  33. 50 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/Optional.java
  34. 13 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/request/AlarmRecordReq.java
  35. 20 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/request/AuthFaceReq.java
  36. 19 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/request/ChemicalReq.java
  37. 16 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/request/FaceCompareReq.java
  38. 15 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/request/LearnLoginReq.java
  39. 15 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/request/OperRecordReq.java
  40. 16 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/request/OutputStockReq.java
  41. 18 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/request/RevertReq.java
  42. 16 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/request/TagCodeReq.java
  43. 13 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/request/UpdateAlarmRecordReq.java
  44. 13 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/request/UpdateRfidReq.java
  45. 18 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/request/UseReq.java
  46. 18 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/request/UserValidationVo.java
  47. 126 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/AioAlarmRecordVo.java
  48. 33 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/AioUserecordVo.java
  49. 13 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/ApkInfoResp.java
  50. 29 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/CabinetBean.java
  51. 79 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/CabinetLockVo.java
  52. 196 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/ChemicalBean.java
  53. 58 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/ClassifyConfig.java
  54. 14 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/ConfLogo.java
  55. 9 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/FaceBean.java
  56. 13 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/FileRespBean.java
  57. 36 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/HazardBook.java
  58. 73 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/HxpUserecord.java
  59. 21 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/LearnLoginVo.java
  60. 57 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/LockRespBean.java
  61. 67 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/SubInfoVo.java
  62. 8 0
      LICENSE
  63. 3 0
      README.md
  64. 1 0
      RcCore/.gitignore
  65. 52 0
      RcCore/build.gradle
  66. 0 0
      RcCore/consumer-rules.pro
  67. BIN
      RcCore/libs/tbs_sdk_v4.3.0.165_20210628_103707.jar
  68. 21 0
      RcCore/proguard-rules.pro
  69. 5 0
      RcCore/src/main/AndroidManifest.xml
  70. 8 0
      RcCore/src/main/java/com/rc/core/event/RefreshEvent.kt
  71. 29 0
      RcCore/src/main/java/com/rc/core/log/RcLog.kt
  72. 71 0
      RcCore/src/main/java/com/rc/core/ui/ActivityCollector.kt
  73. 128 0
      RcCore/src/main/java/com/rc/core/ui/activity/RcBaseActivity.kt
  74. 235 0
      RcCore/src/main/java/com/rc/core/ui/activity/RcRefreshActivity.kt
  75. 37 0
      RcCore/src/main/java/com/rc/core/ui/common/AbsUIDelegate.kt
  76. 25 0
      RcCore/src/main/java/com/rc/core/ui/common/IUIListener.kt
  77. 97 0
      RcCore/src/main/java/com/rc/core/ui/common/UIDelegateImpl.kt
  78. 104 0
      RcCore/src/main/java/com/rc/core/ui/dialog/LoadingDialog.kt
  79. 113 0
      RcCore/src/main/java/com/rc/core/ui/dialog/RcBaseDialog.kt
  80. 84 0
      RcCore/src/main/java/com/rc/core/ui/fragment/RcBaseFragment.kt
  81. 37 0
      RcCore/src/main/java/com/rc/core/ui/fragment/RcLazyFragment.kt
  82. 225 0
      RcCore/src/main/java/com/rc/core/ui/fragment/RcRefreshFragment.kt
  83. 301 0
      RcCore/src/main/java/com/rc/core/ui/widget/MultipleStatusView.kt
  84. 53 0
      RcCore/src/main/java/com/rc/core/ui/widget/decoration/GridSpacingItemDecoration.java
  85. 148 0
      RcCore/src/main/java/com/rc/core/ui/widget/decoration/NoLastLineItemDecoration.java
  86. 195 0
      RcCore/src/main/java/com/rc/core/util/ApkController.kt
  87. 61 0
      RcCore/src/main/java/com/rc/core/util/ApkUpdater.kt
  88. 208 0
      RcCore/src/main/java/com/rc/core/util/CrashHandler.kt
  89. 120 0
      RcCore/src/main/java/com/rc/core/util/CrashHelper.kt
  90. 102 0
      RcCore/src/main/java/com/rc/core/util/DateUtils.kt
  91. 91 0
      RcCore/src/main/java/com/rc/core/util/DeviceUtils.kt
  92. 68 0
      RcCore/src/main/java/com/rc/core/util/EscapeUnescape.java
  93. 18 0
      RcCore/src/main/java/com/rc/core/util/Extension.kt
  94. 32 0
      RcCore/src/main/java/com/rc/core/util/FastClickDelegate.kt
  95. 69 0
      RcCore/src/main/java/com/rc/core/util/Format.kt
  96. 162 0
      RcCore/src/main/java/com/rc/core/util/MediaUtils.kt
  97. 131 0
      RcCore/src/main/java/com/rc/core/util/ScreenAdapter.kt
  98. 50 0
      RcCore/src/main/java/com/rc/core/util/VideoFullScreenWebChromeClient.kt
  99. 27 0
      RcCore/src/main/java/com/rc/core/util/Weak.kt
  100. 0 0
      RcCore/src/main/java/com/rc/core/util/WebViewHelper.kt

+ 10 - 0
.gitignore

@@ -0,0 +1,10 @@
+*.iml
+/.gradle
+/.idea
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
+/gradle-build

+ 1 - 0
HttpCoreLibrary/.gitignore

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

+ 51 - 0
HttpCoreLibrary/build.gradle

@@ -0,0 +1,51 @@
+plugins {
+    id 'com.android.library'
+    id 'kotlin-android'
+}
+
+android {
+    compileSdkVersion env.compileSdkVersion
+    buildToolsVersion env.buildToolsVersion
+
+    defaultConfig {
+        minSdkVersion env.minSdkVersion
+        targetSdkVersion env.targetSdkVersion
+        versionCode 1
+        versionName "1.0"
+
+        consumerProguardFiles "consumer-rules.pro"
+    }
+
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+    }
+    compileOptions {
+        sourceCompatibility env.jdk_version
+        targetCompatibility env.jdk_version
+    }
+    kotlinOptions {
+        jvmTarget = '1.8'
+    }
+}
+
+dependencies {
+
+    implementation fileTree(dir: "libs", include: ["*.jar"])
+    implementation dep.kotlinStdlib
+    implementation dep.androidxCoreKtx
+    implementation dep.androidxLocalbroadcastmanager
+
+    api dep.retrofit
+    implementation dep.converterGson
+    implementation dep.converterScalars
+    implementation dep.rxJavaAdapter
+    implementation dep.okhttp3Logs
+    api dep.rxJava
+    api dep.rxAndroid
+    api dep.gson
+
+//    implementation dep.luban
+}

+ 0 - 0
HttpCoreLibrary/consumer-rules.pro


+ 21 - 0
HttpCoreLibrary/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

+ 5 - 0
HttpCoreLibrary/src/main/AndroidManifest.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.rc.httpcore">
+
+</manifest>

+ 87 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/HttpClient.kt

@@ -0,0 +1,87 @@
+package com.rc.httpcore
+
+import android.content.Context
+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.HttpLoggingInterceptorLog
+import com.rc.httpcore.interceptor.TokenHeaderInterceptor
+import okhttp3.OkHttpClient
+import okhttp3.logging.HttpLoggingInterceptor
+import retrofit2.Retrofit
+import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
+import retrofit2.converter.gson.GsonConverterFactory
+import java.util.concurrent.TimeUnit
+
+
+object HttpClient {
+
+    const val TIMEOUT_DEFAULT = 3 * 60L
+
+    private var mAppContext: Context? = null
+
+    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(RxJava2CallAdapterFactory.create())
+            .addConverterFactory(NullOnEmptyConverterFactory())
+            .addConverterFactory(GsonConverterFactory.create())
+            .build()
+    }
+
+    private fun buildHttpClient(): OkHttpClient {
+
+//        val httpLoggingInterceptor = HttpLoggingInterceptor(HttpLoggingInterceptorLog())//创建拦截对象
+//        httpLoggingInterceptor.level = HttpLoggingInterceptor.Level.BODY//这一句一定要记得写,否则没有数据输出
+        return OkHttpClient.Builder()
+            .readTimeout(TIMEOUT_DEFAULT, TimeUnit.SECONDS) // 设置读取超时时间
+            .connectTimeout(TIMEOUT_DEFAULT, TimeUnit.SECONDS) // 设置请求超时时间
+            .writeTimeout(TIMEOUT_DEFAULT, TimeUnit.SECONDS) // 设置写入超时时间
+            .addNetworkInterceptor(TokenHeaderInterceptor())
+            .addInterceptor(HttpLoggingInterceptorLog())//添加请求日志
+            .retryOnConnectionFailure(true) // 设置出现错误进行重新连接
+            .dns(OkHttpDNS())
+            .build()
+//        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()
+//        }
+    }
+
+
+
+}

+ 35 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/HttpConfig.kt

@@ -0,0 +1,35 @@
+package com.rc.httpcore
+
+class HttpConfig {
+
+    companion object {
+
+//        var API_BASE_URL = "http://pc44sory.xiaomy.net:31738/"
+        var API_BASE_URL = "http://192.168.1.43/labSystem/"
+//        var API_BASE_URL = ""
+
+        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/"
+    }
+
+}

+ 353 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/client/ApiRepository.kt

@@ -0,0 +1,353 @@
+package com.rc.httpcore.client
+
+import com.rc.httpcore.HttpClient
+import com.rc.httpcore.vo.CommonDataResponse
+import com.rc.httpcore.vo.CommonListResponse
+import com.rc.httpcore.vo.Optional
+import com.rc.httpcore.vo.request.*
+import com.rc.httpcore.vo.response.*
+import io.reactivex.Observable
+import io.reactivex.android.schedulers.AndroidSchedulers
+import io.reactivex.schedulers.Schedulers
+import java.io.File
+
+object ApiRepository {
+
+    private val mClientFactory by lazy { HttpClient.createClientFactory() }
+
+    /**
+     * 查询LOGO配置列表
+     */
+    fun queryLogoConfInfo(): Observable<ConfLogo> {
+        return mClientFactory.createLabClient()
+            .queryLogoConfInfo()
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    /**
+     * 查询APK版本
+     *
+     * @param id 设备唯一编码
+     */
+    fun apkVersion(id: String): Observable<ApkInfoResp> {
+        return mClientFactory.createLabClient()
+            .apkVersion(id)
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    /**
+     * 上传APK更新状态
+     *
+     * @param state 0:升级失败; 1:升级成功; 2:升级中
+     */
+    fun onepcApkUpdate(id: String, state: String): Observable<Boolean> {
+        return mClientFactory.createLabClient()
+            .onepcApkUpdate(id, state)
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    /**
+     * 登录+实验室管理员
+     */
+    fun learnLogin(
+        username: String,
+        androidId: String,
+        realLogin: Boolean = false
+    ): Observable<LearnLoginVo> {
+        val client = mClientFactory.createLabClient()
+        val param = LearnLoginReq().apply {
+            userName = username
+            machineCode = androidId
+            type = if (realLogin) "2" else "1"
+        }
+        return if (realLogin) {
+            val observable1 = client.learnLogin(param)
+            val observable2 = client.authValidation()
+            Observable.zip(observable1, observable2, { response1, response2 ->
+                response1.isManager = response2
+                response1
+            })
+        } else {
+            client.learnLogin(param)
+        }.subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    /**
+     * 退出登录
+     */
+    fun loginOut(): Observable<Boolean> {
+        return mClientFactory.createLabClient()
+            .loginOut()
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    /**
+     * 人脸比对
+     */
+    fun faceCompare(file: File,param: FaceCompareReq): Observable<Boolean> {
+        return mClientFactory.createLabClient()
+            .faceCompare(file,param)
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    /**
+     * 人脸特征值
+     */
+    fun getFaceFeature(file: File): Observable<String> {
+        return mClientFactory.createLabClient()
+            .getFaceFeature(file)
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    /**
+     * 查询实验室机柜列表
+     */
+    fun queryCabinetList(): Observable<List<CabinetBean>> {
+        return mClientFactory.createLabClient()
+            .queryCabinetList()
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    /**
+     * 查询化学品
+     *
+     * @param byStock ture: 查询库存化学品
+     */
+    fun queryChemicals(
+        byStock: Boolean,
+        param: ChemicalReq
+    ): Observable<CommonListResponse<ChemicalBean>> {
+        return if (byStock) queryChemicalByStock(param) else queryChemicalList(param)
+    }
+
+    /**
+     * 查询化学品
+     */
+    fun queryChemicalList(param: ChemicalReq): Observable<CommonListResponse<ChemicalBean>> {
+        return mClientFactory.createLabClient()
+            .queryChemicalList(param)
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    /**
+     * 查询库存化学品
+     */
+    fun queryChemicalByStock(param: ChemicalReq): Observable<CommonListResponse<ChemicalBean>> {
+        return mClientFactory.createLabClient()
+            .queryChemicalByStock(param)
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    /**
+     * 双人验证 刷卡识别身份权限
+     */
+    fun userValidation(param: UserValidationVo): Observable<UserValidationVo> {
+        return mClientFactory.createLabClient()
+            .userValidation(param)
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    /**
+     * 化学品入库
+     */
+    fun addStock(param: ChemicalBean): Observable<ChemicalBean> {
+        return mClientFactory.createLabClient()
+            .addStock(param)
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    /**
+     * 获取危化品安全技术说明书详细信息
+     */
+    fun hazardBookDetail(id: String): Observable<HazardBook> {
+        return mClientFactory.createLabClient()
+            .hazardBookDetail(id)
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    /**
+     * 通过识别卡号或者二维码扫码查询已入库化学品
+     */
+    fun queryByTagCode(param: TagCodeReq): Observable<Optional<ChemicalBean>> {
+        return mClientFactory.createLabClient()
+            .queryByTagCode(param)
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    /**
+     * 领用化学品
+     */
+    fun addUserecord(param: UseReq): Observable<Boolean> {
+        return mClientFactory.createLabClient()
+            .addUserecord(param)
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    /**
+     * 归还时查询已领用化学品
+     */
+    fun queryByUserecord(param: TagCodeReq): Observable<ChemicalBean> {
+        return mClientFactory.createLabClient()
+            .queryByUserecord(param)
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    /**
+     * 归还化学品
+     */
+    fun updateUserecord(param: RevertReq): Observable<Boolean> {
+        return mClientFactory.createLabClient()
+            .updateUserecord(param)
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    /**
+     * 化学品出库
+     */
+    fun outStock(param: OutputStockReq): Observable<Boolean> {
+        return mClientFactory.createLabClient()
+            .outStock(param)
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    /**
+     * RFID 标签更换
+     */
+    fun updateStock(param: UpdateRfidReq): Observable<Boolean> {
+        return mClientFactory.createLabClient()
+            .updateStock(param)
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    /**
+     * 查询使用记录
+     */
+    fun queryRecordList(param: OperRecordReq): Observable<CommonListResponse<AioUserecordVo>> {
+        return mClientFactory.createLabClient()
+            .queryRecordList(param)
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    /**
+     * 查询报警记录列表
+     */
+    fun queryAlarmRecordList(param: AlarmRecordReq): Observable<CommonListResponse<AioAlarmRecordVo>> {
+        return mClientFactory.createLabClient()
+            .queryAlarmRecordList(param)
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    /**
+     * 查询报警记录详情
+     */
+    fun queryAlarmRecordDetail(id: String): Observable<AioAlarmRecordVo> {
+        return mClientFactory.createLabClient()
+            .queryAlarmRecordDetail(id)
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    /**
+     * 检测类型报警记录手动处理
+     */
+    fun updateAlarmRecord(param: UpdateAlarmRecordReq): Observable<Boolean> {
+        return mClientFactory.createLabClient()
+            .updateAlarmRecord(param)
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    /**
+     * 判断当前登录人是否管理员
+     */
+    fun authValidation(): Observable<Boolean> {
+        return mClientFactory.createLabClient()
+            .authValidation()
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    /**
+     * 终端登录查询实验室信息
+     */
+    fun querySubInfo(): Observable<SubInfoVo> {
+        return mClientFactory.createLabClient()
+            .querySubInfo()
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    /**
+     * 人脸身份验证
+     */
+    fun faceAuth(param: FaceBean): Observable<UserValidationVo> {
+        return mClientFactory.createLabClient()
+            .faceAuth(param)
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    /**
+     * 查询机柜柜锁列表
+     */
+    fun queryCabinetLockList(param: CabinetLockVo): Observable<List<CabinetLockVo>> {
+        return mClientFactory.createLabClient()
+            .queryCabinetLockList(param)
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    /**
+     * 柜锁开锁
+     */
+    fun openLock(param: CabinetLockVo): Observable<LockRespBean> {
+        return mClientFactory.createLabClient()
+            .openLock(param)
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    /**
+     * 柜锁关锁
+     */
+    fun closeLock(param: CabinetLockVo): Observable<LockRespBean> {
+        return mClientFactory.createLabClient()
+            .closeLock(param)
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    /**
+     * 上传文件
+     */
+    fun fileUpload(file: File): Observable<FileRespBean> {
+        return mClientFactory.createLabClient()
+            .fileUpload(file)
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+
+
+}

+ 172 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/client/ChemicalClient.kt

@@ -0,0 +1,172 @@
+package com.rc.httpcore.client
+
+import com.rc.httpcore.vo.CommonDataResponse
+import com.rc.httpcore.vo.CommonListResponse
+import com.rc.httpcore.vo.Optional
+import com.rc.httpcore.vo.request.*
+import com.rc.httpcore.vo.response.*
+import io.reactivex.Observable
+import java.io.File
+
+interface ChemicalClient {
+
+    /**
+     * 查询LOGO配置列表
+     */
+    fun queryLogoConfInfo(): Observable<ConfLogo>
+
+    /**
+     * 查询APK版本
+     *
+     * @param id 设备唯一编码
+     */
+    fun apkVersion(id: String): Observable<ApkInfoResp>
+
+    /**
+     * 上传APK更新状态
+     *
+     * @param state 0:升级失败; 1:升级成功; 2:升级中
+     */
+    fun onepcApkUpdate(id: String, state: String): Observable<Boolean>
+
+    /**
+     * 学习一体机 用户端登录
+     *
+     * @param param 刷学生卡数据 userName
+     */
+    fun learnLogin(param: LearnLoginReq): Observable<LearnLoginVo>
+
+    /**
+     * 退出登录
+     */
+    fun loginOut(): Observable<Boolean>
+
+    /**
+     * 人脸比对
+     */
+    fun faceCompare(file: File,param: FaceCompareReq): Observable<Boolean>
+
+
+    fun getFaceFeature(file: File): Observable<String>
+
+    /**
+     * 查询实验室机柜列表
+     */
+    fun queryCabinetList(): Observable<List<CabinetBean>>
+
+    /**
+     * 查询化学品
+     */
+    fun queryChemicalList(param: ChemicalReq): Observable<CommonListResponse<ChemicalBean>>
+
+    /**
+     * 查询库存化学品
+     */
+    fun queryChemicalByStock(param: ChemicalReq): Observable<CommonListResponse<ChemicalBean>>
+
+    /**
+     * 双人验证 刷卡识别身份权限
+     */
+    fun userValidation(param: UserValidationVo): Observable<UserValidationVo>
+
+    /**
+     * 化学品入库
+     */
+    fun addStock(param: ChemicalBean): Observable<ChemicalBean>
+
+    /**
+     * 获取危化品安全技术说明书详细信息
+     */
+    fun hazardBookDetail(id: String): Observable<HazardBook>
+
+    /**
+     * 通过识别卡号或者二维码扫码查询已入库化学品
+     */
+    fun queryByTagCode(param: TagCodeReq): Observable<Optional<ChemicalBean>>
+
+    /**
+     * 领用化学品
+     */
+    fun addUserecord(param: UseReq): Observable<Boolean>
+
+    /**
+     * 归还时查询已领用化学品
+     */
+    fun queryByUserecord(param: TagCodeReq): Observable<ChemicalBean>
+
+    /**
+     * 归还化学品
+     */
+    fun updateUserecord(param: RevertReq): Observable<Boolean>
+
+    /**
+     * 化学品出库
+     */
+    fun outStock(param: OutputStockReq): Observable<Boolean>
+
+    /**
+     * RFID 标签更换
+     */
+    fun updateStock(param: UpdateRfidReq): Observable<Boolean>
+
+    /**
+     * 查询使用记录
+     */
+    fun queryRecordList(param: OperRecordReq): Observable<CommonListResponse<AioUserecordVo>>
+
+    /**
+     * 查询报警记录列表
+     */
+    fun queryAlarmRecordList(param: AlarmRecordReq): Observable<CommonListResponse<AioAlarmRecordVo>>
+
+    /**
+     * 查询报警记录详情
+     */
+    fun queryAlarmRecordDetail(id: String): Observable<AioAlarmRecordVo>
+
+    /**
+     * 检测类型报警记录手动处理
+     */
+    fun updateAlarmRecord(param: UpdateAlarmRecordReq): Observable<Boolean>
+
+    /**
+     * 判断当前登录人是否管理员
+     */
+    fun authValidation(): Observable<Boolean>
+
+    /**
+     * 终端登录查询实验室信息
+     */
+    fun querySubInfo(): Observable<SubInfoVo>
+
+    /**
+     * 人脸身份验证
+     */
+    fun faceAuth(param: FaceBean): Observable<UserValidationVo>
+
+    /**
+     * 查询机柜柜锁列表
+     */
+    fun queryCabinetLockList(param: CabinetLockVo): Observable<List<CabinetLockVo>>
+
+    /**
+     * 柜锁开锁
+     */
+    fun openLock(param: CabinetLockVo): Observable<LockRespBean>
+
+    /**
+     * 柜锁关锁
+     */
+    fun closeLock(param: CabinetLockVo): Observable<LockRespBean>
+
+    /**
+     * 上传文件
+     */
+    fun fileUpload(file: File): Observable<FileRespBean>
+
+
+
+
+
+
+}

+ 9 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/client/factory/ClientFactory.kt

@@ -0,0 +1,9 @@
+package com.rc.httpcore.client.factory
+
+import com.rc.httpcore.client.ChemicalClient
+
+interface ClientFactory {
+
+    fun createLabClient(): ChemicalClient
+
+}

+ 9 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/client/factory/RetrofitFactory.kt

@@ -0,0 +1,9 @@
+package com.rc.httpcore.client.factory
+
+import com.rc.httpcore.client.retrofit.ChemicalRetrofit
+
+class RetrofitFactory : ClientFactory {
+
+    override fun createLabClient() = ChemicalRetrofit()
+
+}

+ 247 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/client/retrofit/ApiService.java

@@ -0,0 +1,247 @@
+package com.rc.httpcore.client.retrofit;
+
+import com.rc.httpcore.vo.CommonDataResponse;
+import com.rc.httpcore.vo.CommonListResponse;
+import com.rc.httpcore.vo.CommonResponse;
+import com.rc.httpcore.vo.request.AlarmRecordReq;
+import com.rc.httpcore.vo.request.ChemicalReq;
+import com.rc.httpcore.vo.request.FaceCompareReq;
+import com.rc.httpcore.vo.request.LearnLoginReq;
+import com.rc.httpcore.vo.request.OperRecordReq;
+import com.rc.httpcore.vo.request.OutputStockReq;
+import com.rc.httpcore.vo.request.RevertReq;
+import com.rc.httpcore.vo.request.TagCodeReq;
+import com.rc.httpcore.vo.request.UpdateAlarmRecordReq;
+import com.rc.httpcore.vo.request.UpdateRfidReq;
+import com.rc.httpcore.vo.request.UseReq;
+import com.rc.httpcore.vo.request.UserValidationVo;
+import com.rc.httpcore.vo.response.AioAlarmRecordVo;
+import com.rc.httpcore.vo.response.AioUserecordVo;
+import com.rc.httpcore.vo.response.ApkInfoResp;
+import com.rc.httpcore.vo.response.CabinetBean;
+import com.rc.httpcore.vo.response.CabinetLockVo;
+import com.rc.httpcore.vo.response.ChemicalBean;
+import com.rc.httpcore.vo.response.ConfLogo;
+import com.rc.httpcore.vo.response.FaceBean;
+import com.rc.httpcore.vo.response.FileRespBean;
+import com.rc.httpcore.vo.response.HazardBook;
+import com.rc.httpcore.vo.response.LearnLoginVo;
+import com.rc.httpcore.vo.response.LockRespBean;
+import com.rc.httpcore.vo.response.SubInfoVo;
+
+import java.util.List;
+import java.util.Map;
+
+import io.reactivex.Observable;
+import okhttp3.MultipartBody;
+import okhttp3.RequestBody;
+import retrofit2.http.Body;
+import retrofit2.http.GET;
+import retrofit2.http.Headers;
+import retrofit2.http.Multipart;
+import retrofit2.http.POST;
+import retrofit2.http.PUT;
+import retrofit2.http.Part;
+import retrofit2.http.PartMap;
+import retrofit2.http.Path;
+import retrofit2.http.Query;
+import retrofit2.http.Url;
+
+public interface ApiService {
+
+    /**
+     * 查询LOGO配置列表
+     */
+    @GET("system/logo/config/getConfInfo")
+    Observable<CommonDataResponse<ConfLogo>> queryLogoConfInfo();
+
+    /**
+     * 查询APK版本
+     */
+    @GET("laboratory/apkfile/onepcApkDetail/{id}")
+    Observable<CommonDataResponse<ApkInfoResp>> apkVersion(@Path("id") String id);
+
+    /**
+     * 上传APK更新状态
+     *
+     * @param state 0:升级失败; 1:升级成功; 2:升级中
+     */
+    @PUT("laboratory/apkfile/onepcApkUpdate/{id}/{state}")
+    Observable<CommonResponse> onepcApkUpdate(@Path("id") String id, @Path("state") String state);
+
+    /**
+     * 用户端登录
+     */
+    @POST("auth/learn/login")
+    Observable<CommonDataResponse<LearnLoginVo>> learnLogin(@Body LearnLoginReq param);
+
+    /**
+     * 退出登录
+     */
+    @POST("auth/learn/loginOut")
+    Observable<CommonResponse> loginOut();
+
+    /**
+     * 人脸比对
+     */
+    @POST("{basePath}/faceApi/compare")
+    Observable<CommonDataResponse<String>> faceCompare(@Path("basePath") String basePath, @Body FaceCompareReq param);
+
+    /**
+     * 人脸比对
+     */
+    @Multipart
+    @POST("{basePath}/faceApi/newCompare")
+    Observable<CommonDataResponse<String>> faceNewCompare(@Path("basePath") String basePath, @Part MultipartBody.Part filePart, @PartMap Map<String, RequestBody> files);
+
+    /**
+     * 人脸特征值
+     * @param basePath
+     * @param filePart
+     * @return
+     */
+    @Multipart
+    @POST("{basePath}/faceApi/faceFeature")
+    Observable<CommonDataResponse<String>> getFaceFeature(@Path("basePath") String basePath, @Part MultipartBody.Part filePart);
+
+    /**
+     * 查询实验室机柜列表 ,
+     */
+    @POST("chemical/api/queryCabinetList")
+    Observable<CommonDataResponse<List<CabinetBean>>> queryCabinetList(@Body CabinetBean param);
+
+    /**
+     * 查询化学品
+     */
+    @POST("chemical/api/queryChemicalList")
+    Observable<CommonListResponse<ChemicalBean>> queryChemicalList(@Query("pageNum") int pageNum, @Query("pageSize") int pageSize, @Body ChemicalReq param);
+
+    /**
+     * 查询库存化学品
+     */
+    @POST("chemical/api/queryChemicalByStock")
+    Observable<CommonListResponse<ChemicalBean>> queryChemicalByStock(@Query("pageNum") int pageNum, @Query("pageSize") int pageSize, @Body ChemicalReq param);
+
+    /**
+     * 双人验证 刷卡识别身份权限
+     */
+    @POST("chemical/api/userValidation")
+    Observable<CommonDataResponse<UserValidationVo>> userValidation(@Body UserValidationVo param);
+
+    /**
+     * 化学品入库
+     */
+    @POST("chemical/api/addStock")
+    Observable<CommonDataResponse<ChemicalBean>> addStock(@Body ChemicalBean param);
+
+    /**
+     * 获取危化品安全技术说明书详细信息
+     */
+    @GET("laboratory/hazard_book/{id}")
+    Observable<CommonDataResponse<HazardBook>> hazardBookDetail(@Path("id") String id);
+
+    /**
+     * 通过识别卡号或者二维码扫码查询已入库化学品
+     */
+    @POST("chemical/api/queryByTagCode")
+    Observable<CommonDataResponse<ChemicalBean>> queryByTagCode(@Body TagCodeReq param);
+
+    /**
+     * 领用化学品
+     */
+    @POST("chemical/api/addUserecord")
+    Observable<CommonResponse> addUserecord(@Body UseReq param);
+
+    /**
+     * 归还时查询已领用化学品
+     */
+    @POST("chemical/api/queryByUserecord")
+    Observable<CommonDataResponse<ChemicalBean>> queryByUserecord(@Body TagCodeReq param);
+
+    /**
+     * 归还化学品
+     */
+    @POST("chemical/api/updateUserecord")
+    Observable<CommonResponse> updateUserecord(@Body RevertReq param);
+
+    /**
+     * 化学品出库
+     */
+    @POST("chemical/api/outStock")
+    Observable<CommonResponse> outStock(@Body OutputStockReq param);
+
+    /**
+     * RFID 标签更换
+     */
+    @POST("chemical/api/updateStock")
+    Observable<CommonResponse> updateStock(@Body UpdateRfidReq param);
+
+    /**
+     * 查询使用记录
+     */
+    @POST("chemical/api/queryRecordList")
+    Observable<CommonListResponse<AioUserecordVo>> queryRecordList(@Query("pageNum") int pageNum, @Query("pageSize") int pageSize, @Body OperRecordReq param);
+
+    /**
+     * 查询报警记录列表
+     */
+    @POST("chemical/api/queryAlarmRecordList")
+    Observable<CommonListResponse<AioAlarmRecordVo>> queryAlarmRecordList(@Query("pageNum") int pageNum, @Query("pageSize") int pageSize, @Body AlarmRecordReq param);
+
+    /**
+     * 查询报警记录详情
+     */
+    @GET("chemical/api/queryAlarmRecordDetail")
+    Observable<CommonDataResponse<AioAlarmRecordVo>> queryAlarmRecordDetail(@Query("id") String id);
+
+    /**
+     * 检测类型报警记录手动处理
+     */
+    @POST("chemical/api/updateAlarmRecord")
+    Observable<CommonResponse> updateAlarmRecord(@Body UpdateAlarmRecordReq param);
+
+    /**
+     * 判断当前登录人是否管理员
+     */
+    @POST("chemical/api/authValidation")
+    Observable<CommonDataResponse<Boolean>> authValidation();
+
+    /**
+     * 终端登录查询实验室信息
+     */
+    @POST("chemical/api/querySubInfo")
+    Observable<CommonDataResponse<SubInfoVo>> querySubInfo();
+
+    /**
+     * 人脸身份验证
+     */
+    @POST("chemical/api/faceAuth")
+    Observable<CommonDataResponse<UserValidationVo>> faceAuth(@Body FaceBean param);
+
+    /**
+     * 查询机柜柜锁列表
+     */
+    @POST("chemical/api/queryCabinetLockList")
+    Observable<CommonDataResponse<List<CabinetLockVo>>> queryCabinetLockList(@Body CabinetLockVo param);
+
+    /**
+     * 柜锁开锁
+     */
+    @POST("chemical/api/openLock")
+    Observable<CommonDataResponse<LockRespBean>> openLock(@Body CabinetLockVo param);
+
+    /**
+     * 柜锁关锁
+     */
+    @POST("chemical/api/closeLock")
+    Observable<CommonDataResponse<LockRespBean>> closeLock(@Body CabinetLockVo param);
+
+    /**
+     * 上传文件
+     */
+    @Multipart
+    @POST("{basePath}file/upload")
+    @Headers("Transfer-Encoding: chunked")
+    Observable<CommonDataResponse<FileRespBean>> fileUpload(@Path(value = "basePath", encoded = true) String basePath, @Part MultipartBody.Part filePart);
+
+}

+ 464 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/client/retrofit/ChemicalRetrofit.kt

@@ -0,0 +1,464 @@
+package com.rc.httpcore.client.retrofit
+
+import android.util.Log
+import com.rc.httpcore.HttpClient
+import com.rc.httpcore.HttpConfig
+import com.rc.httpcore.client.ChemicalClient
+import com.rc.httpcore.exception.NetException
+import com.rc.httpcore.vo.CommonDataResponse
+import com.rc.httpcore.vo.CommonListResponse
+import com.rc.httpcore.vo.Optional
+import com.rc.httpcore.vo.request.*
+import com.rc.httpcore.vo.response.*
+import io.reactivex.Observable
+import okhttp3.MediaType
+import okhttp3.MultipartBody
+import okhttp3.RequestBody
+import java.io.File
+
+
+class ChemicalRetrofit : ChemicalClient {
+
+    private val apiService by lazy {
+        HttpClient.createRetrofitApi(ApiService::class.java)
+    }
+
+    /**
+     * 查询LOGO配置列表
+     */
+    override fun queryLogoConfInfo(): Observable<ConfLogo> {
+        return apiService.queryLogoConfInfo()
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+
+                return@map response.data
+            }
+    }
+
+    /**
+     * 查询APK版本
+     *
+     * @param id 设备唯一编码
+     */
+    override fun apkVersion(id: String): Observable<ApkInfoResp> {
+        return apiService.apkVersion(id)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+                return@map response.data
+            }
+    }
+
+    /**
+     * 上传APK更新状态
+     *
+     * @param state 0:升级失败; 1:升级成功; 2:升级中
+     */
+    override fun onepcApkUpdate(id: String, state: String): Observable<Boolean> {
+        return apiService.onepcApkUpdate(id, state)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+                return@map true
+            }
+    }
+
+    /**
+     * 用户端登录
+     *
+     * @param param 刷学生卡数据 userName
+     */
+    override fun learnLogin(param: LearnLoginReq): Observable<LearnLoginVo> {
+        Log.d("param:===========", "" + param.machineCode)
+        Log.d("param:===========", "" + param.userName)
+        Log.d("param:===========", "" + param.type)
+        Log.d("param:===========", "" + param.aioType)
+        return apiService.learnLogin(param)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+
+                HttpClient.token = response.data?.access_token
+                return@map response.data
+            }
+    }
+
+    /**
+     * 退出登录
+     */
+    override fun loginOut(): Observable<Boolean> {
+        return apiService.loginOut()
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+
+                return@map true
+            }
+    }
+
+    /**
+     * 人脸比对
+     */
+    override fun faceCompare(file: File, param: FaceCompareReq): Observable<Boolean> {
+        val fileBody = RequestBody.create(MediaType.parse("multipart/form-data"), file)
+        val filePart = MultipartBody.Part.createFormData("file", file.name, fileBody)
+        val map = HashMap<String, RequestBody>()
+        map["userId"] = RequestBody.create(MediaType.parse("text/plain"), param.userId)
+        return apiService.faceNewCompare(HttpConfig.BASE_PATH_FACE, filePart, map)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+
+                return@map true
+            }
+    }
+
+    override fun getFaceFeature(file: File): Observable<String> {
+        val fileBody = RequestBody.create(MediaType.parse("multipart/form-data"), file)
+        val filePart = MultipartBody.Part.createFormData("file", file.name, fileBody)
+        return apiService.getFaceFeature(HttpConfig.BASE_PATH_FACE, filePart).map { response ->
+            if (!response.isSuccess()) {
+                throw NetException(response.code, response.msg)
+            }
+
+            return@map response.data
+        }
+
+    }
+
+    /**
+     * 查询实验室机柜列表
+     */
+    override fun queryCabinetList(): Observable<List<CabinetBean>> {
+        return apiService.queryCabinetList(CabinetBean())
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+
+                return@map response.data
+            }
+    }
+
+    /**
+     * 查询化学品
+     */
+    override fun queryChemicalList(param: ChemicalReq): Observable<CommonListResponse<ChemicalBean>> {
+        return apiService.queryChemicalList(param.pageNum, param.pageSize, param)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+
+                return@map response
+            }
+    }
+
+    /**
+     * 查询库存化学品
+     */
+    override fun queryChemicalByStock(param: ChemicalReq): Observable<CommonListResponse<ChemicalBean>> {
+        return apiService.queryChemicalByStock(param.pageNum, param.pageSize, param)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+
+                return@map response
+            }
+    }
+
+    /**
+     * 双人验证 刷卡识别身份权限
+     */
+    override fun userValidation(param: UserValidationVo): Observable<UserValidationVo> {
+        return apiService.userValidation(param)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+
+                return@map response.data
+            }
+    }
+
+    /**
+     *
+     */
+    override fun addStock(param: ChemicalBean): Observable<ChemicalBean> {
+        return apiService.addStock(param)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+                return@map response.data
+            }
+    }
+
+    /**
+     * 获取危化品安全技术说明书详细信息
+     */
+    override fun hazardBookDetail(id: String): Observable<HazardBook> {
+        return apiService.hazardBookDetail(id)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+
+                return@map response.data
+            }
+    }
+
+    /**
+     * 通过识别卡号或者二维码扫码查询已入库化学品
+     */
+    override fun queryByTagCode(param: TagCodeReq): Observable<Optional<ChemicalBean>> {
+
+        return apiService.queryByTagCode(param)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+
+                return@map Optional.ofNullable(response.data)
+            }
+    }
+
+    /**
+     * 领用化学品
+     */
+    override fun addUserecord(param: UseReq): Observable<Boolean> {
+        return apiService.addUserecord(param)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+
+                return@map true
+            }
+    }
+
+    /**
+     * 归还时查询已领用化学品
+     */
+    override fun queryByUserecord(param: TagCodeReq): Observable<ChemicalBean> {
+        return apiService.queryByUserecord(param)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+
+                return@map response.data
+            }
+    }
+
+    /**
+     * 归还化学品
+     */
+    override fun updateUserecord(param: RevertReq): Observable<Boolean> {
+        return apiService.updateUserecord(param)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+
+                return@map true
+            }
+    }
+
+    /**
+     * 化学品出库
+     */
+    override fun outStock(param: OutputStockReq): Observable<Boolean> {
+        return apiService.outStock(param)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+
+                return@map true
+            }
+    }
+
+    /**
+     * RFID 标签更换
+     */
+    override fun updateStock(param: UpdateRfidReq): Observable<Boolean> {
+        return apiService.updateStock(param)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+
+                return@map true
+            }
+    }
+
+    /**
+     * 查询使用记录
+     */
+    override fun queryRecordList(param: OperRecordReq): Observable<CommonListResponse<AioUserecordVo>> {
+        return apiService.queryRecordList(param.pageNum, param.pageSize, param)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+
+                return@map response
+            }
+    }
+
+    /**
+     * 查询报警记录列表
+     */
+    override fun queryAlarmRecordList(param: AlarmRecordReq): Observable<CommonListResponse<AioAlarmRecordVo>> {
+        return apiService.queryAlarmRecordList(param.pageNum, param.pageSize, param)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+
+                return@map response
+            }
+    }
+
+    /**
+     * 查询报警记录详情
+     */
+    override fun queryAlarmRecordDetail(id: String): Observable<AioAlarmRecordVo> {
+        return apiService.queryAlarmRecordDetail(id)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+
+                return@map response.data
+            }
+    }
+
+    /**
+     * 检测类型报警记录手动处理
+     */
+    override fun updateAlarmRecord(param: UpdateAlarmRecordReq): Observable<Boolean> {
+        return apiService.updateAlarmRecord(param)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+
+                return@map true
+            }
+    }
+
+    /**
+     * 判断当前登录人是否管理员
+     */
+    override fun authValidation(): Observable<Boolean> {
+        return apiService.authValidation()
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+
+                return@map response.data
+            }
+    }
+
+    /**
+     * 终端登录查询实验室信息
+     */
+    override fun querySubInfo(): Observable<SubInfoVo> {
+        return apiService.querySubInfo()
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+
+                return@map response.data
+            }
+    }
+
+    /**
+     * 人脸身份验证
+     */
+    override fun faceAuth(param: FaceBean): Observable<UserValidationVo> {
+        return apiService.faceAuth(param)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+
+                return@map response.data
+            }
+    }
+
+    /**
+     * 查询机柜柜锁列表
+     */
+    override fun queryCabinetLockList(param: CabinetLockVo): Observable<List<CabinetLockVo>> {
+        return apiService.queryCabinetLockList(param)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+
+                return@map response.data
+            }
+    }
+
+    /**
+     * 柜锁开锁
+     */
+    override fun openLock(param: CabinetLockVo): Observable<LockRespBean> {
+        return apiService.openLock(param)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+                Log.d("开锁============", "response:$response")
+                if (response.msg != null) {
+                    response.data!!.msg = response.msg
+                }
+                return@map response.data
+            }
+    }
+
+    /**
+     * 柜锁关锁
+     */
+    override fun closeLock(param: CabinetLockVo): Observable<LockRespBean> {
+        return apiService.closeLock(param)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+
+                return@map response.data
+            }
+    }
+
+    /**
+     * 上传文件
+     */
+    override fun fileUpload(file: File): Observable<FileRespBean> {
+        val fileBody = RequestBody.create(MediaType.parse("application/octet-stream"), file)
+        val filePart = MultipartBody.Part.createFormData("file", file.name, fileBody)
+        return apiService.fileUpload(HttpConfig.BASE_PATH_BASE, filePart)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+
+                return@map response.data
+            }
+    }
+
+
+}

+ 25 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/config/ConfigCore.kt

@@ -0,0 +1,25 @@
+package com.rc.httpcore.config
+
+import com.rc.httpcore.HttpConfig
+
+object ConfigCore {
+
+    fun initConfig(config: ConfigParam?, baseUrlSp: String?, apiVersion: Int) {
+        if (null != config) {
+            HttpConfig.API_BASE_URL = if (baseUrlSp.isNullOrEmpty()) config.baseUrl else baseUrlSp
+            HttpConfig.HTTP_STRATEGY = config.httpStrategy
+
+            when (apiVersion) {
+                HttpConfig.ApiVersion.V1.code -> {
+                    HttpConfig.BASE_PATH_FACE = HttpConfig.BasePathV1.FACE
+                    HttpConfig.BASE_PATH_BASE = HttpConfig.BasePathV1.BASE
+                }
+                HttpConfig.ApiVersion.V2.code -> {
+                    HttpConfig.BASE_PATH_FACE = HttpConfig.BasePathV2.ALGORITHM
+                    HttpConfig.BASE_PATH_BASE = HttpConfig.BasePathV2.BASE
+                }
+            }
+        }
+    }
+
+}

+ 30 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/config/ConfigFactory.kt

@@ -0,0 +1,30 @@
+package com.rc.httpcore.config
+
+import com.rc.httpcore.HttpConfig
+
+class ConfigFactory {
+
+    companion object {
+        val buildDebugConfig = ConfigParam.Builder()
+            //.baseUrl("https://lab.sxitdlc.com/appTest/")
+//            .baseUrl("http://192.168.1.43/labSystem/")
+            .baseUrl("http://192.168.1.43/labSystem/")
+//            .fileBrowserBaseUrl("http://180.76.134.43:31007/")
+            .httpStrategy(HttpConfig.HTTP_STRATEGY_Retrofit)
+            //.mqttServerUri("tcp://180.76.134.43:1883")
+            .mqttServerUri("tcp://192.168.1.43:1883")
+            .mqttUName("mqtt")
+            .mqttUPwd("mqtt@zd1883")
+            .build()
+
+        val buildReleaseConfig = ConfigParam.Builder()
+            .baseUrl("https://lab.sxitdlc.com/labSystem/")
+            .fileBrowserBaseUrl("http://180.76.134.43:31007/")
+            .httpStrategy(HttpConfig.HTTP_STRATEGY_Retrofit)
+            .mqttServerUri("tcp://180.76.134.43:1883")
+            .mqttUName("mqtt")
+            .mqttUPwd("mqtt@zd1883")
+            .build()
+    }
+
+}

+ 68 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/config/ConfigParam.kt

@@ -0,0 +1,68 @@
+package com.rc.httpcore.config
+
+import com.rc.httpcore.HttpConfig
+
+class ConfigParam(builder: Builder) {
+
+    val baseUrl: String = builder.baseUrl
+    val fileBrowserBaseUrl: String = builder.fileBrowserBaseUrl
+
+    val httpStrategy: Int = builder.httpStrategy
+
+    val mqttServerUri: String = builder.mqttServerUri
+    val mqttUName: String = builder.mqttUName
+    val mqttUPwd: String = builder.mqttUPwd
+
+    class Builder {
+        internal var baseUrl: String
+        internal var fileBrowserBaseUrl: String
+        internal var httpStrategy: Int
+        internal var mqttServerUri: String
+        internal var mqttUName: String
+        internal var mqttUPwd: String
+
+        init {
+            baseUrl = "http://pc44sory.xiaomy.net:31738/"
+            fileBrowserBaseUrl = "http://180.76.134.43:31007/"
+            httpStrategy = HttpConfig.HTTP_STRATEGY_Retrofit
+            mqttServerUri = "tcp://42.193.12.99:1883"
+            mqttUName = "dlc"
+            mqttUPwd = "123456"
+        }
+
+        fun baseUrl(baseUrl: String): Builder {
+            this.baseUrl = baseUrl
+            return this
+        }
+
+        fun fileBrowserBaseUrl(fileBrowserBaseUrl: String): Builder {
+            this.fileBrowserBaseUrl = fileBrowserBaseUrl
+            return this
+        }
+
+        fun httpStrategy(httpStrategy: Int): Builder {
+            this.httpStrategy = httpStrategy
+            return this
+        }
+
+        fun mqttServerUri(mqttServerUri: String): Builder {
+            this.mqttServerUri = mqttServerUri
+            return this
+        }
+
+        fun mqttUName(mqttUName: String): Builder {
+            this.mqttUName = mqttUName
+            return this
+        }
+
+        fun mqttUPwd(mqttUPwd: String): Builder {
+            this.mqttUPwd = mqttUPwd
+            return this
+        }
+
+        fun build(): ConfigParam {
+            return ConfigParam(this)
+        }
+
+    }
+}

+ 30 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/config/OkHttpDNS.kt

@@ -0,0 +1,30 @@
+package com.rc.httpcore.config
+
+import android.text.TextUtils
+import android.util.Log
+import okhttp3.Dns
+import java.net.InetAddress
+import java.net.UnknownHostException
+
+
+class OkHttpDNS : Dns {
+    private var mIpString: String? = null
+
+    @Throws(UnknownHostException::class)
+    override fun lookup(hostname: String): List<InetAddress> {
+        Log.d("OkHttpDNS", "hostname----:$hostname")
+        if (TextUtils.isEmpty(hostname) || null == hostname) return Dns.SYSTEM.lookup(hostname)
+
+        //根据域名获取IP
+        val ips = InetAddress.getAllByName(hostname)
+        for (inetAddress in ips) {
+            mIpString = inetAddress.hostAddress
+            Log.d("OkHttpDNS", "mIpString----:$mIpString")
+        }
+        return if (null != mIpString && !TextUtils.isEmpty(mIpString)) {
+            listOf(*InetAddress.getAllByName(mIpString))
+        } else {
+            Dns.SYSTEM.lookup(hostname)
+        }
+    }
+}

+ 22 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/converter/NullOnEmptyConverterFactory.kt

@@ -0,0 +1,22 @@
+package com.rc.httpcore.converter
+
+import okhttp3.ResponseBody
+import retrofit2.Converter
+import retrofit2.Retrofit
+import java.lang.reflect.Type
+
+class NullOnEmptyConverterFactory : Converter.Factory() {
+
+    override fun responseBodyConverter(
+        type: Type?,
+        annotations: Array<out Annotation>?,
+        retrofit: Retrofit?
+    ): Converter<ResponseBody, *> {
+        val delegate: Converter<ResponseBody, Any>? =
+            retrofit?.nextResponseBodyConverter(this, type, annotations)
+
+        return Converter<ResponseBody, Any>() {
+            if (it.contentLength() == 0L) null else delegate?.convert(it)
+        }
+    }
+}

+ 9 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/exception/NetException.kt

@@ -0,0 +1,9 @@
+package com.rc.httpcore.exception
+
+class NetException(val code: String, message: String?): Exception(message) {
+
+    override fun toString(): String {
+        val message = super.getLocalizedMessage() ?: ""
+        return "$message (${code})"
+    }
+}

+ 43 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/interceptor/HttpLoggingInterceptorLog.kt

@@ -0,0 +1,43 @@
+package com.rc.httpcore.interceptor
+
+import android.content.ContentValues.TAG
+import android.util.Log
+import okhttp3.FormBody
+import okhttp3.Interceptor
+import okhttp3.Response
+import okhttp3.ResponseBody
+
+class HttpLoggingInterceptorLog : Interceptor {
+    override fun intercept(chain: Interceptor.Chain): Response {
+        val request = chain.request()
+        val startTime = System.currentTimeMillis()
+        val response = chain.proceed(chain.request())
+        val endTime = System.currentTimeMillis()
+        val duration = endTime - startTime
+        val mediaType = response.body()!!.contentType()
+        val content = response.body()!!.string()
+        Log.d(TAG, "\n")
+        Log.d(TAG, "----------Start----------------")
+        Log.d(TAG, "| $request")
+        val method = request.method()
+        if ("POST" == method) {
+            val sb = StringBuilder()
+            if (request.body() is FormBody) {
+                val body = request.body() as FormBody?
+                for (i in 0 until body!!.size()) {
+                    sb.append(body!!.encodedName(i) + "=" + body!!.encodedValue(i) + ",")
+                }
+                sb.delete(sb.length - 1, sb.length)
+                Log.d(TAG, "| RequestParams:{$sb}")
+            }
+        }
+        Log.d(TAG, "| Response:$content")
+        Log.d(TAG, "----------End:" + duration + "毫秒----------")
+        return response.newBuilder()
+            .body(ResponseBody.create(mediaType, content))
+            .build()
+    }
+
+
+
+}

+ 62 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/interceptor/TokenHeaderInterceptor.kt

@@ -0,0 +1,62 @@
+package com.rc.httpcore.interceptor
+
+import android.annotation.SuppressLint
+import android.content.Intent
+import android.util.Log
+import androidx.localbroadcastmanager.content.LocalBroadcastManager
+import com.google.gson.Gson
+import com.rc.httpcore.HttpClient
+import com.rc.httpcore.vo.CommonResponse
+import okhttp3.Interceptor
+import okhttp3.Request
+import okhttp3.Response
+import java.lang.Exception
+import java.nio.charset.StandardCharsets
+
+class TokenHeaderInterceptor : Interceptor {
+
+    @SuppressLint("LongLogTag")
+    override fun intercept(chain: Interceptor.Chain): Response {
+        val originalRequest: Request = chain.request()
+        val requestBuilder: Request.Builder = originalRequest.newBuilder()
+
+        if (!HttpClient.token.isNullOrEmpty()) {
+            requestBuilder.addHeader("authorization", HttpClient.token!!)
+            Log.d("===========",HttpClient.token!!)
+//            requestBuilder.addHeader("authorization", "2cb6ac5a-33e7-404f-a3a7-145fc7420763")
+        }
+
+        requestBuilder
+//            .header("client", "android_app")
+            .header("vName", HttpClient.vName)
+            .header("Accept", "application/json, text/plain,*/*")
+//            .header("Accept", "application/x-www-form-urlencoded")
+            .build()
+
+        val newlyRequest = requestBuilder.build()
+//        if (newlyRequest.url().toString().contains("download")) {
+//            return chain.proceed(newlyRequest);
+//        }
+
+        val response = chain.proceed(newlyRequest)
+        try {
+            response.body()?.let {
+                val source = it.source()
+                source.request(Long.MAX_VALUE)
+                val data = source.buffer.clone().readString(StandardCharsets.UTF_8)
+
+                val commonResponse = Gson().fromJson<CommonResponse>(data, CommonResponse::class.java)
+                if (commonResponse.isTokenExpired()) {
+                    HttpClient.getAppContext()?.apply {
+                        val intent = Intent("com.rc.core.token_expired")
+                        LocalBroadcastManager.getInstance(this).sendBroadcast(intent)
+                    }
+                }
+            }
+        } catch (e: Exception) {
+            e.printStackTrace()
+        }
+
+        return response
+    }
+}

+ 26 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/interceptor/formatter/GsonFormatter.kt

@@ -0,0 +1,26 @@
+package com.rc.httpcore.interceptor.formatter
+
+import com.google.gson.GsonBuilder
+import com.google.gson.JsonParser
+
+class GsonFormatter : JSONFormatter() {
+
+    companion object {
+        fun buildIfSupported(): JSONFormatter? {
+            return try {
+                Class.forName("com.google.gson.Gson")
+                GsonFormatter()
+            } catch (ignore: ClassNotFoundException) {
+                null
+            }
+        }
+    }
+
+    private val GSON = GsonBuilder().setPrettyPrinting().create()
+    private val PARSER = JsonParser()
+
+    override fun format(source: String?): String? {
+        return GSON.toJson(PARSER.parse(source))
+    }
+
+}

+ 37 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/interceptor/formatter/JSONFormatter.kt

@@ -0,0 +1,37 @@
+package com.rc.httpcore.interceptor.formatter
+
+import org.json.JSONException
+
+/**
+ * json格式化
+ */
+open class JSONFormatter {
+
+    companion object {
+
+        private val FORMATTER = findJSONFormatter()
+
+        fun formatJSON(source: String?): String? {
+            return try {
+                FORMATTER.format(source)
+            } catch (e: java.lang.Exception) {
+                ""
+            }
+        }
+
+        private fun findJSONFormatter(): JSONFormatter {
+            val jsonFormatter = OrgJsonFormatter.buildIfSupported()
+            if (jsonFormatter != null) {
+                return jsonFormatter
+            }
+            val gsonFormatter = GsonFormatter.buildIfSupported()
+            return gsonFormatter ?: JSONFormatter()
+        }
+    }
+
+    @Throws(JSONException::class)
+    open fun format(source: String?): String? {
+        return ""
+    }
+
+}

+ 26 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/interceptor/formatter/OrgJsonFormatter.kt

@@ -0,0 +1,26 @@
+package com.rc.httpcore.interceptor.formatter
+
+import org.json.JSONObject
+
+class OrgJsonFormatter : JSONFormatter() {
+
+    companion object {
+
+        private const val INDENT_SPACES = 4
+
+        fun buildIfSupported(): OrgJsonFormatter? {
+            return try {
+                Class.forName("org.json.JSONObject")
+                OrgJsonFormatter()
+            } catch (ignore: ClassNotFoundException) {
+                null
+            }
+        }
+    }
+
+    override fun format(source: String?): String? {
+        return if (source.isNullOrEmpty())
+            "" else JSONObject(source).toString(INDENT_SPACES)
+    }
+
+}

+ 14 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/net/DownloadListener.kt

@@ -0,0 +1,14 @@
+package com.rc.httpcore.net
+
+interface DownloadListener {
+
+    fun onProgress(progress: Int)
+
+    fun onSuccess()
+
+    fun onFailed(errMsg: String?)
+
+    fun onPaused()
+
+    fun onCanceled()
+}

+ 156 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/net/DownloadTask.kt

@@ -0,0 +1,156 @@
+package com.rc.httpcore.net
+
+import android.os.AsyncTask
+import android.os.Environment
+import android.text.TextUtils
+import okhttp3.OkHttpClient
+import okhttp3.Request
+import java.io.File
+import java.io.InputStream
+import java.io.RandomAccessFile
+
+/**
+ * eg: http://www.download.com/projectname/dl/test.apk
+ * 将文件下载到sd卡/Download目录。
+ * 下载到本地的文件件名,如果没指定则为test.apk。
+ *
+ */
+class DownloadTask(
+    private val listener: DownloadListener,
+    private val dlPath: String = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).path,
+    private var fileName: String?,
+    private val headers: Map<String, String>? = null
+) :
+    AsyncTask<String, Int, Int>() {
+
+    companion object {
+        const val TYPE_SUCCESS: Int = 1
+        const val TYPE_FAILED: Int = 2
+        const val TYPE_PAUSED: Int = 3
+        const val TYPE_CANCELED: Int = 4
+    }
+
+    private var isCanceled: Boolean = false
+    private var isPaused: Boolean = false
+
+    private var lastProgress: Int = 0
+
+    override fun doInBackground(vararg params: String?): Int {
+        var file: File? = null
+        var inputStream: InputStream? = null
+        var saveFile: RandomAccessFile? = null
+        try {
+            var downloadedLength = 0L
+            val downloadUrl = params[0]
+            downloadUrl ?: return TYPE_FAILED
+
+            if (TextUtils.isEmpty(fileName)) {
+                fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/"))
+            }
+
+            file = File(dlPath, fileName!!)
+            if (file.exists()) {
+                downloadedLength = file.length()
+            }
+            val contentLength: Long = getContentLength(downloadUrl)
+            if (contentLength == 0L) {
+                return TYPE_FAILED
+            } else if (contentLength == downloadedLength) {
+                // 已经下载的字节数和文件的总字节数相等,说明已经下载完成。
+                return TYPE_SUCCESS
+            }
+            val builder = Request.Builder()
+            headers?.let {
+                for ((key, value) in it)
+                    builder.addHeader(key, value)
+            }
+            val request = builder
+                // 断点下载,指定从那个字节开始下
+                .addHeader("RANGE", "bytes=" + downloadedLength + "-")
+                .url(downloadUrl)
+                .build()
+            val response = OkHttpClient()
+                .newCall(request)
+                .execute()
+
+            inputStream = response.body()!!.byteStream()
+            saveFile = RandomAccessFile(file, "rw")
+            saveFile.seek(downloadedLength) // 跳过已经下载的字节
+            val buff = ByteArray(1024)
+            var total = 0
+            var len = 0;
+            while ({ len = inputStream.read(buff);len }() != -1) {
+                if (isCanceled) {
+                    return TYPE_CANCELED
+                } else if (isPaused) {
+                    return TYPE_PAUSED
+                } else {
+                    total += len
+                    saveFile.write(buff, 0, len)
+                    // 计算已经下载的百分比
+                    val progress: Int = ((total + downloadedLength) * 100 / contentLength).toInt()
+                    publishProgress(progress)
+                }
+            }
+            response.body()!!.close()
+            return TYPE_SUCCESS
+        } catch (e: Exception) {
+            e.printStackTrace()
+        } finally {
+            try {
+                inputStream?.close()
+                saveFile?.close()
+                if (isCanceled) file?.delete()
+            } catch (e: Exception) {
+                e.printStackTrace()
+            }
+        }
+        return TYPE_FAILED
+    }
+
+    override fun onProgressUpdate(vararg values: Int?) {
+        val progress = values[0]
+        progress?.let {
+            if (it > lastProgress) {
+                listener.onProgress(it)
+                lastProgress = it
+            }
+        }
+    }
+
+    override fun onPostExecute(status: Int?) {
+        when (status) {
+            TYPE_SUCCESS -> listener.onSuccess()
+            TYPE_FAILED -> listener.onFailed(null)
+            TYPE_PAUSED -> listener.onPaused()
+            TYPE_CANCELED -> listener.onCanceled()
+            else -> Unit
+        }
+    }
+
+    fun pauseDownload() {
+        this.isPaused = true
+    }
+
+    fun cancelDownload() {
+        this.isCanceled = true
+    }
+
+    private fun getContentLength(downloadUrl: String): Long {
+        val builder = Request.Builder()
+        headers?.let {
+            for ((key, value) in it)
+                builder.addHeader(key, value)
+        }
+        val request = builder.url(downloadUrl).build()
+        val response = OkHttpClient()
+            .newCall(request)
+            .execute()
+        if (response.isSuccessful) {
+            val contentLength = response.body()!!.contentLength()
+            response.close()
+            return contentLength
+        }
+        return 0
+    }
+}

+ 140 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/net/ProgressMultipartBody.java

@@ -0,0 +1,140 @@
+package com.rc.httpcore.net;
+
+import androidx.annotation.Nullable;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import okhttp3.Headers;
+import okhttp3.MediaType;
+import okhttp3.MultipartBody;
+import okhttp3.RequestBody;
+import okio.Buffer;
+import okio.BufferedSink;
+import okio.ForwardingSink;
+import okio.Okio;
+import okio.Sink;
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+public class ProgressMultipartBody extends RequestBody {
+
+    private final MultipartBody requestBody;
+    private final ProgressListener progressListener;
+    private final long contentLength;
+
+    private BufferedSink bufferedSink;
+
+    private long bytesWritten = 0L;
+
+    private ProgressMultipartBody(MultipartBody requestBody, ProgressListener progressListener) throws IOException {
+        this.requestBody = requestBody;
+        this.progressListener = progressListener;
+        this.contentLength = requestBody.contentLength();
+    }
+
+    @Override
+    public MediaType contentType() {
+        return requestBody.contentType();
+    }
+
+    @Override
+    public long contentLength() throws IOException {
+        return contentLength;
+    }
+
+    @Override
+    public void writeTo(@NotNull BufferedSink sink) throws IOException {
+        if (null == bufferedSink) {
+            bufferedSink = Okio.buffer(new ProgressBufferSink(sink));
+        }
+
+        requestBody.writeTo(bufferedSink);
+        bufferedSink.flush();
+    }
+
+    class ProgressBufferSink extends ForwardingSink {
+
+        public ProgressBufferSink(Sink delegate) {
+            super(delegate);
+        }
+
+        @Override
+        public void write(Buffer source, long byteCount) throws IOException {
+            super.write(source, byteCount);
+            bytesWritten += byteCount;
+            progressListener.onProgress(bytesWritten, contentLength);
+        }
+    }
+
+    public interface ProgressListener {
+        void onProgress(long currentBytes, long contentLength);
+    }
+
+    public static class Builder {
+        private MediaType type = MultipartBody.MIXED;
+        private final List<MultipartBody.Part> parts = new ArrayList<>();
+        private ProgressListener progressListener;
+
+        public ProgressMultipartBody.Builder setType(MediaType type) {
+            if (type == null) {
+                throw new NullPointerException("type == null");
+            }
+            if (!type.type().equals("multipart")) {
+                throw new IllegalArgumentException("multipart != " + type);
+            }
+            this.type = type;
+            return this;
+        }
+
+        public ProgressMultipartBody.Builder addPart(RequestBody body) {
+            return addPart(MultipartBody.Part.create(body));
+        }
+
+        public ProgressMultipartBody.Builder addPart(@Nullable Headers headers, RequestBody body) {
+            return addPart(MultipartBody.Part.create(headers, body));
+        }
+
+        public ProgressMultipartBody.Builder addFormDataPart(String name, String value) {
+            return addPart(MultipartBody.Part.createFormData(name, value));
+        }
+
+        public ProgressMultipartBody.Builder addFormDataPart(String name, @Nullable String filename, RequestBody body) {
+            return addPart(MultipartBody.Part.createFormData(name, filename, body));
+        }
+
+        public ProgressMultipartBody.Builder addPart(MultipartBody.Part part) {
+            if (part == null) throw new NullPointerException("part == null");
+            parts.add(part);
+            return this;
+        }
+
+        public ProgressMultipartBody.Builder addProgressListener(ProgressListener progressListener) {
+            this.progressListener = progressListener;
+            return this;
+        }
+
+        public ProgressMultipartBody build() throws IOException {
+            if (parts.isEmpty()) {
+                throw new IllegalStateException("Multipart body must have at least one part.");
+            }
+            if (null == progressListener) {
+                throw new IllegalStateException("progress listener can't null.");
+            }
+
+            MultipartBody.Builder builder = new MultipartBody.Builder().setType(type);
+            for (MultipartBody.Part part : parts) {
+                builder.addPart(part);
+            }
+
+            return new ProgressMultipartBody(builder.build(), progressListener);
+        }
+    }
+
+}

+ 121 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/net/UploadTask.kt

@@ -0,0 +1,121 @@
+package com.rc.httpcore.net
+
+import android.os.AsyncTask
+import android.util.Log
+import com.google.gson.Gson
+import com.google.gson.reflect.TypeToken
+import com.rc.httpcore.HttpClient
+import com.rc.httpcore.HttpConfig
+import com.rc.httpcore.exception.NetException
+import com.rc.httpcore.interceptor.TokenHeaderInterceptor
+import com.rc.httpcore.vo.CommonDataResponse
+import com.rc.httpcore.vo.response.FileRespBean
+import io.reactivex.functions.Consumer
+import okhttp3.MultipartBody
+import okhttp3.OkHttpClient
+import okhttp3.Request
+import okhttp3.RequestBody
+import java.io.File
+import java.util.concurrent.TimeUnit
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+class UploadTask(
+    private val onProgress: Consumer<Int>? = null,
+    private val onSuccess: Consumer<FileRespBean>,
+    private val onFailed: Consumer<Throwable?>
+) :
+    AsyncTask<String, Int, UploadTask.UploadResult>() {
+
+    companion object {
+        const val TYPE_SUCCESS: Int = 1
+        const val TYPE_FAILED: Int = 2
+    }
+
+    override fun doInBackground(vararg params: String?): UploadResult {
+        if (params.isNullOrEmpty()) return UploadResult(
+            TYPE_FAILED,
+            throwable = IllegalArgumentException("params is null or empty")
+        )
+
+        try {
+            val file = File(params[0]!!)
+            val fileBody =
+                RequestBody.create(okhttp3.MediaType.parse("application/octet-stream"), file)
+            val filePart = MultipartBody.Part.createFormData("file", file.name, fileBody)
+
+            val requestBody = ProgressMultipartBody.Builder()
+                .setType(MultipartBody.FORM)
+                .addPart(filePart)
+                .addProgressListener { currentBytes, contentLength ->
+                    val progress: Int = (currentBytes * 100 / contentLength).toInt()
+                    Log.i(
+                        "RcLog",
+                        "currentBytes:${currentBytes}, contentLength:${contentLength}, progress=${progress}%"
+                    )
+                    publishProgress(if (progress >= 100) 99 else progress)
+                }
+                .build()
+
+            val request = Request.Builder()
+                .addHeader("Transfer-Encoding", "chunked")
+                .url("${HttpConfig.API_BASE_URL}${HttpConfig.BASE_PATH_BASE}file/upload")
+                .post(requestBody)
+                .build()
+
+            val okHttpClient = OkHttpClient.Builder()
+                .readTimeout(HttpClient.TIMEOUT_DEFAULT, TimeUnit.SECONDS) // 设置读取超时时间
+                .connectTimeout(HttpClient.TIMEOUT_DEFAULT, TimeUnit.SECONDS) // 设置请求超时时间
+                .writeTimeout(HttpClient.TIMEOUT_DEFAULT, TimeUnit.SECONDS) // 设置写入超时时间
+                .callTimeout(5, TimeUnit.MINUTES) // 上传超时时间:5分钟
+                .addNetworkInterceptor(TokenHeaderInterceptor())
+//                .retryOnConnectionFailure(false) // 设置出现错误进行重新连接
+                .build()
+
+            val response = okHttpClient.newCall(request).execute()
+            if (response.isSuccessful) {
+
+                val responseData = response.body()?.string()
+
+                val typeOfT = object : TypeToken<CommonDataResponse<FileRespBean>>() {}.type
+                val result =
+                    Gson().fromJson<CommonDataResponse<FileRespBean>>(responseData, typeOfT)
+                return if (result.isSuccess()) {
+                    publishProgress(100)
+                    UploadResult(TYPE_SUCCESS, data = result.data)
+                } else {
+                    UploadResult(TYPE_FAILED, throwable = NetException(result.code, result.msg))
+                }
+            }
+        } catch (e: Exception) {
+            e.printStackTrace()
+            return UploadResult(TYPE_FAILED, throwable = e)
+        }
+        return UploadResult(TYPE_FAILED, throwable = RuntimeException("上传失败"))
+    }
+
+    override fun onProgressUpdate(vararg values: Int?) {
+        val progress = values[0]
+        progress?.let {
+            onProgress?.accept(it)
+        }
+    }
+
+    override fun onPostExecute(result: UploadResult?) {
+        when (result?.status) {
+            TYPE_SUCCESS -> onSuccess.accept(result.data!!)
+            TYPE_FAILED -> onFailed.accept(result.throwable)
+            else -> Unit
+        }
+    }
+
+    data class UploadResult(
+        val status: Int?,
+        val data: FileRespBean? = null,
+        val throwable: Throwable? = null
+    )
+
+}

+ 5 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/CommonDataResponse.kt

@@ -0,0 +1,5 @@
+package com.rc.httpcore.vo
+
+class CommonDataResponse<T> : CommonResponse() {
+    var data: T? = null
+}

+ 6 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/CommonListResponse.kt

@@ -0,0 +1,6 @@
+package com.rc.httpcore.vo
+
+class CommonListResponse<T> : CommonResponse() {
+    var rows: List<T>? = null
+    var total: Int = 0
+}

+ 17 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/CommonResponse.kt

@@ -0,0 +1,17 @@
+package com.rc.httpcore.vo
+
+open class CommonResponse {
+
+    var code: String = ""
+    var msg: String = ""
+
+    companion object {
+        const val CODE_SUCCESS = "200"
+        const val CODE_TOKEN_EXPIRED = "401"
+    }
+
+    fun isSuccess() = CODE_SUCCESS == code
+
+    fun isTokenExpired() = CODE_TOKEN_EXPIRED == code
+
+}

+ 50 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/Optional.java

@@ -0,0 +1,50 @@
+package com.rc.httpcore.vo;
+
+import java.util.NoSuchElementException;
+import java.util.Objects;
+
+/**
+ * 解决RxJava不能返回null的问题
+ *
+ * @author ReiChin_
+ */
+public class Optional<T> {
+
+    private static final Optional<?> EMPTY = new Optional<>();
+
+    private final T value;
+
+    private Optional() {
+        this.value = null;
+    }
+
+    public static <T> Optional<T> empty() {
+        @SuppressWarnings("unchecked")
+        Optional<T> t = (Optional<T>) EMPTY;
+        return t;
+    }
+
+    private Optional(T value) {
+        this.value = Objects.requireNonNull(value);
+    }
+
+    public static <T> Optional<T> of(T value) {
+        return new Optional<>(value);
+    }
+
+    public static <T> Optional<T> ofNullable(T value) {
+        return value == null ? empty() : of(value);
+    }
+
+    public T get() {
+        if (value == null) {
+            throw new NoSuchElementException("No value present");
+        }
+        return value;
+    }
+
+    public boolean isPresent() {
+        return value != null;
+    }
+
+}

+ 13 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/request/AlarmRecordReq.java

@@ -0,0 +1,13 @@
+package com.rc.httpcore.vo.request;
+
+/**
+ * 查询报警记录列表 request
+ *
+ * @author ReiChin_
+ */
+public class AlarmRecordReq {
+
+    public int pageNum;
+    public int pageSize;
+
+}

+ 20 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/request/AuthFaceReq.java

@@ -0,0 +1,20 @@
+package com.rc.httpcore.vo.request;
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+public class AuthFaceReq {
+
+    public String data; // 人像特征值
+    public String labId; // 实验室ID
+
+    public AuthFaceReq() {
+    }
+
+    public AuthFaceReq(String data, String labId) {
+        this.data = data;
+        this.labId = labId;
+    }
+}

+ 19 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/request/ChemicalReq.java

@@ -0,0 +1,19 @@
+package com.rc.httpcore.vo.request;
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+public class ChemicalReq {
+
+    public String chemicalName; // 化学品名称/keyword
+    public String cabinetId; // 机柜ID
+    public String labelType; // 标签类型(1是RFID,2是二维码) enum TagType
+    public String joinId; // 化学品机柜关联表id
+    public String qType; // 在领用界面查询时候要多传个值 qType = 1 目的是排除掉正在领用中的数据
+
+    public int pageNum;
+    public int pageSize;
+
+}

+ 16 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/request/FaceCompareReq.java

@@ -0,0 +1,16 @@
+package com.rc.httpcore.vo.request;
+
+import java.io.File;
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+public class FaceCompareReq {
+
+    public byte[] data; // 特征码
+//    public File data; // 特征码
+    public String userId;
+
+}

+ 15 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/request/LearnLoginReq.java

@@ -0,0 +1,15 @@
+package com.rc.httpcore.vo.request;
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+public class LearnLoginReq {
+
+    public String userName;
+    public String machineCode; // 设备码
+    public String type; // 登录类型,1 表示第一次调用获取信息,2 人脸验证之后调用 实现真正的登录
+    public int aioType = 4; // 4-化学品终端 3-学习一体机
+
+}

+ 15 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/request/OperRecordReq.java

@@ -0,0 +1,15 @@
+package com.rc.httpcore.vo.request;
+
+/**
+ * 查询使用记录 request
+ *
+ * @author ReiChin_
+ */
+public class OperRecordReq {
+
+    public String type; // 查询类型:1 领用(默认),2 归还
+
+    public int pageNum;
+    public int pageSize;
+
+}

+ 16 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/request/OutputStockReq.java

@@ -0,0 +1,16 @@
+package com.rc.httpcore.vo.request;
+
+/**
+ * 化学品出库 request
+ *
+ * @author ReiChin_
+ */
+public class OutputStockReq {
+
+    public String id; // 库存ID
+    public String status; // 库存状态 2 用结出库,3 作废出库
+    public String outOneUser; // 出库第一验证人
+    public String outTwoUser; // 出库第二验证人
+    public String lockLogId; // 柜锁开关锁记录Id
+
+}

+ 18 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/request/RevertReq.java

@@ -0,0 +1,18 @@
+package com.rc.httpcore.vo.request;
+
+/**
+ * 归还化学品 request
+ *
+ * @author ReiChin_
+ */
+public class RevertReq {
+
+    public String id; // 领用ID
+    public String returnType; // 归还方式(1是称重,2是录入)
+    public String returnStockNum; // 归还时称重重量
+    public String backOneUser; // 归还第一验证人
+    public String backTwoUser; // 归还第二验证人
+    public String returnVideo; // 归还视频
+    public String lockLogId; // 柜锁开关锁记录Id
+
+}

+ 16 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/request/TagCodeReq.java

@@ -0,0 +1,16 @@
+package com.rc.httpcore.vo.request;
+
+/**
+ * 通过识别卡号或者二维码扫码查询已入库化学品 request
+ *
+ * @author ReiChin_
+ */
+public class TagCodeReq {
+
+    public String tagCode; // 标签编号
+    public String rfidCode; // RFID编号
+    public String machineCode; // 设备编号
+    public String id;
+    public String qType; // 查询类型 enum ModuleEnum
+
+}

+ 13 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/request/UpdateAlarmRecordReq.java

@@ -0,0 +1,13 @@
+package com.rc.httpcore.vo.request;
+
+/**
+ * 检测类型报警记录手动处理 request
+ *
+ * @author ReiChin_
+ */
+public class UpdateAlarmRecordReq {
+
+    public String id; // 报警记录ID
+    public String handlingStatus; // 处理状态 1.待处理,2是已处理
+
+}

+ 13 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/request/UpdateRfidReq.java

@@ -0,0 +1,13 @@
+package com.rc.httpcore.vo.request;
+
+/**
+ * RFID 标签更换 request
+ *
+ * @author ReiChin_
+ */
+public class UpdateRfidReq {
+
+    public String id; // 库存ID
+    public String rfidCode; // RFID编号
+
+}

+ 18 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/request/UseReq.java

@@ -0,0 +1,18 @@
+package com.rc.httpcore.vo.request;
+
+/**
+ * 领用化学品 request
+ *
+ * @author ReiChin_
+ */
+public class UseReq {
+
+    public String stockId; // 库存ID
+    public String collectStockNum; // 领用时库存
+    public String outOneUser; // 领用第一验证人
+    public String outTwoUser; // 领用第二验证人
+    public String outType; // 领用方式(1 称重,2 录入)
+    public String outVideo; // 领用视频
+    public String lockLogId; // 柜锁开关锁记录Id
+
+}

+ 18 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/request/UserValidationVo.java

@@ -0,0 +1,18 @@
+package com.rc.httpcore.vo.request;
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+public class UserValidationVo {
+
+    public String cardNum; // 卡号
+    public String nickName; // 用户名称
+    public String phonenumber; // 手机号码
+    public String position; // 职位
+    public String positionName; // 职称名称
+    public String userId; // 用户ID
+    public String subId; // 实验室ID
+
+}

+ 126 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/AioAlarmRecordVo.java

@@ -0,0 +1,126 @@
+package com.rc.httpcore.vo.response;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * 查询报警记录列表 response bean
+ *
+ * @author ReiChin_
+ */
+public class AioAlarmRecordVo implements Parcelable {
+
+    public String alarmContent; // 报警内容
+    public String alarmMode; // 报警方式(1.系统通知,2.声光报警,3.短信通知)
+    public String alarmTime; // 报警时间
+    public String alarmType; // 报警类型(1 自动(过期),2 检测,3 手动)
+    public String deptId; // 实验室所属学院id
+    public String deptName; // 实验室所属学院
+    public String handlingContent; // 处理内容
+    public String handlingStatus; // 处理状态 1.待处理,2.已处理
+    public String handlingTime; // 处理时间
+    public String handlingUserId; // 处理人id
+    public String id; // 报警记录ID
+    public String liableUserIds; // 责任ID
+    public String liableUserNames; // 责任人姓名
+    public String liableUserPhones; // 责任人手机号码
+    public String adminNames; // 责任人 柴云龙 13319297777
+    public String safeUserId;
+    public String safeUserName; // 实验室安全责任人
+    public String safeUserPhones; // 实验室安全责任人手机号码
+    public String safeUserNames; // 实验室安全责任人  高升 18681887295
+    public String subId; // 实验室id
+    public String subName; // 实验室名称
+    public String terminalNum; // 设备编号
+
+    public String decodeAlarmMode() {
+        if ("1".equals(alarmMode)) return "系统通知";
+        if ("2".equals(alarmMode)) return "声光报警";
+        if ("3".equals(alarmMode)) return "短信通知";
+        return "";
+    }
+
+    public String decodeAlarmType() {
+        if ("1".equals(alarmType)) return "自动(过期)";
+        if ("2".equals(alarmType)) return "检测";
+        if ("3".equals(alarmType)) return "手动";
+        return "";
+    }
+
+    public String decodeHandlingStatus() {
+        if ("1".equals(handlingStatus)) return "待处理";
+        if ("2".equals(handlingStatus)) return "已处理";
+        return "";
+    }
+
+    public AioAlarmRecordVo() {
+    }
+
+    protected AioAlarmRecordVo(Parcel in) {
+        alarmContent = in.readString();
+        alarmMode = in.readString();
+        alarmTime = in.readString();
+        alarmType = in.readString();
+        deptId = in.readString();
+        deptName = in.readString();
+        handlingContent = in.readString();
+        handlingStatus = in.readString();
+        handlingTime = in.readString();
+        handlingUserId = in.readString();
+        id = in.readString();
+        liableUserIds = in.readString();
+        liableUserNames = in.readString();
+        liableUserPhones = in.readString();
+        adminNames = in.readString();
+        safeUserId = in.readString();
+        safeUserName = in.readString();
+        safeUserPhones = in.readString();
+        safeUserNames = in.readString();
+        subId = in.readString();
+        subName = in.readString();
+        terminalNum = in.readString();
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(alarmContent);
+        dest.writeString(alarmMode);
+        dest.writeString(alarmTime);
+        dest.writeString(alarmType);
+        dest.writeString(deptId);
+        dest.writeString(deptName);
+        dest.writeString(handlingContent);
+        dest.writeString(handlingStatus);
+        dest.writeString(handlingTime);
+        dest.writeString(handlingUserId);
+        dest.writeString(id);
+        dest.writeString(liableUserIds);
+        dest.writeString(liableUserNames);
+        dest.writeString(liableUserPhones);
+        dest.writeString(adminNames);
+        dest.writeString(safeUserId);
+        dest.writeString(safeUserName);
+        dest.writeString(safeUserPhones);
+        dest.writeString(safeUserNames);
+        dest.writeString(subId);
+        dest.writeString(subName);
+        dest.writeString(terminalNum);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Creator<AioAlarmRecordVo> CREATOR = new Creator<AioAlarmRecordVo>() {
+        @Override
+        public AioAlarmRecordVo createFromParcel(Parcel in) {
+            return new AioAlarmRecordVo(in);
+        }
+
+        @Override
+        public AioAlarmRecordVo[] newArray(int size) {
+            return new AioAlarmRecordVo[size];
+        }
+    };
+}

+ 33 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/AioUserecordVo.java

@@ -0,0 +1,33 @@
+package com.rc.httpcore.vo.response;
+
+/**
+ * 查询使用记录 response bean
+ *
+ * @author ReiChin_
+ */
+public class AioUserecordVo {
+
+    public String cabinetName; // 机柜名称
+    public String chemicalName; // 化学品名称
+    public String chemicalNum; // 化学品编号
+    public String chemicalUnit; // 化学品单位
+    public String collectStockNum; // 领用时剩余库存
+    public String collectTime; // 领用时间
+    public String returnStockNum; // 归还时称重重量
+    public String returnTime; // 归还时间
+    public String subId; // 实验室ID
+    public String type; // 查询类型:1 领用(默认),2 归还
+    public String useStatus; // 领用状态(1是使用中,0是已归还,2是超时未归还)
+    public String useStockNum; // 使用量
+    public String tagCode; // 使用量
+    public String userId; // 用户ID
+    public String userecordId; // 记录ID
+
+    public String decodeUseStatus() {
+        if ("0".equals(useStatus)) return "已归还";
+        if ("1".equals(useStatus)) return "使用中";
+        if ("2".equals(useStatus)) return "超时未归还";
+        return "";
+    }
+
+}

+ 13 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/ApkInfoResp.java

@@ -0,0 +1,13 @@
+package com.rc.httpcore.vo.response;
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+public class ApkInfoResp {
+
+    public String apkFileUpload;
+    public String version;
+
+}

+ 29 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/CabinetBean.java

@@ -0,0 +1,29 @@
+package com.rc.httpcore.vo.response;
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+public class CabinetBean {
+
+    public String cabinetName; // 机柜名称
+    public String cabinetNum; // 机柜编号
+    public String cabinetStatus; // 机柜状态
+    public String createBy; // 创建人
+    public String createTime; // 创建时间
+    public String deptName; // 学院
+    public String id;
+    public String posi; // 机柜位置
+    public String safeUserName; // 负责人
+
+    public static CabinetBean createDefCabinetBean() {
+        CabinetBean bean = new CabinetBean();
+        bean.cabinetName = "全部";
+        bean.id = DEF_CABINET_ID;
+        return bean;
+    }
+
+    public static final String DEF_CABINET_ID = "ALL";
+
+}

+ 79 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/CabinetLockVo.java

@@ -0,0 +1,79 @@
+package com.rc.httpcore.vo.response;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+public class CabinetLockVo implements Parcelable {
+
+    public String cabinetId; // 机柜ID
+    public String id; // ID主键
+    public String ipAddress; // 摄像头IP地址
+    public String joinId; // 库存ID
+    public String lockCode; // 柜锁编号
+    public String lockId; // 柜锁ID
+    public String lockName; // 柜锁名称
+    public String lockType; // 开锁类型: 0 系统,1 入库,2 出库,3 领用,4 归还 enum LockType
+    public String operateTime; // 操作时长(单位分)
+    public String subId; // 实验室ID
+    public String stockId; // 入库ID
+    public String lockLogId; // 记录开锁的ID
+    public String msgs;//返回参数
+
+    public CabinetLockVo() {
+    }
+
+    protected CabinetLockVo(Parcel in) {
+        cabinetId = in.readString();
+        id = in.readString();
+        ipAddress = in.readString();
+        joinId = in.readString();
+        lockCode = in.readString();
+        lockId = in.readString();
+        lockName = in.readString();
+        lockType = in.readString();
+        operateTime = in.readString();
+        subId = in.readString();
+        stockId = in.readString();
+        lockLogId = in.readString();
+        msgs=in.readString();
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(cabinetId);
+        dest.writeString(id);
+        dest.writeString(ipAddress);
+        dest.writeString(joinId);
+        dest.writeString(lockCode);
+        dest.writeString(lockId);
+        dest.writeString(lockName);
+        dest.writeString(lockType);
+        dest.writeString(operateTime);
+        dest.writeString(subId);
+        dest.writeString(stockId);
+        dest.writeString(lockLogId);
+        dest.writeString(msgs);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Creator<CabinetLockVo> CREATOR = new Creator<CabinetLockVo>() {
+        @Override
+        public CabinetLockVo createFromParcel(Parcel in) {
+            return new CabinetLockVo(in);
+        }
+
+        @Override
+        public CabinetLockVo[] newArray(int size) {
+            return new CabinetLockVo[size];
+        }
+    };
+}

+ 196 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/ChemicalBean.java

@@ -0,0 +1,196 @@
+package com.rc.httpcore.vo.response;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+public class ChemicalBean implements Parcelable {
+
+    public String anotherName; // 别名
+    public String cabinetName; // 机柜名称
+    public String cabinetNum; // 机柜编号
+    public String casNum; // cas号
+    public String chemicalClassify; // 化学品分类
+    public String chemicalName; // 化学品名字
+    public String chemicalNum; // [入库用]化学品编号
+    public String joinNum; // [回显用]化学品编号
+    public String chemicalShape; // 化学品形态
+    public String chemicalShapeName; // 化学品形态
+    public String chemicalUnit; // 称重单位,固定值:g
+    public String deptId; // 部门ID
+    public String deptName; // 部门名称
+    public String expirationTime; // [入库用]过期时间
+    public String expireTime; // [回显用]过期时间
+    public String factory; // 生产厂家
+    public String id;
+    public String joinId; // 化学品机柜关联表id
+    public String joinOneUser; // 入库第一验证人
+    public String joinTime; // 入库时间
+    public String joinTwoUser; // 入库第二验证人
+    public String joinType; // 入库方式(1 称重,2 录入)
+    public String joinUserId; // 入库人ID
+    public String labelType; // 标签类型(1是RFID,2是二维码) enum TagType
+    public String measuringMethod; // 计量方式,固定值:1
+    public String outOneUser; // 出库第一验证人
+    public String outTime; // 出库时间
+    public String outTwoUser; // 出库第二验证人
+    public String outUsages; // 剩余库存量
+    public String outUserId; // 出库人ID
+    public String purity; // 纯度
+    public String remark;
+    public String rfidCode; // RFID编号(标签类型是1 时,不能为空!)
+    public String status; // 库存状态
+    public String subId; // 实验室id
+    public String tagCode; // 标签编号
+    public String usages; // 入库重量
+    public String userId;
+    public String verification; // 验证方式(1是单人验证,2是双人双卡)
+    public String safeUserName; // 负责人
+    public String chemicalAmount; // 化学品规格
+    public String chemicalAmountUnit; // 化学品规格单位
+    public String hazardId; // 化学品 MSDS ID
+    public String classifyName; // 化学品分类名称
+    public String classifyAttribute; // 化学品属性名称
+    public String tare; // 容器重量
+    public String printCode; // 是否打印二维码标签(1是,0否)
+    public HxpUserecord hxpUserecord; // 当前领用信息
+    public ClassifyConfig classifyConfig; // 化学品配置表
+    public String suttle; // 库存净重量
+    public String returnStockNum; // 归还时称重重量
+    public String joinVideo; // 入库视频
+
+    public ChemicalBean() {
+    }
+
+    protected ChemicalBean(Parcel in) {
+        anotherName = in.readString();
+        cabinetName = in.readString();
+        cabinetNum = in.readString();
+        casNum = in.readString();
+        chemicalClassify = in.readString();
+        chemicalName = in.readString();
+        chemicalNum = in.readString();
+        joinNum = in.readString();
+        chemicalShape = in.readString();
+        chemicalShapeName = in.readString();
+        chemicalUnit = in.readString();
+        deptId = in.readString();
+        deptName = in.readString();
+        expirationTime = in.readString();
+        expireTime = in.readString();
+        factory = in.readString();
+        id = in.readString();
+        joinId = in.readString();
+        joinOneUser = in.readString();
+        joinTime = in.readString();
+        joinTwoUser = in.readString();
+        joinType = in.readString();
+        joinUserId = in.readString();
+        labelType = in.readString();
+        measuringMethod = in.readString();
+        outOneUser = in.readString();
+        outTime = in.readString();
+        outTwoUser = in.readString();
+        outUsages = in.readString();
+        outUserId = in.readString();
+        purity = in.readString();
+        remark = in.readString();
+        rfidCode = in.readString();
+        status = in.readString();
+        subId = in.readString();
+        tagCode = in.readString();
+        usages = in.readString();
+        userId = in.readString();
+        verification = in.readString();
+        safeUserName = in.readString();
+        chemicalAmount = in.readString();
+        chemicalAmountUnit = in.readString();
+        hazardId = in.readString();
+        classifyName = in.readString();
+        classifyAttribute = in.readString();
+        tare = in.readString();
+        printCode = in.readString();
+        hxpUserecord = in.readParcelable(HxpUserecord.class.getClassLoader());
+        classifyConfig = in.readParcelable(ClassifyConfig.class.getClassLoader());
+        suttle = in.readString();
+        returnStockNum = in.readString();
+        joinVideo = in.readString();
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(anotherName);
+        dest.writeString(cabinetName);
+        dest.writeString(cabinetNum);
+        dest.writeString(casNum);
+        dest.writeString(chemicalClassify);
+        dest.writeString(chemicalName);
+        dest.writeString(chemicalNum);
+        dest.writeString(joinNum);
+        dest.writeString(chemicalShape);
+        dest.writeString(chemicalShapeName);
+        dest.writeString(chemicalUnit);
+        dest.writeString(deptId);
+        dest.writeString(deptName);
+        dest.writeString(expirationTime);
+        dest.writeString(expireTime);
+        dest.writeString(factory);
+        dest.writeString(id);
+        dest.writeString(joinId);
+        dest.writeString(joinOneUser);
+        dest.writeString(joinTime);
+        dest.writeString(joinTwoUser);
+        dest.writeString(joinType);
+        dest.writeString(joinUserId);
+        dest.writeString(labelType);
+        dest.writeString(measuringMethod);
+        dest.writeString(outOneUser);
+        dest.writeString(outTime);
+        dest.writeString(outTwoUser);
+        dest.writeString(outUsages);
+        dest.writeString(outUserId);
+        dest.writeString(purity);
+        dest.writeString(remark);
+        dest.writeString(rfidCode);
+        dest.writeString(status);
+        dest.writeString(subId);
+        dest.writeString(tagCode);
+        dest.writeString(usages);
+        dest.writeString(userId);
+        dest.writeString(verification);
+        dest.writeString(safeUserName);
+        dest.writeString(chemicalAmount);
+        dest.writeString(chemicalAmountUnit);
+        dest.writeString(hazardId);
+        dest.writeString(classifyName);
+        dest.writeString(classifyAttribute);
+        dest.writeString(tare);
+        dest.writeString(printCode);
+        dest.writeParcelable(hxpUserecord, flags);
+        dest.writeParcelable(classifyConfig, flags);
+        dest.writeString(suttle);
+        dest.writeString(returnStockNum);
+        dest.writeString(joinVideo);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Creator<ChemicalBean> CREATOR = new Creator<ChemicalBean>() {
+        @Override
+        public ChemicalBean createFromParcel(Parcel in) {
+            return new ChemicalBean(in);
+        }
+
+        @Override
+        public ChemicalBean[] newArray(int size) {
+            return new ChemicalBean[size];
+        }
+    };
+}

+ 58 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/ClassifyConfig.java

@@ -0,0 +1,58 @@
+package com.rc.httpcore.vo.response;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * = 2 的时候必须双人验证
+ *
+ * @author ReiChin_
+ */
+public class ClassifyConfig implements Parcelable {
+
+    public String id;
+    public String joinClassifyId; // 关联分类Id
+    public String joinStatus; // 化学品入库状态 1-是 2-否
+    public String outStatus; // 化学品出库状态 1-是 2-否
+    public String collectStatus; // 化学品领用状态 1-是 2-否
+    public String returnStatus; // 化学品归还状态 1-是 2-否
+
+    public ClassifyConfig() {
+    }
+
+    protected ClassifyConfig(Parcel in) {
+        id = in.readString();
+        joinClassifyId = in.readString();
+        joinStatus = in.readString();
+        outStatus = in.readString();
+        collectStatus = in.readString();
+        returnStatus = in.readString();
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(id);
+        dest.writeString(joinClassifyId);
+        dest.writeString(joinStatus);
+        dest.writeString(outStatus);
+        dest.writeString(collectStatus);
+        dest.writeString(returnStatus);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Creator<ClassifyConfig> CREATOR = new Creator<ClassifyConfig>() {
+        @Override
+        public ClassifyConfig createFromParcel(Parcel in) {
+            return new ClassifyConfig(in);
+        }
+
+        @Override
+        public ClassifyConfig[] newArray(int size) {
+            return new ClassifyConfig[size];
+        }
+    };
+}

+ 14 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/ConfLogo.java

@@ -0,0 +1,14 @@
+package com.rc.httpcore.vo.response;
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+public class ConfLogo {
+
+    public String schoolName; // 学校名称
+    public String circularLogo; // 圆形logo
+    public String rectangleLogo; // 长方形logo
+
+}

+ 9 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/FaceBean.java

@@ -0,0 +1,9 @@
+package com.rc.httpcore.vo.response;
+
+public class FaceBean {
+    public String adminId; // 负责人
+    public String data; // 特征码
+    public String safeUserId; // 安全责任人
+    public String subId; // 实验室ID
+    public String subName; // 实验室名称
+}

+ 13 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/FileRespBean.java

@@ -0,0 +1,13 @@
+package com.rc.httpcore.vo.response;
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+public class FileRespBean {
+
+    public String name;
+    public String url;
+
+}

+ 36 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/HazardBook.java

@@ -0,0 +1,36 @@
+package com.rc.httpcore.vo.response;
+
+import java.util.List;
+
+/**
+ * 危化品安全技术说明书
+ *
+ * @author ReiChin_
+ */
+public class HazardBook {
+
+    public String code; // 编号
+    public String content; // 内容
+    public String createBy;
+    public String createTime; // 创建时间
+    public String deptId; // 题目ID
+    public String deptName; // 部门名称
+    public String id;
+    public List<String> ids; //批量操作的id集合
+    public String name; // 名称
+    public Object params;
+    public String pinYin; // 名称全拼
+    public String pinYinChar; // 首字母
+    public String qrCodeUrl; // 二维码地址
+    public String remark;
+    public String scanCount; // 查看次数
+    public String searchValue;
+    public String updateBy;
+    public String updateTime; // 更新时间
+    public String userId; // 用户ID(数据权限)
+
+    @Override
+    public String toString() {
+        return this.name;
+    }
+}

+ 73 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/HxpUserecord.java

@@ -0,0 +1,73 @@
+package com.rc.httpcore.vo.response;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+public class HxpUserecord implements Parcelable {
+
+    public String collectStockNum; // 领用时库存
+    public String collectTime; // 领用时间
+    public String deptId; // 部门ID
+    public String id;
+    public String outOneUser; // 领用第一验证人
+    public String outTwoUser; // 领用第二验证人
+    public String returnStockNum; // 归还时称重重量
+    public String returnTime; // 归还时间
+    public String returnType; // 归还方式(1是称重,2是录入)
+    public String stockId; // 库存ID
+    public String useStatus; // 状态
+
+    public HxpUserecord() {
+    }
+
+    protected HxpUserecord(Parcel in) {
+        collectStockNum = in.readString();
+        collectTime = in.readString();
+        deptId = in.readString();
+        id = in.readString();
+        outOneUser = in.readString();
+        outTwoUser = in.readString();
+        returnStockNum = in.readString();
+        returnTime = in.readString();
+        returnType = in.readString();
+        stockId = in.readString();
+        useStatus = in.readString();
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(collectStockNum);
+        dest.writeString(collectTime);
+        dest.writeString(deptId);
+        dest.writeString(id);
+        dest.writeString(outOneUser);
+        dest.writeString(outTwoUser);
+        dest.writeString(returnStockNum);
+        dest.writeString(returnTime);
+        dest.writeString(returnType);
+        dest.writeString(stockId);
+        dest.writeString(useStatus);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Creator<HxpUserecord> CREATOR = new Creator<HxpUserecord>() {
+        @Override
+        public HxpUserecord createFromParcel(Parcel in) {
+            return new HxpUserecord(in);
+        }
+
+        @Override
+        public HxpUserecord[] newArray(int size) {
+            return new HxpUserecord[size];
+        }
+    };
+}

+ 21 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/LearnLoginVo.java

@@ -0,0 +1,21 @@
+package com.rc.httpcore.vo.response;
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+public class LearnLoginVo {
+
+    public String access_token;
+    public String user_id;
+    public String type;
+    public String expires_in;
+    public String username;
+    public String nickName; // 用户姓名
+    public String positionName; // 用户角色
+    public boolean airBottle; // 是否设置气瓶
+    public boolean cabinetLock; // 是否设置柜锁
+    public boolean isManager; // 是否管理员
+
+}

+ 57 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/LockRespBean.java

@@ -0,0 +1,57 @@
+package com.rc.httpcore.vo.response;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+public class LockRespBean implements Parcelable {
+
+    public String lockId; // 柜锁ID
+    public boolean success; // 开锁/关锁成功
+    public String cabinetName; // 机柜名称
+    public String lockName; // 柜锁名称
+    public String lockLogId; // 记录开锁的ID
+    public String msg;//返回参数
+    public LockRespBean() {
+    }
+
+    protected LockRespBean(Parcel in) {
+        lockId = in.readString();
+        success = in.readByte() != 0;
+        cabinetName = in.readString();
+        lockName = in.readString();
+        lockLogId = in.readString();
+        msg = in.readString();
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(lockId);
+        dest.writeByte((byte) (success ? 1 : 0));
+        dest.writeString(cabinetName);
+        dest.writeString(lockName);
+        dest.writeString(lockLogId);
+        dest.writeString(msg);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Creator<LockRespBean> CREATOR = new Creator<LockRespBean>() {
+        @Override
+        public LockRespBean createFromParcel(Parcel in) {
+            return new LockRespBean(in);
+        }
+
+        @Override
+        public LockRespBean[] newArray(int size) {
+            return new LockRespBean[size];
+        }
+    };
+}

+ 67 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/SubInfoVo.java

@@ -0,0 +1,67 @@
+package com.rc.httpcore.vo.response;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+public class SubInfoVo implements Parcelable, Cloneable {
+
+    public String adminId; // 负责人
+    public byte[] data; // 特征码
+    public String safeUserId; // 安全责任人
+    public String subId; // 实验室ID
+    public String subName; // 实验室名称
+
+
+    public SubInfoVo() {
+    }
+
+    protected SubInfoVo(Parcel in) {
+        adminId = in.readString();
+        data = in.createByteArray();
+        safeUserId = in.readString();
+        subId = in.readString();
+        subName = in.readString();
+    }
+
+    @Override
+    public SubInfoVo clone() {
+        SubInfoVo bean = null;
+        try {
+            bean = (SubInfoVo) super.clone();
+        } catch (CloneNotSupportedException e) {
+            e.printStackTrace();
+        }
+        return bean;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(adminId);
+        dest.writeByteArray(data);
+        dest.writeString(safeUserId);
+        dest.writeString(subId);
+        dest.writeString(subName);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Creator<SubInfoVo> CREATOR = new Creator<SubInfoVo>() {
+        @Override
+        public SubInfoVo createFromParcel(Parcel in) {
+            return new SubInfoVo(in);
+        }
+
+        @Override
+        public SubInfoVo[] newArray(int size) {
+            return new SubInfoVo[size];
+        }
+    };
+}

+ 8 - 0
LICENSE

@@ -0,0 +1,8 @@
+MIT License
+Copyright (c) <year> <copyright holders>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ 3 - 0
README.md

@@ -0,0 +1,3 @@
+# lab-android
+
+化学品一体机   长安大学

+ 1 - 0
RcCore/.gitignore

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

+ 52 - 0
RcCore/build.gradle

@@ -0,0 +1,52 @@
+plugins {
+    id 'com.android.library'
+    id 'kotlin-android'
+}
+
+android {
+    compileSdkVersion env.compileSdkVersion
+    buildToolsVersion env.buildToolsVersion
+
+    defaultConfig {
+        minSdkVersion env.minSdkVersion
+        targetSdkVersion env.targetSdkVersion
+        versionCode 1
+        versionName "1.0"
+
+        consumerProguardFiles "consumer-rules.pro"
+    }
+
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+    }
+    compileOptions {
+        sourceCompatibility env.jdk_version
+        targetCompatibility env.jdk_version
+    }
+    kotlinOptions {
+        jvmTarget = '1.8'
+    }
+    buildFeatures{
+        viewBinding = true
+    }
+}
+
+dependencies {
+
+    implementation project(':HttpCoreLibrary')
+
+    api fileTree(dir: "libs", include: ["*.jar"])
+    implementation dep.kotlinStdlib
+    implementation dep.androidxCoreKtx
+    implementation dep.androidxSwipeRefreshLayout
+
+    implementation dep.androidxAppCompat
+    implementation dep.androidMaterial
+    implementation dep.RecyclerViewAdapterHelper
+
+    implementation dep.eventbus
+
+}

+ 0 - 0
RcCore/consumer-rules.pro


BIN
RcCore/libs/tbs_sdk_v4.3.0.165_20210628_103707.jar


+ 21 - 0
RcCore/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

+ 5 - 0
RcCore/src/main/AndroidManifest.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.rc.core">
+
+</manifest>

+ 8 - 0
RcCore/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)

+ 29 - 0
RcCore/src/main/java/com/rc/core/log/RcLog.kt

@@ -0,0 +1,29 @@
+package com.rc.core.log
+
+import java.util.logging.Level
+import java.util.logging.Logger
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+object RcLog {
+
+    private val logger = Logger.getLogger(RcLog::class.java.name)
+    var DE_BUG: Boolean = false
+    fun warn(message: String, throwable: Throwable? = null) {
+        if (!DE_BUG)return
+        printLog(Level.WARNING, message, throwable)
+    }
+
+    fun info(message: String, throwable: Throwable? = null) {
+        if (!DE_BUG)return
+        printLog(Level.INFO, message, throwable)
+    }
+
+    private fun printLog(level: Level, message: String, throwable: Throwable? = null) {
+        logger.log(level, message, throwable)
+    }
+
+}

+ 71 - 0
RcCore/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()
+    }
+
+}

+ 128 - 0
RcCore/src/main/java/com/rc/core/ui/activity/RcBaseActivity.kt

@@ -0,0 +1,128 @@
+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.disposables.Disposable
+
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+abstract class RcBaseActivity<VB : ViewBinding> : AppCompatActivity(), IUIListener {
+
+    private lateinit var _viewBinding: VB
+
+    protected val viewBinding get() = _viewBinding
+
+    private 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 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
+                )
+            }
+        }
+    }
+}

+ 235 - 0
RcCore/src/main/java/com/rc/core/ui/activity/RcRefreshActivity.kt

@@ -0,0 +1,235 @@
+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.R
+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.disposables.Disposable
+import org.greenrobot.eventbus.EventBus
+import org.greenrobot.eventbus.Subscribe
+import org.greenrobot.eventbus.ThreadMode
+
+/**
+ * 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) {
+    }
+
+}

+ 37 - 0
RcCore/src/main/java/com/rc/core/ui/common/AbsUIDelegate.kt

@@ -0,0 +1,37 @@
+package com.rc.core.ui.common
+
+import android.content.Context
+import androidx.fragment.app.FragmentManager
+import androidx.recyclerview.widget.RecyclerView
+import io.reactivex.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 clearDisposable()
+
+    abstract fun createItemDecoration(context: Context?): RecyclerView.ItemDecoration?
+
+}

+ 25 - 0
RcCore/src/main/java/com/rc/core/ui/common/IUIListener.kt

@@ -0,0 +1,25 @@
+package com.rc.core.ui.common
+
+import androidx.recyclerview.widget.RecyclerView
+import io.reactivex.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 createItemDecoration(): RecyclerView.ItemDecoration?
+
+}

+ 97 - 0
RcCore/src/main/java/com/rc/core/ui/common/UIDelegateImpl.kt

@@ -0,0 +1,97 @@
+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 com.rc.core.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.disposables.CompositeDisposable
+import io.reactivex.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 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
RcCore/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 com.rc.core.R
+import com.rc.core.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
RcCore/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 com.rc.core.R
+import com.rc.core.ui.common.AbsUIDelegate
+import com.rc.core.ui.common.IUIListener
+import io.reactivex.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)
+    }
+
+}

+ 84 - 0
RcCore/src/main/java/com/rc/core/ui/fragment/RcBaseFragment.kt

@@ -0,0 +1,84 @@
+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.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 onDestroyView() {
+        mUIDelegate.clearDisposable()
+        super.onDestroyView()
+    }
+
+    override fun createItemDecoration(): RecyclerView.ItemDecoration? {
+        return mUIDelegate.createItemDecoration(context)
+    }
+
+}

+ 37 - 0
RcCore/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
RcCore/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.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(context!!) }
+    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
RcCore/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 com.rc.core.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
RcCore/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
RcCore/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
RcCore/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
RcCore/src/main/java/com/rc/core/util/ApkUpdater.kt

@@ -0,0 +1,61 @@
+package com.rc.core.util
+
+import android.content.Context
+import com.rc.core.log.RcLog
+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) {
+                    RcLog.info("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
RcCore/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
RcCore/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
RcCore/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
+        }
+    }
+
+}

+ 91 - 0
RcCore/src/main/java/com/rc/core/util/DeviceUtils.kt

@@ -0,0 +1,91 @@
+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版本名称(versionName)
+     */
+    fun getVersionName(context: Context): String {
+        return try {
+            // 获取包信息
+            // 参1 包名 参2 获取额外信息的flag 不需要的话 写0
+            context.packageManager.getPackageInfo(
+                context.packageName, 0
+            ).versionName
+        } catch (e: PackageManager.NameNotFoundException) {
+            e.printStackTrace()
+            ""
+        }
+    }
+
+    /**
+     * 获取当前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
RcCore/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
RcCore/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
RcCore/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
RcCore/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
RcCore/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
RcCore/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
RcCore/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
RcCore/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)
+    }
+
+}

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


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