sunqiang преди 1 година
ревизия
e031350f5b
променени са 100 файла, в които са добавени 6598 реда и са изтрити 0 реда
  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. 72 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/HttpClient.kt
  8. 35 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/HttpConfig.kt
  9. 633 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/client/ApiRepository.kt
  10. 323 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/client/LabClient.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. 429 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/client/retrofit/ApiService.java
  14. 822 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/client/retrofit/LabRetrofit.kt
  15. 27 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/config/ConfigCore.kt
  16. 27 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. 22 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/converter/NullOnEmptyConverterFactory.kt
  19. 16 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/exception/AICheckException.kt
  20. 9 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/exception/NetException.kt
  21. 50 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/interceptor/TokenHeaderInterceptor.kt
  22. 26 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/interceptor/formatter/GsonFormatter.kt
  23. 37 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/interceptor/formatter/JSONFormatter.kt
  24. 26 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/interceptor/formatter/OrgJsonFormatter.kt
  25. 14 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/net/DownloadListener.kt
  26. 156 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/net/DownloadTask.kt
  27. 5 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/CommonDataResponse.kt
  28. 17 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/CommonResponse.kt
  29. 11 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/CommonRowsResponse.kt
  30. 16 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/request/AccessTokenReq.java
  31. 20 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/request/AuthFaceReq.java
  32. 15 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/request/AuthPwdReq.java
  33. 16 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/request/CheckInAllReq.java
  34. 14 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/request/CommonSignInReq.java
  35. 14 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/request/ControllerCMD.java
  36. 13 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/request/FaceCompareReq.java
  37. 15 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/request/HazardReq.java
  38. 14 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/request/OnLineUserReq.java
  39. 13 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/request/PatrolSignInReq.java
  40. 13 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/request/PatrolSignOutReq.java
  41. 12 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/request/SignInReq.java
  42. 14 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/AccessTokenResp.java
  43. 13 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/ApkInfoResp.java
  44. 12 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/BannerImageBean.java
  45. 58 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/CheckMachineVo.java
  46. 14 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/ContentMachineVo.java
  47. 20 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/DutyPersonVo.java
  48. 36 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/HazardBook.java
  49. 35 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/HomeMiddleResp.java
  50. 16 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/HomeRightResp.java
  51. 15 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/HomeTopResp.java
  52. 27 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/LabBulletinBoardVo.java
  53. 17 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/LabConfig.java
  54. 18 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/LabHazardVo.java
  55. 26 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/LabPersonVo.java
  56. 76 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/LaboratoryVo.java
  57. 86 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/LotDeviceVo.java
  58. 93 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/MonitorVo.java
  59. 16 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/NoticeDetailVo.java
  60. 15 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/NoticeSummaryVo.java
  61. 33 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/SafeBook.java
  62. 20 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/SafeClassify.java
  63. 51 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/SignFaceVo.java
  64. 154 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/SignInCheckResp.java
  65. 14 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/SpeakInfo.java
  66. 55 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/UserFingerVo.java
  67. 70 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/UserVo.java
  68. 8 0
      LICENSE
  69. 3 0
      README.md
  70. 1 0
      RcCore/.gitignore
  71. 52 0
      RcCore/build.gradle
  72. 0 0
      RcCore/consumer-rules.pro
  73. BIN
      RcCore/libs/tbs_sdk_v4.3.0.165_20210628_103707.jar
  74. 21 0
      RcCore/proguard-rules.pro
  75. 5 0
      RcCore/src/main/AndroidManifest.xml
  76. 8 0
      RcCore/src/main/java/com/rc/core/event/RefreshEvent.kt
  77. 30 0
      RcCore/src/main/java/com/rc/core/log/RcLog.kt
  78. 71 0
      RcCore/src/main/java/com/rc/core/ui/ActivityCollector.kt
  79. 106 0
      RcCore/src/main/java/com/rc/core/ui/activity/RcBaseActivity.kt
  80. 166 0
      RcCore/src/main/java/com/rc/core/ui/activity/RcRefreshActivity.kt
  81. 36 0
      RcCore/src/main/java/com/rc/core/ui/common/AbsUIDelegate.kt
  82. 25 0
      RcCore/src/main/java/com/rc/core/ui/common/IUIListener.kt
  83. 93 0
      RcCore/src/main/java/com/rc/core/ui/common/UIDelegateImpl.kt
  84. 101 0
      RcCore/src/main/java/com/rc/core/ui/dialog/LoadingDialog.kt
  85. 116 0
      RcCore/src/main/java/com/rc/core/ui/dialog/RcBaseDialog.kt
  86. 83 0
      RcCore/src/main/java/com/rc/core/ui/fragment/RcBaseFragment.kt
  87. 37 0
      RcCore/src/main/java/com/rc/core/ui/fragment/RcLazyFragment.kt
  88. 169 0
      RcCore/src/main/java/com/rc/core/ui/fragment/RcRefreshFragment.kt
  89. 301 0
      RcCore/src/main/java/com/rc/core/ui/widget/MultipleStatusView.kt
  90. 33 0
      RcCore/src/main/java/com/rc/core/ui/widget/SwipeViewPager.kt
  91. 115 0
      RcCore/src/main/java/com/rc/core/ui/widget/decoration/GridSpacingItemDecoration.java
  92. 148 0
      RcCore/src/main/java/com/rc/core/ui/widget/decoration/NoLastLineItemDecoration.java
  93. 195 0
      RcCore/src/main/java/com/rc/core/util/ApkController.kt
  94. 61 0
      RcCore/src/main/java/com/rc/core/util/ApkUpdater.kt
  95. 208 0
      RcCore/src/main/java/com/rc/core/util/CrashHandler.kt
  96. 120 0
      RcCore/src/main/java/com/rc/core/util/CrashHelper.kt
  97. 91 0
      RcCore/src/main/java/com/rc/core/util/DeviceUtils.kt
  98. 68 0
      RcCore/src/main/java/com/rc/core/util/EscapeUnescape.java
  99. 17 0
      RcCore/src/main/java/com/rc/core/util/Extension.kt
  100. 0 0
      RcCore/src/main/java/com/rc/core/util/FastClickDelegate.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>

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

@@ -0,0 +1,72 @@
+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.converter.NullOnEmptyConverterFactory
+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 {
+
+    private const val TIMEOUT_DEFAULT = 3 * 60L
+
+    private var mAppContext: Context? = null
+
+    var token: String? = null
+    var vName: String = "1.0.0"
+    // Log信息拦截器
+    private val loggingInterceptor = HttpLoggingInterceptor()
+    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 {
+        //设置日志打印级别
+        loggingInterceptor.level = HttpLoggingInterceptor.Level.BASIC
+        return OkHttpClient.Builder()
+            .readTimeout(TIMEOUT_DEFAULT, TimeUnit.SECONDS) // 设置读取超时时间
+            .connectTimeout(TIMEOUT_DEFAULT, TimeUnit.SECONDS) // 设置请求超时时间
+            .writeTimeout(TIMEOUT_DEFAULT, TimeUnit.SECONDS) // 设置写入超时时间
+            .addNetworkInterceptor(TokenHeaderInterceptor())
+            .addInterceptor(loggingInterceptor)//添加请求日志
+            .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.251.2/labSystem/"
+
+//        var SIGN_IN_CHECK_BASE_URL = "http://180.76.134.43:931/"
+        var SIGN_IN_CHECK_BASE_URL = "http://192.168.251.2:8012/"
+
+        var BASE_PATH_FACE = BasePathV1.FACE
+        var BASE_PATH_SPEAK = BasePathV1.SPEAK
+
+        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 SPEAK = "speak"
+    }
+
+    object BasePathV2 {
+        const val ALGORITHM = "algorithm"
+    }
+
+}

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

@@ -0,0 +1,633 @@
+package com.rc.httpcore.client
+
+import com.rc.httpcore.HttpClient
+import com.rc.httpcore.exception.NetException
+import com.rc.httpcore.vo.CommonRowsResponse
+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 mLibClient by lazy { HttpClient.createClientFactory().createLabClient() }
+
+    /**
+     * 登录获取token
+     */
+    fun authOneLogin(): Observable<Boolean> {
+        return mLibClient.authOneLogin().schedulers()
+    }
+
+    /**
+     * 查询APK版本
+     *
+     * @param id 设备唯一编码
+     */
+    fun apkVersion(id: String): Observable<ApkInfoResp> {
+        return mLibClient.apkVersion(id).schedulers()
+    }
+
+    /**
+     * 上传APK更新状态
+     *
+     * @param state 0:升级失败; 1:升级成功; 2:升级中
+     */
+    fun onepcApkUpdate(id: String, state: String): Observable<Boolean> {
+        return mLibClient.onepcApkUpdate(id, state).schedulers()
+    }
+
+    /**
+     * 实验室信息
+     *
+     * @param id 设备唯一编码
+     */
+    fun laboratoryInfo(id: String): Observable<LaboratoryVo> {
+        return mLibClient.laboratoryInfo(id).schedulers()
+    }
+
+    /**
+     * 危险源信息
+     *
+     * @param param 实验室Id、分页信息
+     */
+    fun hazardlist(param: HazardReq): Observable<List<LabHazardVo>> {
+        return mLibClient.hazardlist(param).schedulers()
+    }
+
+    /**
+     * 签到验证(进入)
+     *
+     * @param eBoard 是否为电子信息牌
+     * @param subId 实验室id
+     * @param username 学生卡编号/人员id
+     */
+    fun signInCheck(eBoard: Boolean, subId: String, username: String): Observable<SignInCheckResp> {
+        return mLibClient.signInCheck(eBoard, subId, username).schedulers()
+    }
+
+    /**
+     * 签到提交-人脸验证
+     */
+    fun signInFace(code: String, faceFeature: SignInReq? = null): Observable<String> {
+        return mLibClient.signInFace(code, faceFeature).schedulers()
+    }
+
+    /**
+     * 签到-安全准入检测三合一
+     *
+     * @param patrolSign 是否为巡查签到
+     */
+    fun checkInAll(patrolSign: Boolean, param: CheckInAllReq): Observable<Boolean> {
+        return mLibClient.checkInAll(patrolSign, param).schedulers()
+    }
+
+    /**
+     * 签到提交
+     */
+    fun signIn(id: String): Observable<Boolean> {
+        return mLibClient.signIn(id).schedulers()
+    }
+
+    /**
+     * 签到提交-有跳过安全准入检测时使用
+     */
+    fun signInJump(id: String, code: String): Observable<Boolean> {
+        return mLibClient.signInJump(id, code).schedulers()
+    }
+
+    /**
+     * 签到提交
+     * @param jumpSafetyCheck 是否跳过安全检查
+     * @param code 查询人员状态返回的code
+     * @param id 人脸比对成功后返回的id
+     */
+    fun signIn(jumpSafetyCheck: Boolean, code: String, id: String): Observable<Boolean> {
+        return if (jumpSafetyCheck) {
+            signInJump(id, code)
+        } else {
+            signIn(id)
+        }
+    }
+
+    /**
+     * 签到验证(离开)
+     *
+     * @param eBoard 是否为电子信息牌
+     * @param subId 实验室id
+     * @param username 学生卡编号/人员id
+     */
+    fun signOutCheck(eBoard: Boolean, subId: String, username: String): Observable<SignInCheckResp> {
+        return mLibClient.signOutCheck(eBoard, subId, username).schedulers()
+    }
+
+    /**
+     * 离开提交
+     */
+    fun signOut(code: String): Observable<Boolean> {
+        return mLibClient.signOut(code).schedulers()
+    }
+
+    /**
+     * 获取实验室一体机可控制设备
+     *
+     * @param subId 实验室id
+     */
+    fun controllerList(subId: String): Observable<List<LotDeviceVo>> {
+        return mLibClient.controllerList(subId).schedulers()
+    }
+
+    /**
+     * 控制设备
+     *
+     * @param param 设备编号、命令
+     */
+    fun sendControllerCMD(param: ControllerCMD): Observable<Boolean> {
+        return mLibClient.sendControllerCMD(param).schedulers()
+    }
+
+    /**
+     * 实验室测点功能列表(首页-左侧看板)
+     *
+     * @param subId 实验室id
+     */
+    fun functionList(subId: String): Observable<List<LabBulletinBoardVo>> {
+        return mLibClient.functionList(subId).schedulers()
+    }
+
+    /**
+     * 实验室预警测点
+     *
+     * @param subId 实验室id
+     */
+    fun warnList(subId: String): Observable<List<LabBulletinBoardVo>> {
+        return mLibClient.warnList(subId).schedulers()
+    }
+
+    /**
+     * 人脸比对
+     */
+    fun faceCompare(param: FaceCompareReq): Observable<Boolean> {
+        return mLibClient.faceCompare(param).schedulers()
+    }
+
+    /**
+     * 心跳
+     *
+     * @param num 设备唯一编码
+     */
+    fun heartbeat(num: String): Observable<Boolean> {
+        return mLibClient.heartbeat(num).schedulers()
+    }
+
+    /**
+     * 查询实验室安全制度列表
+     *
+     * @param type 1:学校制度 2:学院制度 3: 中心制度
+     */
+    fun safeBookList(type: String): Observable<List<SafeBook>> {
+        return mLibClient.safeBookList(type).schedulers()
+    }
+
+    /**
+     * 获取实验室安全制度详细信息
+     */
+    fun safeBookDetail(id: String): Observable<SafeBook> {
+        return mLibClient.safeBookDetail(id).schedulers()
+    }
+
+    /**
+     * 一体机查询危化品
+     */
+    fun hazardBookList(): Observable<List<HazardBook>> {
+        return mLibClient.hazardBookList().schedulers()
+    }
+
+    /**
+     * 获取危化品安全技术说明书详细信息
+     */
+    fun hazardBookDetail(id: String): Observable<HazardBook> {
+        return mLibClient.hazardBookDetail(id).schedulers()
+    }
+
+    /**
+     * 查询实验室在线人员
+     */
+    fun onlineUser(param: OnLineUserReq): Observable<CommonRowsResponse<LabPersonVo>> {
+        return mLibClient.onlineUser(param).schedulers()
+    }
+
+    /**
+     * 实验室安全整改信息
+     */
+    fun checkMachineMsgList(subId: String): Observable<List<CheckMachineVo>> {
+        return mLibClient.checkMachineMsgList(subId).schedulers()
+    }
+
+    /**
+     * 实验室通知消息信息
+     */
+    fun contentMachineMsgList(subId: String): Observable<List<ContentMachineVo>> {
+        return mLibClient.contentMachineMsgList(subId).schedulers()
+    }
+
+    /**
+     * 实验室文化图
+     *
+     * @param id 设备唯一编码
+     */
+    fun bannerImages(id: String): Observable<List<BannerImageBean>> {
+        return mLibClient.bannerImages(id).schedulers()
+    }
+
+    /**
+     * 文字转语音
+     */
+    fun textParseVideo(text: String): Observable<SpeakInfo> {
+        return mLibClient.textParseVideo(text).schedulers()
+    }
+
+    /**
+     * 物联控制权限验证
+     */
+    fun lotInCheck(subId: String, username: String): Observable<String> {
+        return mLibClient.lotInCheck(subId, username).schedulers()
+    }
+
+    /**
+     * 查询实验室配置
+     */
+    fun queryLabConfig(deviceNum: String): Observable<LabConfig> {
+        return mLibClient.queryLabConfig(deviceNum).schedulers()
+    }
+
+    fun laboratoryInfo(openEBoard: Boolean, id: String): Observable<LaboratoryVo> {
+        return if (openEBoard) laboratoryInfoEBoard(id) else laboratoryInfo(id)
+    }
+
+    fun laboratoryInfoEBoard(labId: String): Observable<LaboratoryVo> {
+        val observable1 = mLibClient.homeTopInfo(labId)
+        val observable2 = mLibClient.homeMiddleInfo(labId)
+
+        return Observable.zip(observable1, observable2, { top, middle ->
+            LaboratoryVo().apply {
+                subId = labId
+                rectangleLogo = top.circularLogo
+                subName = top.subjectName
+
+                dangerColor = middle.filedColor
+                dangerName = middle.typeName
+                dangerLevel = middle.levelName
+                address = middle.subjectName
+                deptName = middle.deptName
+                qrCodeUrl = middle.qrCodeUrl
+                adminName = middle.adminName
+                adminPhone = middle.adminPhone
+                adminUserDesc = "${middle.adminName ?: ""}  |  ${middle.adminPhone ?: ""}"
+                safeUserDesc = middle.safeUserVoList?.fold("") { acc, safeUserVo ->
+                    "$acc  ${safeUserVo.safeUserName ?: ""}  |  ${safeUserVo.safeUserPhone ?: ""}"
+                }
+                buildName = middle.buildName
+                room = middle.room
+                // 责任单位、实验室负责人
+                val safePersons = mutableListOf(
+                    LaboratoryVo.SafePersonInfo("责任单位:", middle.deptName),
+                    LaboratoryVo.SafePersonInfo("实验室负责人:", middle.adminName, middle.adminPhone),
+                )
+                // 安全责任人
+                safePersonList = middle.safeUserVoList?.mapTo(safePersons) { item ->
+                    LaboratoryVo.SafePersonInfo("安全责任人:", item.safeUserName, item.safeUserPhone)
+                } ?: safePersons
+                val specialList = middle.xxpSubjectVoList?.filter { it.isSpecial == "2" }
+                specialClassify = specialList?.flatMap { it.classifyList }
+                specialClassify2 = specialList?.map { it.classifyList }
+                safeClassifyList = middle.xxpSubjectVoList?.filter { it.isSpecial != "2" }
+                inCheck = middle.inCheck
+                inspectInCheck = middle.inspectInCheck
+                outCheck = middle.outCheck
+            }
+        }).schedulers()
+    }
+
+    /**
+     * 查询首页右侧人员信息
+     */
+    fun homeRightInfo(labId: String): Observable<HomeRightResp> {
+        return mLibClient.homeRightInfo(labId).schedulers()
+    }
+
+    /**
+     * 实验室介绍
+     */
+    fun labIntro(labId: String): Observable<String> {
+        return mLibClient.labIntro(labId).schedulers()
+    }
+
+    /**
+     * 查询值班人员列表
+     */
+    fun dutyUserList(labId: String, startTime: String): Observable<DutyPersonVo> {
+        return mLibClient.dutyUserList(labId, startTime).schedulers()
+    }
+
+    /**
+     * 查询实验人员列表
+     */
+    fun signUserList(labId: String): Observable<List<LabPersonVo>> {
+        return mLibClient.signUserList(labId).schedulers()
+    }
+
+    /**
+     * 查询准入人员列表
+     */
+    fun securityUserList(
+        labId: String,
+        pageNumber: Int,
+        pageSize: Int
+    ): Observable<List<LabPersonVo>> {
+        return mLibClient.securityUserList(labId, pageNumber, pageSize).schedulers()
+    }
+
+    /**
+     * 查询巡查人员列表
+     */
+    fun inspectUserList(labId: String, startTime: String): Observable<DutyPersonVo> {
+        return mLibClient.inspectUserList(labId, startTime).schedulers()
+    }
+
+    /**
+     * 查询值班人员/巡查人员
+     * @param type 1-值班人员 2-巡查人员
+     */
+    fun queryDutyPerson(type: Int, labId: String, startTime: String): Observable<DutyPersonVo> {
+        return if (1 == type) dutyUserList(labId, startTime) else inspectUserList(labId, startTime)
+    }
+
+    /**
+     * 查询实验人员/准入人员
+     * @param type 1-实验人员 2-准入人员
+     */
+    fun queryAccessPerson(
+        type: Int,
+        labId: String,
+        pageNumber: Int,
+        pageSize: Int
+    ): Observable<List<LabPersonVo>> {
+        return if (1 == type) signUserList(labId) else securityUserList(labId, pageNumber, pageSize)
+    }
+
+    /**
+     * 视频监控
+     */
+    fun cameraBySubjectId(labId: String): Observable<List<MonitorVo>> {
+        return mLibClient.cameraBySubjectId(labId).schedulers()
+    }
+
+    /**
+     * 获取准入人员的指纹信息列表
+     */
+    fun getFingerList(labId: String): Observable<List<UserFingerVo>> {
+        return mLibClient.getFingerList(labId).schedulers()
+    }
+
+    /**
+     * 根据用户查询指纹列表
+     */
+    fun getFingerByUserId(labId: String, userId: String): Observable<List<UserFingerVo>> {
+        return mLibClient.getFingerByUserId(labId, userId).schedulers()
+    }
+
+    /**
+     * 指纹录入
+     */
+    fun addUserFinger(param: UserFingerVo): Observable<Boolean> {
+        return mLibClient.addUserFinger(param).schedulers()
+    }
+
+    /**
+     * 删除指纹
+     */
+    fun deleteFingerById(id: String): Observable<Boolean> {
+        return mLibClient.deleteFingerById(id).schedulers()
+    }
+
+    /**
+     * 获取人像特征值
+     */
+    fun faceFeature(file: File): Observable<String> {
+        return mLibClient.faceFeature(file).schedulers()
+    }
+
+    /**
+     * 人脸识别
+     */
+    fun authFace(file: File, labId: String): Observable<UserVo> {
+        return mLibClient.faceFeature(file)
+            .flatMap {
+                val faceAuthReq =
+                    AuthFaceReq(it, labId)
+                mLibClient.multiFaceDetection(faceAuthReq)
+            }.schedulers()
+    }
+
+    /**
+     * 刷卡验证
+     */
+    fun authCard(labId: String, cardNum: String): Observable<UserVo> {
+        return mLibClient.cardValidate(labId, cardNum).schedulers()
+    }
+
+    /**
+     * 密码验证
+     */
+    fun authPassword(param: AuthPwdReq): Observable<UserVo> {
+        return mLibClient.pwdValidate(param).schedulers()
+    }
+
+    /**
+     * 签到人像比对
+     */
+    fun signFaceMatching(file: File, code: String): Observable<String> {
+        return mLibClient.faceFeature(file)
+            .flatMap { feature ->
+                val param = SignInReq()
+                param.data = feature.toByteArray()
+                mLibClient.signInFace(code, param)
+            }.schedulers()
+    }
+
+    /**
+     * 离开人像比对
+     */
+    fun leaveFaceMatching(file: File, userId: String): Observable<Boolean> {
+        return mLibClient.faceFeature(file)
+            .flatMap { feature ->
+                val param = FaceCompareReq()
+                param.data = feature.toByteArray()
+                param.userId = userId
+                mLibClient.faceCompare(param)
+            }.schedulers()
+    }
+
+    /**
+     * 校园卡是否能开启门禁
+     */
+    fun getCardIsOpen(labId: String, cardNum: String): Observable<UserVo> {
+        return mLibClient.getCardIsOpen(labId, cardNum).schedulers()
+    }
+
+    /**
+     * 巡查签到
+     */
+    fun signInWithPatrol(param: PatrolSignInReq): Observable<Boolean> {
+        return mLibClient.signInWithPatrol(param).schedulers()
+    }
+
+    /**
+     * 巡查签退
+     */
+    fun signOutWithPatrol(param: PatrolSignOutReq): Observable<Boolean> {
+        return mLibClient.signOutWithPatrol(param.labId, param.userId).schedulers()
+    }
+
+    /**
+     * 巡查签退/准入签退判断
+     *  true-巡查 false-准入
+     */
+    fun isSignInType(labId: String, userId: String): Observable<Boolean> {
+        return mLibClient.isSignInType(labId, userId).schedulers()
+    }
+
+    /**
+     * 准入签到:跳过人脸,传空数据即可
+     *
+     * @param code 查询人员状态返回的code
+     *
+     */
+    fun jumpFaceSignIn(code: String): Observable<Boolean> {
+        return mLibClient.signInFace(code, SignInReq())
+            .flatMap { id ->
+                mLibClient.signInJump(id, code)
+            }.schedulers()
+    }
+
+    /**
+     * 信息牌准入签到,前置检测
+     */
+    fun accessSignCheck(param: CommonSignInReq): Observable<SignFaceVo> {
+        return mLibClient.signInCheck(true, param.labId, param.userId)
+            .flatMap { response ->
+                if (!response.state) {
+                    throw NetException("500", "核验失败,请联系管理员!")
+                }
+                mLibClient.signInFace(response.code, SignInReq())
+                    .map { SignFaceVo(it, response.code) }
+            }.schedulers()
+    }
+
+    /**
+     * 信息牌准入签到,前置检测
+     */
+    fun openAccessControl(labId:String,userId:String): Observable<Boolean> {
+        return mLibClient.openAccessControl(labId, userId).schedulers()
+    }
+
+    /**
+     * 巡查签到、准入签到(无AI检查) 通用接口
+     *
+     * 一、巡查签到
+     *   #signInCheckWithPatrol labId userId
+     *   #signInWithPatrol num userId
+     *
+     * 二、准入签到(无AI检查)
+     *   #signInCheck(true, labId, userId)
+     *   #jumpFaceSignIn(code)
+     *
+     * @param signType 1-准入签到 2-巡查签到
+     */
+    fun commonSignIn(signType: Int, param: CommonSignInReq): Observable<Boolean> {
+        return if (2 == signType) {
+            mLibClient.signInCheckWithPatrol(param.labId, param.userId)
+                .flatMap {
+                    val reqParam = PatrolSignInReq().apply {
+                        num = param.num
+                        userId = param.userId
+                    }
+                    mLibClient.signInWithPatrol(reqParam)
+                }.schedulers()
+        } else {
+            mLibClient.signInCheck(true, param.labId, param.userId)
+                .flatMap { response ->
+                    if (!response.state) {
+                        throw NetException("500", "核验失败,请联系管理员!")
+                    }
+                    mLibClient.signInFace(response.code, SignInReq())
+                        .map { SignFaceVo(it, response.code) }
+                }.flatMap { face ->
+                    mLibClient.signInJump(face.id, face.code)
+                }.schedulers()
+        }
+    }
+
+    /**
+     * 巡查签退、准入签退(无离开检查项) 通用接口
+     *
+     * 一、巡查签退
+     *   #signOutWithPatrol labId userId
+     *
+     * 二、准入签退(无离开检查项)
+     *   #signOutCheck(true, labId, userId)
+     *   #signOut(code)
+     *
+     * @param signType 1-准入签到 2-巡查签到
+     */
+    fun commonLeave(signType: Int, labId: String, userId: String): Observable<Boolean> {
+        return if (2 == signType) {
+            mLibClient.signOutWithPatrol(labId, userId).schedulers()
+        } else {
+            mLibClient.signOutCheck(true, labId, userId)
+                .flatMap { response ->
+                    if (!response.state) {
+                        throw NetException("500", "核验失败,请联系管理员!")
+                    }
+                    mLibClient.signOut(response.code)
+                }.schedulers()
+        }
+    }
+
+    /**
+     * 巡查签到,前置检测
+     */
+    fun patrolSignCheck(param: CommonSignInReq): Observable<Boolean> {
+        return mLibClient.signInCheckWithPatrol(param.labId, param.userId).schedulers()
+    }
+
+    /**
+     * 滚动消息列表
+     */
+    fun newMsgGroup(labId: String): Observable<List<NoticeSummaryVo>> {
+        return mLibClient.newMsgGroup(labId).schedulers()
+    }
+
+    /**
+     * 通知消息列表
+     */
+    fun newMsg(labId: String, pageNum: Int, pageSize: Int): Observable<List<NoticeDetailVo>> {
+        return mLibClient.newMsg(labId, pageNum, pageSize).schedulers()
+    }
+
+    /**
+     * 整改消息列表
+     */
+    fun checkNewMsg(labId: String, pageNum: Int, pageSize: Int): Observable<List<NoticeDetailVo>> {
+        return mLibClient.checkNewMsg(labId, pageNum, pageSize).schedulers()
+    }
+
+}
+
+fun <T> Observable<T>.schedulers(): Observable<T> {
+    return this.subscribeOn(Schedulers.io())
+        .observeOn(AndroidSchedulers.mainThread())
+}

+ 323 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/client/LabClient.kt

@@ -0,0 +1,323 @@
+package com.rc.httpcore.client
+
+import com.rc.httpcore.vo.CommonRowsResponse
+import com.rc.httpcore.vo.request.*
+import com.rc.httpcore.vo.response.*
+import io.reactivex.Observable
+import java.io.File
+
+interface LabClient {
+
+    /**
+     * 登录获取token
+     */
+    fun authOneLogin(): Observable<Boolean>
+
+    /**
+     * 查询APK版本
+     *
+     * @param id 设备唯一编码
+     */
+    fun apkVersion(id: String): Observable<ApkInfoResp>
+
+    /**
+     * 上传APK更新状态
+     *
+     * @param state 0:升级失败; 1:升级成功; 2:升级中
+     */
+    fun onepcApkUpdate(id: String, state: String): Observable<Boolean>
+
+    /**
+     * 实验室信息
+     *
+     * @param id 设备唯一编码
+     */
+    fun laboratoryInfo(id: String): Observable<LaboratoryVo>
+
+    /**
+     * 危险源信息
+     *
+     * @param param 实验室Id、分页信息
+     */
+    fun hazardlist(param: HazardReq): Observable<List<LabHazardVo>>
+
+    /**
+     * 签到验证(进入)
+     *
+     * @param eBoard 是否为电子信息牌
+     * @param subId 实验室id
+     * @param username 学生卡编号/人员id
+     */
+    fun signInCheck(eBoard: Boolean, subId: String, username: String): Observable<SignInCheckResp>
+
+    /**
+     * 签到验证(进入)
+     *
+     * @param eBoard 是否为电子信息牌
+     * @param subId 实验室id
+     * @param username 学生卡编号/人员id
+     */
+    fun openAccessControl(labId:String,userId:String): Observable<Boolean>
+
+    /**
+     * 签到提交-人脸验证
+     */
+    fun signInFace(code: String, faceFeature: SignInReq? = null): Observable<String>
+
+    /**
+     * 签到-安全准入检测三合一
+     *
+     * @param patrolSign 是否为巡查签到
+     */
+    fun checkInAll(patrolSign: Boolean, param: CheckInAllReq): Observable<Boolean>
+
+    /**
+     * 签到提交
+     */
+    fun signIn(id: String): Observable<Boolean>
+
+    /**
+     * 签到提交-有跳过安全准入检测时使用
+     */
+    fun signInJump(id: String, code: String): Observable<Boolean>
+
+    /**
+     * 签到验证(离开)
+     *
+     * @param eBoard 是否为电子信息牌
+     * @param subId 实验室id
+     * @param username 学生卡编号/人员id
+     */
+    fun signOutCheck(eBoard: Boolean, subId: String, username: String): Observable<SignInCheckResp>
+
+    /**
+     * 离开提交
+     */
+    fun signOut(code: String): Observable<Boolean>
+
+    /**
+     * 获取实验室一体机可控制设备
+     *
+     * @param subId 实验室id
+     */
+    fun controllerList(subId: String): Observable<List<LotDeviceVo>>
+
+    /**
+     * 控制设备
+     *
+     * @param param 设备编号、命令
+     */
+    fun sendControllerCMD(param: ControllerCMD): Observable<Boolean>
+
+    /**
+     * 实验室测点功能列表(首页-左侧看板)
+     *
+     * @param subId 实验室id
+     */
+    fun functionList(subId: String): Observable<List<LabBulletinBoardVo>>
+
+    /**
+     * 实验室预警测点
+     *
+     * @param subId 实验室id
+     */
+    fun warnList(subId: String): Observable<List<LabBulletinBoardVo>>
+
+    /**
+     * 人脸比对
+     */
+    fun faceCompare(param: FaceCompareReq): Observable<Boolean>
+
+    /**
+     * 心跳
+     *
+     * @param num 设备唯一编码
+     */
+    fun heartbeat(num: String): Observable<Boolean>
+
+    /**
+     * 查询实验室安全制度列表
+     *
+     * @param type 1:学校制度 2:学院制度 3: 中心制度
+     */
+    fun safeBookList(type: String): Observable<List<SafeBook>>
+
+    /**
+     * 获取实验室安全制度详细信息
+     */
+    fun safeBookDetail(id: String): Observable<SafeBook>
+
+    /**
+     * 一体机查询危化品
+     */
+    fun hazardBookList(): Observable<List<HazardBook>>
+
+    /**
+     * 获取危化品安全技术说明书详细信息
+     */
+    fun hazardBookDetail(id: String): Observable<HazardBook>
+
+    /**
+     * 查询实验室在线人员
+     */
+    fun onlineUser(param: OnLineUserReq): Observable<CommonRowsResponse<LabPersonVo>>
+
+    /**
+     * 实验室安全整改信息
+     */
+    fun checkMachineMsgList(subId: String): Observable<List<CheckMachineVo>>
+
+    /**
+     * 实验室通知消息信息
+     */
+    fun contentMachineMsgList(subId: String): Observable<List<ContentMachineVo>>
+
+    /**
+     * 实验室文化图
+     *
+     * @param id 设备唯一编码
+     */
+    fun bannerImages(id: String): Observable<List<BannerImageBean>>
+
+    /**
+     * 文字转语音
+     */
+    fun textParseVideo(text: String): Observable<SpeakInfo>
+
+    /**
+     * 物联控制权限验证
+     */
+    fun lotInCheck(subId: String, username: String): Observable<String>
+
+    /**
+     * 查询实验室配置
+     */
+    fun queryLabConfig(deviceNum: String): Observable<LabConfig>
+
+    /**
+     * 查询首页头部信息
+     */
+    fun homeTopInfo(labId: String): Observable<HomeTopResp>
+
+    /**
+     * 查询首页中部信息
+     */
+    fun homeMiddleInfo(labId: String): Observable<HomeMiddleResp>
+
+    /**
+     * 查询首页右侧人员信息
+     */
+    fun homeRightInfo(labId: String): Observable<HomeRightResp>
+
+    /**
+     * 实验室介绍
+     */
+    fun labIntro(labId: String): Observable<String>
+
+    /**
+     * 查询值班人员列表
+     */
+    fun dutyUserList(labId: String, startTime: String): Observable<DutyPersonVo>
+
+    /**
+     * 查询实验人员列表
+     */
+    fun signUserList(labId: String): Observable<List<LabPersonVo>>
+
+    /**
+     * 查询准入人员列表
+     */
+    fun securityUserList(labId: String, pageNumber: Int, pageSize: Int): Observable<List<LabPersonVo>>
+
+    /**
+     * 查询巡查人员列表
+     */
+    fun inspectUserList(labId: String, startTime: String): Observable<DutyPersonVo>
+
+    /**
+     * 视频监控
+     */
+    fun cameraBySubjectId(labId: String): Observable<List<MonitorVo>>
+
+    /**
+     * 获取准入人员的指纹信息列表
+     */
+    fun getFingerList(labId: String): Observable<List<UserFingerVo>>
+
+    /**
+     * 根据用户查询指纹列表
+     */
+    fun getFingerByUserId(labId: String, userId: String): Observable<List<UserFingerVo>>
+
+    /**
+     * 指纹录入
+     */
+    fun addUserFinger(param: UserFingerVo): Observable<Boolean>
+
+    /**
+     * 删除指纹
+     */
+    fun deleteFingerById(id: String): Observable<Boolean>
+
+    /**
+     * 获取人像特征值
+     */
+    fun faceFeature(file: File): Observable<String>
+
+    /**
+     * 人脸识别
+     */
+    fun multiFaceDetection(param: AuthFaceReq): Observable<UserVo>
+
+    /**
+     * 刷卡验证
+     */
+    fun cardValidate(labId: String, cardNum: String): Observable<UserVo>
+
+    /**
+     * 密码验证
+     */
+    fun pwdValidate(param: AuthPwdReq): Observable<UserVo>
+
+    /**
+     * 校园卡是否能开启门禁
+     */
+    fun getCardIsOpen(labId: String, cardNum: String): Observable<UserVo>
+
+    /**
+     * 巡查签到
+     */
+    fun signInWithPatrol(param: PatrolSignInReq): Observable<Boolean>
+
+    /**
+     * 巡查签退
+     */
+    fun signOutWithPatrol(labId: String, userId: String): Observable<Boolean>
+
+    /**
+     * 巡查签退/准入签退判断
+     *  true-巡查 false-准入
+     */
+    fun isSignInType(labId: String, userId: String): Observable<Boolean>
+
+    /**
+     * 巡查签到前置校验
+     */
+    fun signInCheckWithPatrol(labId: String, userId: String): Observable<Boolean>
+
+    /**
+     * 滚动消息列表
+     */
+    fun newMsgGroup(labId: String): Observable<List<NoticeSummaryVo>>
+
+    /**
+     * 通知消息列表
+     */
+    fun newMsg(labId: String, pageNum: Int, pageSize: Int): Observable<List<NoticeDetailVo>>
+
+    /**
+     * 整改消息列表
+     */
+    fun checkNewMsg(labId: String, pageNum: Int, pageSize: Int): Observable<List<NoticeDetailVo>>
+
+}

+ 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.LabClient
+
+interface ClientFactory {
+
+    fun createLabClient(): LabClient
+
+}

+ 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.LabRetrofit
+
+class RetrofitFactory : ClientFactory {
+
+    override fun createLabClient() = LabRetrofit()
+
+}

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

@@ -0,0 +1,429 @@
+package com.rc.httpcore.client.retrofit;
+
+import com.rc.httpcore.vo.CommonDataResponse;
+import com.rc.httpcore.vo.CommonResponse;
+import com.rc.httpcore.vo.CommonRowsResponse;
+import com.rc.httpcore.vo.request.AccessTokenReq;
+import com.rc.httpcore.vo.request.AuthFaceReq;
+import com.rc.httpcore.vo.request.FaceCompareReq;
+import com.rc.httpcore.vo.request.OnLineUserReq;
+import com.rc.httpcore.vo.request.PatrolSignInReq;
+import com.rc.httpcore.vo.request.SignInReq;
+import com.rc.httpcore.vo.response.AccessTokenResp;
+import com.rc.httpcore.vo.response.ApkInfoResp;
+import com.rc.httpcore.vo.response.BannerImageBean;
+import com.rc.httpcore.vo.response.CheckMachineVo;
+import com.rc.httpcore.vo.response.ContentMachineVo;
+import com.rc.httpcore.vo.response.DutyPersonVo;
+import com.rc.httpcore.vo.response.HazardBook;
+import com.rc.httpcore.vo.response.HomeMiddleResp;
+import com.rc.httpcore.vo.response.HomeRightResp;
+import com.rc.httpcore.vo.response.HomeTopResp;
+import com.rc.httpcore.vo.response.LabBulletinBoardVo;
+import com.rc.httpcore.vo.response.LabConfig;
+import com.rc.httpcore.vo.response.LabHazardVo;
+import com.rc.httpcore.vo.response.LabPersonVo;
+import com.rc.httpcore.vo.response.LaboratoryVo;
+import com.rc.httpcore.vo.response.LotDeviceVo;
+import com.rc.httpcore.vo.response.MonitorVo;
+import com.rc.httpcore.vo.response.NoticeDetailVo;
+import com.rc.httpcore.vo.response.NoticeSummaryVo;
+import com.rc.httpcore.vo.response.SafeBook;
+import com.rc.httpcore.vo.response.SignInCheckResp;
+import com.rc.httpcore.vo.response.SpeakInfo;
+import com.rc.httpcore.vo.response.UserFingerVo;
+import com.rc.httpcore.vo.response.UserVo;
+
+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.Field;
+import retrofit2.http.FormUrlEncoded;
+import retrofit2.http.GET;
+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;
+
+public interface ApiService {
+
+    /**
+     * 登录获取token
+     */
+    @POST("auth/one/login")
+    Observable<CommonDataResponse<AccessTokenResp>> authOneLogin(@Body AccessTokenReq param);
+
+    /**
+     * 查询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);
+
+    /**
+     * 实验室信息
+     */
+    @GET("laboratory/onemachine/{id}")
+    Observable<CommonDataResponse<LaboratoryVo>> laboratoryInfo(@Path("id") String id);
+
+    /**
+     * 危险源信息
+     */
+    @GET("laboratory/onemachine/{subId}/Hazard/list")
+    Observable<CommonRowsResponse<LabHazardVo>> hazardlist(@Path("subId") String subId, @Query("pageNum") int pageNum, @Query("pageSize") int pageSize);
+
+    /**
+     * 签到验证(进入)
+     */
+    @GET("laboratory/onemachine/{subId}/SignInCheck/{username}")
+    Observable<CommonDataResponse<SignInCheckResp>> signInCheck(@Path("subId") String subId, @Path("username") String username);
+
+    /**
+     * 签到验证(进入)
+     */
+    @GET("laboratory/onemachine/{subId}/SignInXxpCheck/{userId}")
+    Observable<CommonDataResponse<SignInCheckResp>> signInXxpCheck(@Path("subId") String subId, @Path("userId") String userId);
+
+    /**
+     * 签到验证(进入)
+     */
+    @GET("/app/board/openAccessControl")
+    Observable<CommonResponse> openAccessControl(@Query("labId") String subId, @Query("userId") String userId);
+
+    /**
+     * 签到提交-人脸验证
+     */
+    @POST("laboratory/onemachine/{code}/SignIn2")
+    Observable<CommonDataResponse<String>> signInFace(@Path("code") String code, @Body SignInReq data);
+
+    /**
+     * 签到-安全准入检测三合一
+     */
+    @Multipart
+    @POST("algorithm/signIn/check/checkInAll")
+    Observable<CommonDataResponse<String>> checkInAll(@PartMap Map<String, RequestBody> params, @Part MultipartBody.Part file);
+
+    /**
+     * 签到-安全准入检测三合一
+     */
+    @Multipart
+    @POST("algorithm/signIn/check/checkInXxpAll")
+    Observable<CommonDataResponse<String>> checkInXxpAll(@PartMap Map<String, RequestBody> params, @Part MultipartBody.Part file);
+
+    /**
+     * 签到提交
+     */
+    @POST("laboratory/onemachine/{id}/SignIn/commit")
+    Observable<CommonDataResponse<String>> signIn(@Path("id") String id);
+
+    /**
+     * 签到提交-有跳过安全准入检测时使用
+     */
+    @POST("laboratory/checklog/{id}/jump/{code}")
+    Observable<CommonDataResponse<String>> signInJump(@Path("id") String id, @Path("code") String code);
+
+    /**
+     * 签到验证(离开)
+     */
+    @GET("laboratory/onemachine/{subId}/SignOutCheck/{username}")
+    Observable<CommonDataResponse<SignInCheckResp>> signOutCheck(@Path("subId") String subId, @Path("username") String username);
+
+    /**
+     * 签到验证(离开)
+     */
+    @GET("laboratory/onemachine/{subId}/SignOutXXpCheck/{userId}")
+    Observable<CommonDataResponse<SignInCheckResp>> signOutXXpCheck(@Path("subId") String subId, @Path("userId") String userId);
+
+    /**
+     * 离开提交
+     */
+    @POST("laboratory/onemachine/{code}/SignOut")
+    Observable<CommonDataResponse<String>> signOut(@Path("code") String code);
+
+    /**
+     * 获取实验室一体机可控制设备
+     */
+    @GET("laboratory/onemachine/{subId}/controller/list")
+    Observable<CommonDataResponse<List<LotDeviceVo>>> controllerList(@Path("subId") String subId);
+
+    /**
+     * 控制设备
+     */
+    @POST("laboratory/subject/manger/control/{id}/{command}")
+    Observable<CommonResponse> sendControllerCMD(@Path("id") String id, @Path("command") String command);
+
+    /**
+     * 实验室测点功能列表(首页-左侧看板)
+     */
+    @GET("laboratory/onemachine/{subId}/function/list")
+    Observable<CommonDataResponse<List<LabBulletinBoardVo>>> functionList(@Path("subId") String subId);
+
+    /**
+     * 实验室预警测点
+     */
+    @POST("laboratory/onemachine/{subId}/warn")
+    Observable<CommonDataResponse<List<LabBulletinBoardVo>>> warnList(@Path("subId") String subId);
+
+    /**
+     * 人脸比对
+     */
+    @POST("{basePath}/faceApi/compare")
+    Observable<CommonDataResponse<String>> faceCompare(@Path("basePath") String basePath, @Body FaceCompareReq param);
+
+    /**
+     * 心跳
+     */
+    @PUT("laboratory/subject/manger/{num}/heartbeat")
+    Observable<CommonResponse> heartbeat(@Path("num") String num);
+
+    /**
+     * 查询实验室安全制度列表
+     *
+     * @param type 1:学校制度 2:学院制度 3: 中心制度
+     */
+    @GET("laboratory/safe_book/queryOption")
+    Observable<CommonDataResponse<List<SafeBook>>> safeBookList(@Query("type") String type);
+
+    /**
+     * 获取实验室安全制度详细信息
+     */
+    @GET("laboratory/safe_book/{id}")
+    Observable<CommonDataResponse<SafeBook>> safeBookDetail(@Path("id") String id);
+
+    /**
+     * 一体机查询危化品
+     */
+    @GET("laboratory/hazard_book/queryOption")
+    Observable<CommonDataResponse<List<HazardBook>>> hazardBookList();
+
+    /**
+     * 获取危化品安全技术说明书详细信息
+     */
+    @GET("laboratory/hazard_book/{id}")
+    Observable<CommonDataResponse<HazardBook>> hazardBookDetail(@Path("id") String id);
+
+    /**
+     * 查询实验室在线人员
+     */
+    @POST("laboratory/onemachine/sub/online/user")
+    Observable<CommonRowsResponse<LabPersonVo>> onlineUser(@Body OnLineUserReq param);
+
+    /**
+     * 实验室安全整改信息
+     */
+    @GET("laboratory/checkMachineMsg/list")
+    Observable<CommonDataResponse<List<CheckMachineVo>>> checkMachineMsgList(@Query("subId") String subId);
+
+    /**
+     * 实验室通知消息信息
+     */
+    @GET("laboratory/contentMachineMsg/list")
+    Observable<CommonRowsResponse<ContentMachineVo>> contentMachineMsgList(@Query("subId") String subId);
+
+    /**
+     * 实验室文化图
+     */
+    @GET("laboratory/onemachine/rotation/chart")
+    Observable<CommonDataResponse<List<BannerImageBean>>> bannerImages(@Query("hardwareNum") String hardwareNum);
+
+    /**
+     * 文字转语音
+     */
+    @GET("{basePath}/speaker/textParseVideo")
+    Observable<SpeakInfo> textParseVideo(@Path("basePath") String basePath, @Query("speed") String speed, @Query("volume") String volume, @Query("text") String text);
+
+    /**
+     * 物联控制权限验证
+     */
+    @GET("laboratory/onemachine/{subId}/LotInCheck/{username}")
+    Observable<CommonDataResponse<String>> lotInCheck(@Path("subId") String subId, @Path("username") String username);
+
+    /**
+     * 查询实验室配置
+     */
+    @GET("base/app/board/getSubjectIdByNum/{deviceNum}")
+    Observable<CommonDataResponse<LabConfig>> queryLabConfig(@Path("deviceNum") String deviceNum);
+
+    /**
+     * 查询首页头部信息
+     */
+    @GET("base/app/board/topInfo")
+    Observable<CommonDataResponse<HomeTopResp>> homeTopInfo(@Query("labId") String labId);
+
+    /**
+     * 查询首页中部信息
+     */
+    @GET("base/app/board/middleInfo")
+    Observable<CommonDataResponse<HomeMiddleResp>> homeMiddleInfo(@Query("labId") String labId);
+
+    /**
+     * 查询首页右侧人员信息
+     */
+    @GET("base/app/board/rightInfo")
+    Observable<CommonDataResponse<HomeRightResp>> homeRightInfo(@Query("labId") String labId);
+
+    /**
+     * 实验室介绍
+     */
+    @GET("base/app/board/labIntro")
+    Observable<CommonDataResponse<String>> labIntro(@Query("labId") String labId);
+
+    /**
+     * 查询值班人员列表
+     */
+    @GET("base/app/board/dutyUserList")
+    Observable<CommonDataResponse<DutyPersonVo>> dutyUserList(@Query("labId") String labId, @Query("startTime") String startTime);
+
+    /**
+     * 查询实验人员列表
+     */
+    @GET("base/app/board/signUserList")
+    Observable<CommonDataResponse<List<LabPersonVo>>> signUserList(@Query("labId") String labId);
+
+    /**
+     * 查询准入人员列表
+     */
+    @GET("base/app/board/securityUserList")
+    Observable<CommonDataResponse<List<LabPersonVo>>> securityUserList(@Query("labId") String labId, @Query("pageNumber") int pageNumber, @Query("pageSize") int pageSize);
+
+    /**
+     * 查询巡查人员列表
+     */
+    @GET("base/app/board/inspectUserList")
+    Observable<CommonDataResponse<DutyPersonVo>> inspectUserList(@Query("labId") String labId, @Query("startTime") String startTime);
+
+    /**
+     * 视频监控
+     */
+    @GET("base/app/board/cameraBySubjectId")
+    Observable<CommonDataResponse<List<MonitorVo>>> cameraBySubjectId(@Query("labId") String labId);
+
+    /**
+     * 获取准入人员的指纹信息列表
+     */
+    @GET("base/app/board/getFingerList")
+    Observable<CommonDataResponse<List<UserFingerVo>>> getFingerList(@Query("labId") String labId);
+
+    /**
+     * 根据用户查询指纹列表
+     */
+    @GET("base/app/board/getFingerByUserId")
+    Observable<CommonDataResponse<List<UserFingerVo>>> getFingerByUserId(@Query("labId") String labId,
+                                                                         @Query("userId") String userId);
+
+    /**
+     * 指纹录入
+     */
+    @POST("base/app/board/addUserFinger")
+    Observable<CommonResponse> addUserFinger(@Body UserFingerVo param);
+
+    /**
+     * 删除指纹
+     */
+    @GET("base/app/board/deleteFingerById")
+    Observable<CommonResponse> deleteFingerById(@Query("id") String id);
+
+    /**
+     * 获取人像特征值
+     */
+    @Multipart
+    @POST("base/app/board/faceFeature")
+    Observable<CommonDataResponse<String>> faceFeature(@Part MultipartBody.Part filePart);
+
+    /**
+     * 人脸识别
+     */
+    @POST("base/app/board/multiFaceDetection")
+    Observable<CommonDataResponse<UserVo>> multiFaceDetection(@Body AuthFaceReq param);
+
+    /**
+     * 刷卡验证
+     */
+    @GET("base/app/board/cardValidate")
+    Observable<CommonDataResponse<UserVo>> cardValidate(@Query("labId") String labId,
+                                                        @Query("cardNum") String cardNum);
+
+    /**
+     * 密码验证
+     */
+    @GET("base/app/board/pwdValidate")
+    Observable<CommonDataResponse<UserVo>> pwdValidate(@Query("num") String num,
+                                                       @Query("labId") String labId,
+                                                       @Query("userId") String userId,
+                                                       @Query("pwd") String pwd);
+
+    /**
+     * 校园卡是否能开启门禁
+     */
+    @GET("base/app/board/getCardIsOpen")
+    Observable<CommonDataResponse<UserVo>> getCardIsOpen(@Query("labId") String labId,
+                                                       @Query("cardNum") String cardNum);
+
+    /**
+     * 巡查签到
+     */
+    @POST("base/app/board/XxpInspection/addInspection/signIn")
+    Observable<CommonResponse> signInWithPatrol(@Body PatrolSignInReq param);
+
+    /**
+     * 巡查签退
+     */
+    @FormUrlEncoded
+    @POST("base/app/board/XxpInspection/addInspection/signOut")
+    Observable<CommonResponse> signOutWithPatrol(@Field("labId") String labId,
+                                                 @Field("userId") String userId);
+
+    /**
+     * 巡查签退/准入签退判断
+     *  1-巡查 2-准入
+     */
+    @FormUrlEncoded
+    @POST("base/app/board/XxpInspection/isSignInType")
+    Observable<CommonDataResponse<String>> isSignInType(@Field("labId") String labId,
+                                                        @Field("userId") String userId);
+
+    /**
+     * 巡查签到前置校验
+     */
+    @FormUrlEncoded
+    @POST("base/app/board/XxpInspection/isSignInspection")
+    Observable<CommonResponse> signInCheckWithPatrol(@Field("labId") String labId,
+                                                     @Field("userId") String userId);
+
+    /**
+     * 滚动消息列表
+     */
+    @GET("laboratory/onemachine/{labId}/newMsgGroup")
+    Observable<CommonDataResponse<List<NoticeSummaryVo>>> newMsgGroup(@Path("labId") String labId);
+
+    /**
+     * 通知消息列表
+     */
+    @GET("laboratory/onemachine/{labId}/newMsg")
+    Observable<CommonRowsResponse<NoticeDetailVo>> newMsg(@Path("labId") String labId,
+                                                          @Query("pageNum") int pageNum,
+                                                          @Query("pageSize") int pageSize);
+
+    /**
+     * 整改消息列表
+     */
+    @GET("laboratory/onemachine/{labId}/checkNewMsg")
+    Observable<CommonRowsResponse<NoticeDetailVo>> checkNewMsg(@Path("labId") String labId,
+                                                               @Query("pageNum") int pageNum,
+                                                               @Query("pageSize") int pageSize);
+
+}

+ 822 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/client/retrofit/LabRetrofit.kt

@@ -0,0 +1,822 @@
+package com.rc.httpcore.client.retrofit
+
+import com.rc.httpcore.HttpClient
+import com.rc.httpcore.HttpConfig
+import com.rc.httpcore.client.LabClient
+import com.rc.httpcore.exception.AICheckException
+import com.rc.httpcore.exception.NetException
+import com.rc.httpcore.vo.CommonRowsResponse
+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 LabRetrofit : LabClient {
+
+    private val apiService by lazy {
+        HttpClient.createRetrofitApi(ApiService::class.java)
+    }
+
+    private val signInCheckService by lazy {
+        HttpClient.createRetrofitApi(ApiService::class.java, HttpConfig.SIGN_IN_CHECK_BASE_URL)
+    }
+
+    /**
+     * 登录获取token
+     */
+    override fun authOneLogin(): Observable<Boolean> {
+        val param = AccessTokenReq().apply {
+            username = "onecUser"
+            password = "admin123"
+        }
+        return apiService.authOneLogin(param)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+
+                HttpClient.token = response.data?.access_token
+                return@map !HttpClient.token.isNullOrEmpty()
+            }
+    }
+
+    /**
+     * 查询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 id 设备唯一编码
+     */
+    override fun laboratoryInfo(id: String): Observable<LaboratoryVo> {
+        return apiService.laboratoryInfo(id)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+                return@map response.data
+            }
+    }
+
+    /**
+     * 危险源信息
+     *
+     * @param param 实验室Id、分页信息
+     */
+    override fun hazardlist(param: HazardReq): Observable<List<LabHazardVo>> {
+        return apiService.hazardlist(param.subId, param.pageNum, param.pageSize)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+                return@map response.rows
+            }
+    }
+
+    /**
+     * 签到验证(进入)
+     *
+     * @param eBoard 是否为电子信息牌
+     * @param subId 实验室id
+     * @param username 学生卡编号/人员id
+     */
+    override fun signInCheck(eBoard: Boolean, subId: String, username: String): Observable<SignInCheckResp> {
+        val observable = if (eBoard) {
+            apiService.signInXxpCheck(subId, username)
+        } else {
+            apiService.signInCheck(subId, username)
+        }
+        return observable.map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+                return@map response.data
+            }
+    }
+
+    override fun openAccessControl(labId: String, userId: String): Observable<Boolean> {
+        return apiService.openAccessControl(labId, userId)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+                return@map true
+            }
+    }
+
+    /**
+     * 签到提交-人脸验证
+     */
+    override fun signInFace(code: String, faceFeature: SignInReq?): Observable<String> {
+        return apiService.signInFace(code, faceFeature)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+
+                return@map response.data
+            }
+    }
+
+    /**
+     * 签到-安全准入检测三合一
+     *
+     * @param patrolSign 是否为巡查签到
+     */
+    override fun checkInAll(patrolSign: Boolean, param: CheckInAllReq): Observable<Boolean> {
+        val fileBody = RequestBody.create(MediaType.parse("application/octet-stream"), param.file)
+        val filePart = MultipartBody.Part.createFormData("file", param.file.name, fileBody)
+
+        val formMediaType = MediaType.parse("multipart/form-data")
+        val bodyPartMap = mutableMapOf<String, RequestBody>()
+        if (!param.id.isNullOrEmpty()) {
+            bodyPartMap["id"] = RequestBody.create(formMediaType, param.id)
+        }
+        if (!param.subId.isNullOrEmpty()) {
+            bodyPartMap["subId"] = RequestBody.create(formMediaType, param.subId)
+        }
+
+        val observable = if (patrolSign) {
+            apiService.checkInXxpAll(bodyPartMap, filePart)
+        } else {
+            apiService.checkInAll(bodyPartMap, filePart)
+        }
+        return observable.map { response ->
+                if (!response.isSuccess()) {
+                    throw AICheckException(response.code, response.msg, response.data)
+                }
+
+                return@map true
+            }
+    }
+
+    /**
+     * 签到提交
+     */
+    override fun signIn(id: String): Observable<Boolean> {
+        return apiService.signIn(id)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+
+                return@map true
+            }
+    }
+
+    /**
+     * 签到提交-有跳过安全准入检测时使用
+     */
+    override fun signInJump(id: String, code: String): Observable<Boolean> {
+        return apiService.signInJump(id, code)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+
+                return@map true
+            }
+    }
+
+    /**
+     * 签到验证(离开)
+     *
+     * @param eBoard 是否为电子信息牌
+     * @param subId 实验室id
+     * @param username 学生卡编号/人员id
+     */
+    override fun signOutCheck(eBoard: Boolean, subId: String, username: String): Observable<SignInCheckResp> {
+        val observable = if (eBoard) {
+            apiService.signOutXXpCheck(subId, username)
+        } else {
+            apiService.signOutCheck(subId, username)
+        }
+        return observable.map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+                return@map response.data
+            }
+    }
+
+    /**
+     * 离开提交
+     */
+    override fun signOut(code: String): Observable<Boolean> {
+        return apiService.signOut(code)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+
+                return@map true
+            }
+    }
+
+    /**
+     * 获取实验室一体机可控制设备
+     *
+     * @param subId 实验室id
+     */
+    override fun controllerList(subId: String): Observable<List<LotDeviceVo>> {
+        return apiService.controllerList(subId)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+
+                return@map response.data
+            }
+    }
+
+    /**
+     * 控制设备
+     *
+     * @param param 设备编号、命令
+     */
+    override fun sendControllerCMD(param: ControllerCMD): Observable<Boolean> {
+        return apiService.sendControllerCMD(param.id, param.command)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+
+                return@map true
+            }
+    }
+
+    /**
+     * 实验室测点功能列表(首页-左侧看板)
+     *
+     * @param subId 实验室id
+     */
+    override fun functionList(subId: String): Observable<List<LabBulletinBoardVo>> {
+        return apiService.functionList(subId)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+
+                return@map response.data
+            }
+    }
+
+    /**
+     * 实验室预警测点
+     *
+     * @param subId 实验室id
+     */
+    override fun warnList(subId: String): Observable<List<LabBulletinBoardVo>> {
+        return apiService.warnList(subId)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+
+                return@map response.data
+            }
+    }
+
+    /**
+     * 人脸比对
+     */
+    override fun faceCompare(param: FaceCompareReq): Observable<Boolean> {
+        return apiService.faceCompare(HttpConfig.BASE_PATH_FACE, param)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+
+                return@map true
+            }
+    }
+
+    /**
+     * 心跳
+     *
+     * @param num 设备唯一编码
+     */
+    override fun heartbeat(num: String): Observable<Boolean> {
+        return apiService.heartbeat(num)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+
+                return@map true
+            }
+    }
+
+    /**
+     * 查询实验室安全制度列表
+     *
+     * @param type 1:学校制度 2:学院制度 3: 中心制度
+     */
+    override fun safeBookList(type: String): Observable<List<SafeBook>> {
+        return apiService.safeBookList(type)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+
+                return@map response.data
+            }
+    }
+
+    /**
+     * 获取实验室安全制度详细信息
+     */
+    override fun safeBookDetail(id: String): Observable<SafeBook> {
+        return apiService.safeBookDetail(id)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+
+                return@map response.data
+            }
+    }
+
+    /**
+     * 一体机查询危化品
+     */
+    override fun hazardBookList(): Observable<List<HazardBook>> {
+        return apiService.hazardBookList()
+            .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 onlineUser(param: OnLineUserReq): Observable<CommonRowsResponse<LabPersonVo>> {
+        return apiService.onlineUser(param)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+
+                return@map response
+            }
+    }
+
+    /**
+     * 实验室安全整改信息
+     */
+    override fun checkMachineMsgList(subId: String): Observable<List<CheckMachineVo>> {
+        return apiService.checkMachineMsgList(subId)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+
+                return@map response.data
+            }
+    }
+
+    /**
+     * 实验室通知消息信息
+     */
+    override fun contentMachineMsgList(subId: String): Observable<List<ContentMachineVo>> {
+        return apiService.contentMachineMsgList(subId)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+
+                return@map response.rows
+            }
+    }
+
+    /**
+     * 实验室文化图
+     *
+     * @param id 设备唯一编码
+     */
+    override fun bannerImages(id: String): Observable<List<BannerImageBean>> {
+        return apiService.bannerImages(id)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+                return@map response.data
+            }
+    }
+
+    /**
+     * 文字转语音
+     */
+    override fun textParseVideo(text: String): Observable<SpeakInfo> {
+        return apiService.textParseVideo(HttpConfig.BASE_PATH_SPEAK, "50", "20", text)
+            .map { response ->
+                if ("0" != response.result) {
+                    throw NetException("1000", "转换异常")
+                }
+                return@map response
+            }
+    }
+
+    /**
+     * 物联控制权限验证
+     */
+    override fun lotInCheck(subId: String, username: String): Observable<String> {
+        return apiService.lotInCheck(subId, username)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+                return@map response.data
+            }
+    }
+
+    /**
+     * 查询实验室配置
+     */
+    override fun queryLabConfig(deviceNum: String): Observable<LabConfig> {
+        return apiService.queryLabConfig(deviceNum)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+                return@map response.data
+            }
+    }
+
+    /**
+     * 查询首页头部信息
+     */
+    override fun homeTopInfo(labId: String): Observable<HomeTopResp> {
+        return apiService.homeTopInfo(labId)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+                return@map response.data
+            }
+    }
+
+    /**
+     * 查询首页中部信息
+     */
+    override fun homeMiddleInfo(labId: String): Observable<HomeMiddleResp> {
+        return apiService.homeMiddleInfo(labId)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+                return@map response.data
+            }
+    }
+
+    /**
+     * 查询首页右侧人员信息
+     */
+    override fun homeRightInfo(labId: String): Observable<HomeRightResp> {
+        return apiService.homeRightInfo(labId)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+                return@map response.data
+            }
+    }
+
+    /**
+     * 实验室介绍
+     */
+    override fun labIntro(labId: String): Observable<String> {
+        return apiService.labIntro(labId)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+                return@map response.data
+            }
+    }
+
+    /**
+     * 查询值班人员列表
+     */
+    override fun dutyUserList(labId: String, startTime: String): Observable<DutyPersonVo> {
+        return apiService.dutyUserList(labId, startTime)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+                return@map response.data
+            }
+    }
+
+    /**
+     * 查询实验人员列表
+     */
+    override fun signUserList(labId: String): Observable<List<LabPersonVo>> {
+        return apiService.signUserList(labId)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+                return@map response.data
+            }
+    }
+
+    /**
+     * 查询准入人员列表
+     */
+    override fun securityUserList(labId: String, pageNumber: Int, pageSize: Int): Observable<List<LabPersonVo>> {
+        return apiService.securityUserList(labId, pageNumber, pageSize)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+                return@map response.data
+            }
+    }
+
+    /**
+     * 查询巡查人员列表
+     */
+    override fun inspectUserList(labId: String, startTime: String): Observable<DutyPersonVo> {
+        return apiService.inspectUserList(labId, startTime)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+                return@map response.data
+            }
+    }
+
+    /**
+     * 视频监控
+     */
+    override fun cameraBySubjectId(labId: String): Observable<List<MonitorVo>> {
+        return apiService.cameraBySubjectId(labId)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+                return@map response.data
+            }
+    }
+
+    /**
+     * 获取准入人员的指纹信息列表
+     */
+    override fun getFingerList(labId: String): Observable<List<UserFingerVo>> {
+        return apiService.getFingerList(labId)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+                return@map response.data
+            }
+    }
+
+    /**
+     * 根据用户查询指纹列表
+     */
+    override fun getFingerByUserId(labId: String, userId: String): Observable<List<UserFingerVo>> {
+        return apiService.getFingerByUserId(labId, userId)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+                return@map response.data
+            }
+    }
+
+    /**
+     * 指纹录入
+     */
+    override fun addUserFinger(param: UserFingerVo): Observable<Boolean> {
+        return apiService.addUserFinger(param)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+                return@map true
+            }
+    }
+
+    /**
+     * 删除指纹
+     */
+    override fun deleteFingerById(id: String): Observable<Boolean> {
+        return apiService.deleteFingerById(id)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+                return@map true
+            }
+    }
+
+    /**
+     * 获取人像特征值
+     */
+    override fun faceFeature(file: File): Observable<String> {
+        val fileBody = RequestBody.create(MediaType.parse("application/octet-stream"), file)
+        val filePart = MultipartBody.Part.createFormData("file", file.name, fileBody)
+        return apiService.faceFeature(filePart)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+                return@map response.data
+            }
+    }
+
+    /**
+     * 人脸识别
+     */
+    override fun multiFaceDetection(param: AuthFaceReq): Observable<UserVo> {
+        return apiService.multiFaceDetection(param)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+                return@map response.data
+            }
+    }
+
+    /**
+     * 刷卡验证
+     */
+    override fun cardValidate(labId: String, cardNum: String): Observable<UserVo> {
+        return apiService.cardValidate(labId, cardNum)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+                return@map response.data
+            }
+    }
+
+    /**
+     * 密码验证
+     */
+    override fun pwdValidate(param: AuthPwdReq): Observable<UserVo> {
+        return apiService.pwdValidate(param.num, param.labId, param.userId, param.pwd)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+                return@map response.data
+            }
+    }
+
+    /**
+     * 校园卡是否能开启门禁
+     */
+    override fun getCardIsOpen(labId: String, cardNum: String): Observable<UserVo> {
+        return apiService.getCardIsOpen(labId, cardNum)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+                return@map response.data
+            }
+    }
+
+    /**
+     * 巡查签到
+     */
+    override fun signInWithPatrol(param: PatrolSignInReq): Observable<Boolean> {
+        return apiService.signInWithPatrol(param)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+                return@map true
+            }
+    }
+
+    /**
+     * 巡查签退
+     */
+    override fun signOutWithPatrol(labId: String, userId: String): Observable<Boolean> {
+        return apiService.signOutWithPatrol(labId, userId)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+                return@map true
+            }
+    }
+
+    /**
+     * 巡查签退/准入签退判断
+     *  true-巡查 false-准入
+     */
+    override fun isSignInType(labId: String, userId: String): Observable<Boolean> {
+        return apiService.isSignInType(labId, userId)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+                return@map "1" == response.data
+            }
+    }
+
+    /**
+     * 巡查签到前置校验
+     */
+    override fun signInCheckWithPatrol(labId: String, userId: String): Observable<Boolean> {
+        return apiService.signInCheckWithPatrol(labId, userId)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+                return@map true
+            }
+    }
+
+    /**
+     * 滚动消息列表
+     */
+    override fun newMsgGroup(labId: String): Observable<List<NoticeSummaryVo>> {
+        return apiService.newMsgGroup(labId)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+                return@map response.data
+            }
+    }
+
+    /**
+     * 通知消息列表
+     */
+    override fun newMsg(labId: String, pageNum: Int, pageSize: Int): Observable<List<NoticeDetailVo>> {
+        return apiService.newMsg(labId, pageNum, pageSize)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+                return@map response.rows
+            }
+    }
+
+    /**
+     * 整改消息列表
+     */
+    override fun checkNewMsg(labId: String, pageNum: Int, pageSize: Int): Observable<List<NoticeDetailVo>> {
+        return apiService.checkNewMsg(labId, pageNum, pageSize)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+                return@map response.rows
+            }
+    }
+
+}

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

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

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

@@ -0,0 +1,27 @@
+package com.rc.httpcore.config
+
+import com.rc.httpcore.HttpConfig
+
+class ConfigFactory {
+
+    companion object {
+        val buildDebugConfig = ConfigParam.Builder()
+            .baseUrl("https://lab.sxitdlc.com/labAppTest/")
+            .signInCheckBaseUrl("http://10.20.10.7/labSystem/algorithm/")
+            .httpStrategy(HttpConfig.HTTP_STRATEGY_Retrofit)
+            .mqttServerUri("tcp://218.3.208.12:18830")
+            .mqttUName("dlc")
+            .mqttUPwd("123456")
+            .build()
+
+        val buildReleaseConfig = ConfigParam.Builder()
+            .baseUrl("https://lab.sxitdlc.com/labAppTest/")
+            .signInCheckBaseUrl("http://10.20.10.7/labSystem/algorithm/")
+            .httpStrategy(HttpConfig.HTTP_STRATEGY_Retrofit)
+            .mqttServerUri("tcp://218.3.208.12:18830")
+            .mqttUName("dlc")
+            .mqttUPwd("123456")
+            .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 signInCheckBaseUrl: String = builder.signInCheckBaseUrl
+
+    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 signInCheckBaseUrl: String
+        internal var httpStrategy: Int
+        internal var mqttServerUri: String
+        internal var mqttUName: String
+        internal var mqttUPwd: String
+
+        init {
+            baseUrl = "http://pc44sory.xiaomy.net:31738/"
+            signInCheckBaseUrl = "http://180.76.134.43:931/"
+            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 signInCheckBaseUrl(signInCheckBaseUrl: String): Builder {
+            this.signInCheckBaseUrl = signInCheckBaseUrl
+            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)
+        }
+
+    }
+}

+ 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)
+        }
+    }
+}

+ 16 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/exception/AICheckException.kt

@@ -0,0 +1,16 @@
+package com.rc.httpcore.exception
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+class AICheckException(code: String, message: String?, val data: String?) :
+    NetException(code, message) {
+
+    override fun toString(): String {
+        val message = super.toString()
+        return "$message ${data ?: ""}"
+    }
+
+}

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

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

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

@@ -0,0 +1,50 @@
+package com.rc.httpcore.interceptor
+
+import android.content.Intent
+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 {
+
+    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!!)
+        }
+        requestBuilder
+            .header("vName", HttpClient.vName)
+            .header("Accept", "application/json, text/plain, */*")
+            .build()
+
+        val newlyRequest = requestBuilder.build()
+        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
+    }
+}

+ 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
+}

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

@@ -0,0 +1,17 @@
+package com.rc.httpcore.vo
+
+open class CommonResponse(/*open var code: String, open var msg: String*/) {
+
+    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
+
+}

+ 11 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/CommonRowsResponse.kt

@@ -0,0 +1,11 @@
+package com.rc.httpcore.vo
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+class CommonRowsResponse<T> : CommonResponse() {
+    var rows: List<T>? = null
+    var total: Int = 0
+}

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

@@ -0,0 +1,16 @@
+package com.rc.httpcore.vo.request;
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+public class AccessTokenReq {
+
+    public String username;
+    public String password;
+
+//    Pair("username", "onecUser"),
+//    Pair("password", "admin123"))
+
+}

+ 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;
+    }
+}

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

@@ -0,0 +1,15 @@
+package com.rc.httpcore.vo.request;
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+public class AuthPwdReq {
+
+    public String num; // 设备号
+    public String labId; // 实验室ID
+    public String userId; // 用户Id
+    public String pwd; // 密码
+
+}

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

@@ -0,0 +1,16 @@
+package com.rc.httpcore.vo.request;
+
+import java.io.File;
+
+/**
+ * 签到-安全准入检测三合一 request
+ *
+ * @author ReiChin_
+ */
+public class CheckInAllReq {
+
+    public String id; // 进出记录id
+    public String subId; // 实验室id
+    public File file; // 图片文件
+
+}

+ 14 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/request/CommonSignInReq.java

@@ -0,0 +1,14 @@
+package com.rc.httpcore.vo.request;
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+public class CommonSignInReq {
+
+    public String num; // 设备号
+    public String labId; // 实验室ID
+    public String userId; // 人员ID
+
+}

+ 14 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/request/ControllerCMD.java

@@ -0,0 +1,14 @@
+package com.rc.httpcore.vo.request;
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+public class ControllerCMD {
+
+    public String id; // 设备ID
+    public String command; // 命令
+    public String num; // 设备编号[hardwareNUM]
+    public Object param; // 扩展参数
+}

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

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

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

@@ -0,0 +1,15 @@
+package com.rc.httpcore.vo.request;
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+public class HazardReq {
+
+    public int pageNum;
+    public int pageSize;
+
+    public String subId; // 实验室ID
+
+}

+ 14 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/request/OnLineUserReq.java

@@ -0,0 +1,14 @@
+package com.rc.httpcore.vo.request;
+
+/**
+ * 在线人员
+ *
+ * @author ReiChin_
+ */
+public class OnLineUserReq {
+
+    public String pageNum;
+    public String pageSize;
+    public String subId;
+
+}

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

@@ -0,0 +1,13 @@
+package com.rc.httpcore.vo.request;
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+public class PatrolSignInReq {
+
+    public String num;
+    public String userId;
+
+}

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

@@ -0,0 +1,13 @@
+package com.rc.httpcore.vo.request;
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+public class PatrolSignOutReq {
+
+    public String labId;
+    public String userId;
+
+}

+ 12 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/request/SignInReq.java

@@ -0,0 +1,12 @@
+package com.rc.httpcore.vo.request;
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+public class SignInReq {
+
+    public byte[] data;
+
+}

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

@@ -0,0 +1,14 @@
+package com.rc.httpcore.vo.response;
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+public class AccessTokenResp {
+
+    public String access_token;
+    public String user_id;
+    public String expires_in;
+    public String username;
+}

+ 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;
+
+}

+ 12 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/BannerImageBean.java

@@ -0,0 +1,12 @@
+package com.rc.httpcore.vo.response;
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+public class BannerImageBean {
+
+    public String imgUrl;
+
+}

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

@@ -0,0 +1,58 @@
+package com.rc.httpcore.vo.response;
+
+import java.util.List;
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+public class CheckMachineVo {
+
+    public String deptId;
+    public String deptName;
+    public String userId;
+    public String id;
+    public String checkRecordId; // 安全检查ID
+    public String checkMsg; // 通知消息
+    public String type; // 通知类型 1 一般整改,2 重大整改
+    public String subId;
+    public List<Detail> details;
+    public List<Detail> detailList;
+    public CheckRecord checkRecord;
+    public String notifyMsg; // 通知消息
+    public String checkDateStr; // 检查日期
+    public String jcRyxm; // 检查人
+    public String createTime; // 检查时间
+
+    public static class CheckRecord {
+        public String laboratoryName; // 实验室名称
+        public String jcDwName; // 检查单位
+    }
+
+    public static class Detail {
+        //                    public String createTime; //": "2022-01-12 21:07:56",
+//                    public String id; // ": 53,
+//                    public String checkId; // ": 42,
+//                    public String jcxId; // ": "24,",
+        public String jcxName; // 检查项名称
+        public String yhMs; // 隐患描述
+        public String optionCode; // 条款号- 对应条款
+        public String zgjzTime; // 整改完成期限
+        public String allCheckOptionName;
+//                    public String yhImg; // 隐患图片
+//                    public String yhZghImg; // ": "http://192.168.1.17:9300//statics/2022/01/12/3f0ff0a3-ebc8-40cd-92cd-d9d8d95c7680.png",
+//                    public String zgYyfx; // ": null,
+//                    public String zgCs; // ": null,
+//                    public String zgMs; // ": "33",
+//                    public String zmClImg; // ": null,
+//                    public String zmClMs; // ": null,
+//                    public String isLingshi; // ": 1,
+//                    public String isZg; // ": 0,
+//                    public String level1": "3",
+//                    public String level2": null,
+//                    public String level3": null,
+//                    public String level4": null
+    }
+
+}

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

@@ -0,0 +1,14 @@
+package com.rc.httpcore.vo.response;
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+public class ContentMachineVo {
+
+    public String createBy; // 发送人
+    public String createTime; // 发送时间
+    public String contentMsg; // 消息内容
+
+}

+ 20 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/DutyPersonVo.java

@@ -0,0 +1,20 @@
+package com.rc.httpcore.vo.response;
+
+import java.util.List;
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+public class DutyPersonVo {
+
+    public List<LabPersonVo> weekday1;
+    public List<LabPersonVo> weekday2;
+    public List<LabPersonVo> weekday3;
+    public List<LabPersonVo> weekday4;
+    public List<LabPersonVo> weekday5;
+    public List<LabPersonVo> weekday6;
+    public List<LabPersonVo> weekday7;
+
+}

+ 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;
+    }
+}

+ 35 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/HomeMiddleResp.java

@@ -0,0 +1,35 @@
+package com.rc.httpcore.vo.response;
+
+import java.util.List;
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+public class HomeMiddleResp {
+
+    public String adminName; // 负责人姓名
+    public String adminPhone; // 负责人电话
+    public String levelName; // 危险级别名称 1高危险级,2较高危险级,3中危险级,4一般危险级
+    public String filedColor; // 分级颜色 #13E63A
+    public String typeName; // 实验室类别 化学类
+    public String qrCodeUrl; // 二维码
+    public String deptName; // 责任单位
+    public String subjectName; // 实验室名称
+    public String buildName; // 楼栋名称
+    public String room; // 房间号
+
+    public List<SafeUserVo> safeUserVoList; // 安全责任人
+    public List<SafeClassify> xxpSubjectVoList; // 安全分类信息
+    public List<LaboratoryVo.InCheckItem> inCheck; // AI检测项(准入)
+    public List<LaboratoryVo.InCheckItem> inspectInCheck; // AI检测项(巡查)
+    public List<String> outCheck; // 离开检查项
+
+    public static class SafeUserVo {
+        public String id;
+        public String safeUserName;
+        public String safeUserPhone;
+    }
+
+}

+ 16 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/HomeRightResp.java

@@ -0,0 +1,16 @@
+package com.rc.httpcore.vo.response;
+
+import java.util.List;
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+public class HomeRightResp {
+
+    public List<LabPersonVo> tentativeUser; // 实验人员
+    public List<LabPersonVo> dutyUser; // 值班人员
+    public List<LabPersonVo> securityUser; // 准入人员
+
+}

+ 15 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/HomeTopResp.java

@@ -0,0 +1,15 @@
+package com.rc.httpcore.vo.response;
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+public class HomeTopResp {
+
+    public String currentTime; // 当前时间
+    public String schoolName; // 学校名称
+    public String circularLogo; // LOGO
+    public String subjectName; // 实验室名称
+
+}

+ 27 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/LabBulletinBoardVo.java

@@ -0,0 +1,27 @@
+package com.rc.httpcore.vo.response;
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+public class LabBulletinBoardVo {
+
+    public String cmd; // 命令
+    public String describe;// 功能描述
+    public String formatVal; // 格式化值
+    public String funNum; // 功能编码
+    public String hardwareNum;
+    public String icon; // 功能图标
+    public int order; // 排序
+    public String unit; // 功能值单位
+    public String val; // 传感器功能值
+
+    public String formatWaring() {
+        String describe_ = null == describe ? "" : describe;
+        String val_ = null == val ? "" : val;
+        String unit_ = null == unit ? "" : unit;
+        return String.format("%s:%s%s", describe_, val_, unit_);
+    }
+
+}

+ 17 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/LabConfig.java

@@ -0,0 +1,17 @@
+package com.rc.httpcore.vo.response;
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+public class LabConfig {
+
+    public String labId; // 设备ID
+    public String isStart; // 是否启用信息牌 0不启用 1启用
+    public int logoutTime; // 自动注销时间 秒
+    public int returnTime; // 自动返回时间 秒
+    public String authType; // 身份核验方式 1-指纹 2-人脸/指纹/刷卡/密码 3-人脸/指纹 4-刷卡+密码 5-人脸+密码 6-人脸+刷卡
+    public String isRelationGuard; // 是否关联门禁  1 关联  2不关联
+
+}

+ 18 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/LabHazardVo.java

@@ -0,0 +1,18 @@
+package com.rc.httpcore.vo.response;
+
+/**
+ * 危险源信息
+ *
+ * @author ReiChin_
+ */
+public class LabHazardVo {
+
+    public String id;
+    public String chName; // 中文名称
+    public String code; // 编号
+    public String content; // 内容
+    public String qrCodeUrl; // 二维码地址
+    public Integer scanCount; // 查看次数
+    public String subName;
+
+}

+ 26 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/LabPersonVo.java

@@ -0,0 +1,26 @@
+package com.rc.httpcore.vo.response;
+
+/**
+ * 在线人员
+ *
+ * @author ReiChin_
+ */
+public class LabPersonVo {
+
+    public String validBeginTime; // 准入期限 yyyy-MM-dd
+    public String validEndTime; // 准入期限 yyyy-MM-dd
+    public String id; // 人员ID
+    public String avatar; // 人员照片
+    public String jobNumber; // 工号
+
+    public String dutyTime; // 值班日期、巡查日期 yyyy-MM-dd
+    public String userPhone; // 联系电话
+    public String userName; // 人员名称
+
+    public String signTime; // 签到时间 yyyy-MM-dd HH:mm:ss
+    public String signNumber; // 签到次数
+    public String signOut; // 离开时间 yyyy-MM-dd HH:mm:ss
+    public String userId; // 人员ID
+
+
+}

+ 76 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/LaboratoryVo.java

@@ -0,0 +1,76 @@
+package com.rc.httpcore.vo.response;
+
+import java.util.List;
+
+/**
+ * 实验室信息
+ *
+ * @author ReiChin_
+ */
+public class LaboratoryVo {
+
+    public String address; // 实验室地址
+    public String adminName; // 负责人名称
+    public String adminPhone; // 负责人电话
+    public String dangerName; // 危险级别名称
+    public String dangerLevel; // 危险级别 1高危险级,2较高危险级,3中危险级,4一般危险级
+    public String dangerColor; // 分级颜色 #13E63A
+    public String deptName; // 所属院系名称
+    public String online; // 当前人数
+    public List<InCheckItem> inCheck;
+    public List<InCheckItem> inspectInCheck;
+    public List<String> outCheck; // 离开检查项
+    public String safeUserName; // 安全负责人名称
+    public String safeUserPhone; // 安全负责人手机号
+    public String subClass; // 学科类型
+    public String subId; // 实验室ID
+    public String subName; // 实验室名称
+    public SubSafeInfo subSafeInfo; // 实验室安全信息
+    public String qrCodeUrl; // 实验室二维码
+    public String signMap; // 签到签出示意图
+    public String videoCover; // 视屏封面
+    public String operationGuide; // 操作指南
+    public String rectangleLogo; // 长方形logo
+    public String adminUserDesc; // 实验室负责人
+    public String safeUserDesc; // 安全责任人
+    public String buildName; // 楼栋名称
+    public String room; // 房间号
+
+    public List<SafeClassify> safeClassifyList;
+    public List<String> specialClassify;
+    public List<List<String>> specialClassify2;
+    public List<SafePersonInfo> safePersonList;
+
+    public static class SubSafeInfo {
+        public List<String> safeSigns; // 安全警示标示
+        public List<String> hazardCategory; // 主要危险类别
+        public List<String> riskMeasure; // 安全防范措施
+        public List<String> outfire; // 灭火要点
+        public String qrCode; // 二维码
+    }
+
+    /**
+     * 1:佩戴护目镜   2:穿实验服  3:戴手套
+     */
+    public static class InCheckItem {
+        public String name;
+        public String code;
+    }
+
+    public static class SafePersonInfo {
+        public String label;
+        public String name;
+        public String phone;
+
+        public SafePersonInfo(String label, String name) {
+            this(label, name, null);
+        }
+
+        public SafePersonInfo(String label, String name, String phone) {
+            this.label = label;
+            this.name = name;
+            this.phone = phone;
+        }
+    }
+
+}

+ 86 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/LotDeviceVo.java

@@ -0,0 +1,86 @@
+package com.rc.httpcore.vo.response;
+
+import java.util.List;
+
+/**
+ * 可控制设备
+ *
+ * @author ReiChin_
+ */
+public class LotDeviceVo {
+
+    public static final String LOT_OFF = "4";
+    public static final String LOT_ON = "3";
+
+    // 设备不在线
+    public static final String SWITCH_ENUM_NO_ONLINE = "NO_ONLINE";
+    // 开关打开
+    public static final String SWITCH_ENUM_ON = "ON";
+    // 开关关闭
+    public static final String SWITCH_ENUM_OFF = "OFF";
+
+    public String id;
+    public String hardwareNUM; // 设备唯一编码
+    public State state; // 设备状态
+    public String hardwareName; // 设备名称
+    public String subId; // 所属实验室ID
+    public HardwareTypeEnum hardwareTypeEnum; // 设备类型
+    public String switchEnums;
+    public FunctionStatus functionStatus; // 当前功能状态
+    public List<FunctionStatus> functionStatusList; // 所有功能状态
+    public String triggerModes; // 1:预案排风 2:人工排风 3:定时排风
+
+    /*
+     * "state":{
+     *         "code":4,
+     *         "name":"关闭",
+     *         "dictKey":4
+     *     }
+     */
+    public static class State {
+        public String code;
+        public String name;
+        public String dictKey;
+    }
+
+    /*
+     * "functionStatus":{
+     *         "funNum":null,
+     *         "describe":"开关状态:关闭",
+     *         "code":"0",
+     *         "cmd":"0"
+     *     }
+     */
+    public static class FunctionStatus {
+//        public String hardwareNum;
+//        public String icon;
+//        public String order;
+//        public String commTypeEnums;
+        public String cmd; // 命令
+        public String code; // 功能状态值
+        public String describe; // 功能描述
+        public String funNum; // 功能编码
+    }
+
+    /*
+     * "hardwareTypeEnum":{
+     *         "code":1,
+     *         "name":"电源开关",
+     *         "img":"",
+     *         "hardwareTypeCode":1,
+     *         "dictKey":1,
+     *         "hardwareTypeName":"电源开关",
+     *         "enumName":"SWITCH"
+     *     }
+     */
+    public static class HardwareTypeEnum {
+        public String code;
+        public String name;
+        public String img;
+        public String hardwareTypeCode;
+        public String dictKey;
+        public String hardwareTypeName;
+        public String enumName;
+    }
+
+}

+ 93 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/MonitorVo.java

@@ -0,0 +1,93 @@
+package com.rc.httpcore.vo.response;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+public class MonitorVo implements Parcelable {
+
+    public String app; // tp",
+    public String streamId; // 440102004920000000010064_34020000001320000064",
+    public String deviceID; // 440102004920000000010064",
+    public String channelId; // 34020000001320000064",
+    public String flv; // http://192.168.1.88:8230/rtp/440102004920000000010064_34020000001320000064.flv",
+    public String ws_flv; // ws://192.168.1.88:8230/rtp/440102004920000000010064_34020000001320000064.flv",
+    public String fmp4; // http://192.168.1.88:8230/rtp/440102004920000000010064_34020000001320000064.live.mp4",
+    public String ws_fmp4; // ws://192.168.1.88:8230/rtp/440102004920000000010064_34020000001320000064.live.mp4",
+    public String hls; // http://192.168.1.88:8230/rtp/440102004920000000010064_34020000001320000064/hls.m3u8",
+    public String ws_hls; // ws://192.168.1.88:8230/rtp/440102004920000000010064_34020000001320000064/hls.m3u8",
+    public String ts; // http://192.168.1.88:8230/rtp/440102004920000000010064_34020000001320000064.live.ts",
+    public String ws_ts; // ws://192.168.1.88:8230/rtp/440102004920000000010064_34020000001320000064.live.ts",
+    public String rtmp; // tmp://192.168.1.88:1935/rtp/440102004920000000010064_34020000001320000064",
+    public String rtsp; // tsp://192.168.1.88:554/rtp/440102004920000000010064_34020000001320000064",
+    public String rtc; // http://192.168.1.88:8230/index/api/webrtc?app=rtp&stream=440102004920000000010064_34020000001320000064&type=play",
+    public String mediaServerId; // your_server_id",
+
+    public MonitorVo(String app, String fmp4) {
+        this.app = app;
+        this.fmp4 = fmp4;
+    }
+
+    public MonitorVo() {
+    }
+
+    protected MonitorVo(Parcel in) {
+        app = in.readString();
+        streamId = in.readString();
+        deviceID = in.readString();
+        channelId = in.readString();
+        flv = in.readString();
+        ws_flv = in.readString();
+        fmp4 = in.readString();
+        ws_fmp4 = in.readString();
+        hls = in.readString();
+        ws_hls = in.readString();
+        ts = in.readString();
+        ws_ts = in.readString();
+        rtmp = in.readString();
+        rtsp = in.readString();
+        rtc = in.readString();
+        mediaServerId = in.readString();
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(app);
+        dest.writeString(streamId);
+        dest.writeString(deviceID);
+        dest.writeString(channelId);
+        dest.writeString(flv);
+        dest.writeString(ws_flv);
+        dest.writeString(fmp4);
+        dest.writeString(ws_fmp4);
+        dest.writeString(hls);
+        dest.writeString(ws_hls);
+        dest.writeString(ts);
+        dest.writeString(ws_ts);
+        dest.writeString(rtmp);
+        dest.writeString(rtsp);
+        dest.writeString(rtc);
+        dest.writeString(mediaServerId);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Creator<MonitorVo> CREATOR = new Creator<MonitorVo>() {
+        @Override
+        public MonitorVo createFromParcel(Parcel in) {
+            return new MonitorVo(in);
+        }
+
+        @Override
+        public MonitorVo[] newArray(int size) {
+            return new MonitorVo[size];
+        }
+    };
+}

+ 16 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/NoticeDetailVo.java

@@ -0,0 +1,16 @@
+package com.rc.httpcore.vo.response;
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+public class NoticeDetailVo {
+
+    public String content; // 消息内容
+    public String deptName; // 责任单位
+    public String createTime; // 创建时间
+    public String createBy; // 创建人
+    public String checkMsg; // 整改消息内容
+
+}

+ 15 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/NoticeSummaryVo.java

@@ -0,0 +1,15 @@
+package com.rc.httpcore.vo.response;
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+public class NoticeSummaryVo {
+
+    public String id;
+    public String messContent;
+    public String messType;
+    public String createTime;
+
+}

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

@@ -0,0 +1,33 @@
+package com.rc.httpcore.vo.response;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 验室安全制度
+ *
+ * @author ReiChin_
+ */
+public class SafeBook implements Serializable {
+
+    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 qrCodeUrl; // 二维码地址
+    public String remark;
+    public String scanCount; // 查看次数
+    public String searchValue;
+    public String type; // 制度类别 取自字典 safe_type
+    public String typeLable;
+    public String updateBy;
+    public String updateTime; // 更新时间
+    public String userId; // 用户ID(数据权限)
+
+}

+ 20 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/SafeClassify.java

@@ -0,0 +1,20 @@
+package com.rc.httpcore.vo.response;
+
+import java.util.List;
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+public class SafeClassify {
+
+    public String classifyName; // 分类名称 eg:灭火要点
+    public int classifyType; // 分类类型 1-文字 2-图片
+    public String sort; // 排序
+    public String isSpecial; // 是否为特殊标记 1-否 2-是
+    public String isShow;
+    public String showColour; // 头部背景色 #B88B7D
+    public List<String> classifyList; // 分类内容
+
+}

+ 51 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/SignFaceVo.java

@@ -0,0 +1,51 @@
+package com.rc.httpcore.vo.response;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+public class SignFaceVo implements Parcelable {
+
+    public String id;
+    public String code;
+
+    public SignFaceVo() {
+    }
+
+    public SignFaceVo(String id, String code) {
+        this.id = id;
+        this.code = code;
+    }
+
+    protected SignFaceVo(Parcel in) {
+        id = in.readString();
+        code = in.readString();
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(id);
+        dest.writeString(code);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Creator<SignFaceVo> CREATOR = new Creator<SignFaceVo>() {
+        @Override
+        public SignFaceVo createFromParcel(Parcel in) {
+            return new SignFaceVo(in);
+        }
+
+        @Override
+        public SignFaceVo[] newArray(int size) {
+            return new SignFaceVo[size];
+        }
+    };
+}

+ 154 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/SignInCheckResp.java

@@ -0,0 +1,154 @@
+package com.rc.httpcore.vo.response;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.List;
+
+/**
+ * 签到验证 Response
+ *
+ * @author ReiChin_
+ */
+public class SignInCheckResp implements Parcelable {
+
+    public String code; // 验证码
+    public List<String> message; // 验证信息
+    public SingInUser singInUser; // 签到用户信息
+    public SingInViolation singInViolation; // 签到违规信息
+    public boolean state;
+    public String subId;
+    public List<String> tips; // 一般提示信息
+
+    public SignInCheckResp() {
+    }
+
+    protected SignInCheckResp(Parcel in) {
+        code = in.readString();
+        message = in.createStringArrayList();
+        singInUser = in.readParcelable(SingInUser.class.getClassLoader());
+        singInViolation = in.readParcelable(SingInViolation.class.getClassLoader());
+        state = in.readByte() != 0;
+        subId = in.readString();
+        tips = in.createStringArrayList();
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(code);
+        dest.writeStringList(message);
+        dest.writeParcelable(singInUser, flags);
+        dest.writeParcelable(singInViolation, flags);
+        dest.writeByte((byte) (state ? 1 : 0));
+        dest.writeString(subId);
+        dest.writeStringList(tips);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Creator<SignInCheckResp> CREATOR = new Creator<SignInCheckResp>() {
+        @Override
+        public SignInCheckResp createFromParcel(Parcel in) {
+            return new SignInCheckResp(in);
+        }
+
+        @Override
+        public SignInCheckResp[] newArray(int size) {
+            return new SignInCheckResp[size];
+        }
+    };
+
+    public static class SingInUser implements Parcelable {
+        public String deptId; // 部门id
+        public String expirationDate; // 失效时间
+        public String nickName; // 用户姓名
+        public String type; // 用户类型
+        public String userId; // 用户id
+        public String userName; // 用户账户
+        public String avatar; // 用户头像
+
+        public SingInUser() {
+        }
+
+        protected SingInUser(Parcel in) {
+            deptId = in.readString();
+            expirationDate = in.readString();
+            nickName = in.readString();
+            type = in.readString();
+            userId = in.readString();
+            userName = in.readString();
+            avatar = in.readString();
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeString(deptId);
+            dest.writeString(expirationDate);
+            dest.writeString(nickName);
+            dest.writeString(type);
+            dest.writeString(userId);
+            dest.writeString(userName);
+            dest.writeString(avatar);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        public static final Creator<SingInUser> CREATOR = new Creator<SingInUser>() {
+            @Override
+            public SingInUser createFromParcel(Parcel in) {
+                return new SingInUser(in);
+            }
+
+            @Override
+            public SingInUser[] newArray(int size) {
+                return new SingInUser[size];
+            }
+        };
+    }
+
+    public static class SingInViolation implements Parcelable {
+        public String createTime; // 违约时间
+        public String subjectName; // 实验室名称
+        public String violationContent; // 违规内容
+
+        public SingInViolation() {
+        }
+
+        protected SingInViolation(Parcel in) {
+            createTime = in.readString();
+            subjectName = in.readString();
+            violationContent = in.readString();
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeString(createTime);
+            dest.writeString(subjectName);
+            dest.writeString(violationContent);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        public static final Creator<SingInViolation> CREATOR = new Creator<SingInViolation>() {
+            @Override
+            public SingInViolation createFromParcel(Parcel in) {
+                return new SingInViolation(in);
+            }
+
+            @Override
+            public SingInViolation[] newArray(int size) {
+                return new SingInViolation[size];
+            }
+        };
+    }
+
+}

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

@@ -0,0 +1,14 @@
+package com.rc.httpcore.vo.response;
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+public class SpeakInfo {
+
+    public String result;
+    public String url;
+    public String text;
+
+}

+ 55 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/UserFingerVo.java

@@ -0,0 +1,55 @@
+package com.rc.httpcore.vo.response;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+public class UserFingerVo extends UserVo implements Parcelable {
+
+    public String id; // 指纹id
+    public String finger; // 指纹
+    public String fingerKey; // 指纹位置
+    public String num; // 设备编号
+
+    public UserFingerVo() {
+    }
+
+    protected UserFingerVo(Parcel in) {
+        super(in);
+        id = in.readString();
+        finger = in.readString();
+        fingerKey = in.readString();
+        num = in.readString();
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        dest.writeString(id);
+        dest.writeString(finger);
+        dest.writeString(fingerKey);
+        dest.writeString(num);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Creator<UserFingerVo> CREATOR = new Creator<UserFingerVo>() {
+        @Override
+        public UserFingerVo createFromParcel(Parcel in) {
+            return new UserFingerVo(in);
+        }
+
+        @Override
+        public UserFingerVo[] newArray(int size) {
+            return new UserFingerVo[size];
+        }
+    };
+
+}

+ 70 - 0
HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/UserVo.java

@@ -0,0 +1,70 @@
+package com.rc.httpcore.vo.response;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+public class UserVo implements Parcelable {
+
+    public String userId; // 人员id
+    public String userName; // 人员姓名
+    public String userType; // 人员类型 00-系统用户 11-教职工 22-学生 99-大屏
+    public String pageType; // 人员分类 1-1类页面  2-2类页面
+    public String isWhite;
+    public String isDutyUser; // 是否值班人员 1-是 0-不是
+
+    public UserVo() {
+    }
+
+    protected UserVo(Parcel in) {
+        userId = in.readString();
+        userName = in.readString();
+        userType = in.readString();
+        pageType = in.readString();
+        isWhite = in.readString();
+        isDutyUser = in.readString();
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(userId);
+        dest.writeString(userName);
+        dest.writeString(userType);
+        dest.writeString(pageType);
+        dest.writeString(isWhite);
+        dest.writeString(isDutyUser);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Creator<UserVo> CREATOR = new Creator<UserVo>() {
+        @Override
+        public UserVo createFromParcel(Parcel in) {
+            return new UserVo(in);
+        }
+
+        @Override
+        public UserVo[] newArray(int size) {
+            return new UserVo[size];
+        }
+    };
+
+    @Override
+    public String toString() {
+        return "UserVo{" +
+                "userId='" + userId + '\'' +
+                ", userName='" + userName + '\'' +
+                ", userType='" + userType + '\'' +
+                ", pageType='" + pageType + '\'' +
+                ", isWhite='" + isWhite + '\'' +
+                ", isDutyUser='" + isDutyUser + '\'' +
+                '}';
+    }
+}

+ 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)

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

@@ -0,0 +1,30 @@
+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()
+    }
+
+}

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

@@ -0,0 +1,106 @@
+package com.rc.core.ui.activity
+
+import android.graphics.Color
+import android.os.Build
+import android.os.Bundle
+import android.view.View
+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) {
+        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)
+    }
+
+}

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

@@ -0,0 +1,166 @@
+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 io.reactivex.disposables.Disposable
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+abstract class RcRefreshActivity<T, VB : ViewBinding>:
+    RcBaseActivity<VB>(),
+    OnLoadMoreListener,
+    OnItemClickListener,
+    OnItemChildClickListener {
+
+    companion object {
+        const val FIRST_PAGE = 1
+        const val PAGE_SIZE = 15
+    }
+
+    protected abstract val mSrlRefresh: SwipeRefreshLayout
+    protected abstract val mRvContent: RecyclerView
+    protected abstract val mAdapter: BaseQuickAdapter<T, BaseViewHolder>
+
+    protected var mCurrentPage = FIRST_PAGE
+    protected var mRefresh = false
+
+    override fun initViews(savedInstanceState: Bundle?) {
+        initSwipeRecyclerView()
+    }
+
+    private fun initSwipeRecyclerView() {
+        mSrlRefresh.setOnRefreshListener { loadData(true) }
+
+        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)
+    }
+
+    override fun initData() {
+        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)
+            }
+        }
+    }
+
+    protected fun loadData(refresh: Boolean) {
+        if (refresh) {
+            mCurrentPage = FIRST_PAGE
+            // 这里的作用是防止下拉刷新的时候还可以上拉加载
+            mAdapter.loadMoreModule.isEnableLoadMore = false
+        }
+        mRefresh = refresh
+        showLoadingView()
+        queryData()?.let { addDisposable(it) }
+    }
+
+    private fun showLoadingView() {
+//        showLoading()
+    }
+
+    /**
+     * 数据加载成功的处理逻辑
+     *
+     * @param data 从服务端查询的数据
+     */
+    protected open fun dispatchLoadDataSuccess(data: List<T>?) {
+//        dismissLoading()
+        if (isDestroyed) return
+
+        if (mRefresh) {
+            // 停止刷新
+            mSrlRefresh.isRefreshing = false
+
+            mAdapter.setNewInstance(data?.toMutableList())
+            mAdapter.loadMoreModule.loadMoreComplete()
+            if (data.isNullOrEmpty()) {
+                mAdapter.setEmptyView(R.layout.view_list_empty)
+            }
+            if (null == data || data.size < PAGE_SIZE) {
+                mAdapter.loadMoreModule.loadMoreEnd(goneLoadMoreView())
+            }
+        } else {
+            if (null == data || data.size < PAGE_SIZE) {
+                data?.let { mAdapter.addData(it) }
+                mAdapter.loadMoreModule.loadMoreEnd(goneLoadMoreView())
+            } else {
+                mAdapter.addData(data)
+                mAdapter.loadMoreModule.loadMoreComplete()
+            }
+        }
+    }
+
+    /**
+     * 数据加载失败的处理逻辑
+     */
+    protected open fun dispatchLoadDataFailure(throwable: Throwable) {
+//        dismissLoading()
+        if (isDestroyed) return
+
+        if (mRefresh) {
+            mSrlRefresh.isRefreshing = false
+            mAdapter.setEmptyView(R.layout.view_list_empty)
+        } else {
+            mCurrentPage = if (mCurrentPage-- < FIRST_PAGE) FIRST_PAGE else mCurrentPage
+            mAdapter.loadMoreModule.loadMoreEnd(goneLoadMoreView())
+        }
+        throwable.printStackTrace()
+        showNetError(throwable)
+    }
+
+    protected open fun goneLoadMoreView() = false
+
+    /**
+     * 查询数据
+     */
+    abstract fun queryData(): Disposable?
+
+    override fun onItemClick(adapter: BaseQuickAdapter<*, *>, view: View, position: Int) {
+    }
+
+    override fun onItemChildClick(adapter: BaseQuickAdapter<*, *>, view: View, position: Int) {
+    }
+
+}

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

@@ -0,0 +1,36 @@
+package com.rc.core.ui.common
+
+import android.content.Context
+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?
+
+}

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

@@ -0,0 +1,93 @@
+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)
+            it.show()
+        }
+    }
+
+    override fun dismissLoading() {
+        mPdLoading?.let {
+            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)!!)
+            }
+        }
+    }
+}

+ 101 - 0
RcCore/src/main/java/com/rc/core/ui/dialog/LoadingDialog.kt

@@ -0,0 +1,101 @@
+package com.rc.core.ui.dialog
+
+import android.app.Dialog
+import android.content.Context
+import android.os.Bundle
+import android.view.*
+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
+//    }
+//
+//}

+ 116 - 0
RcCore/src/main/java/com/rc/core/ui/dialog/RcBaseDialog.kt

@@ -0,0 +1,116 @@
+package com.rc.core.ui.dialog
+
+import android.graphics.Color
+import android.graphics.drawable.ColorDrawable
+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) {
+        fragmentManager?.let {
+            mUIDelegate.showLoading(requireContext(), 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)
+    }
+
+}

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

@@ -0,0 +1,83 @@
+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) {
+        context?.let { mUIDelegate.showLoading(it, 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
+
+}

+ 169 - 0
RcCore/src/main/java/com/rc/core/ui/fragment/RcRefreshFragment.kt

@@ -0,0 +1,169 @@
+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.R
+import io.reactivex.disposables.Disposable
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+abstract class RcRefreshFragment<T, VB : ViewBinding> :
+    RcLazyFragment<VB>(),
+    OnLoadMoreListener,
+    OnItemClickListener,
+    OnItemChildClickListener {
+
+    companion object {
+        const val FIRST_PAGE = 1
+        const val PAGE_SIZE = 15
+    }
+
+    protected abstract val mSrlRefresh: SwipeRefreshLayout
+    protected abstract val mRvContent: RecyclerView
+    protected abstract val mAdapter: BaseQuickAdapter<T, BaseViewHolder>
+
+    protected var mCurrentPage = FIRST_PAGE
+    protected var mRefresh = false
+
+    override fun initViews(savedInstanceState: Bundle?) {
+        initSwipeRecyclerView()
+    }
+
+    private fun initSwipeRecyclerView() {
+        mSrlRefresh.setOnRefreshListener { loadData(true) }
+
+        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(requireContext())
+    }
+
+    override fun onLoadData() {
+        mSrlRefresh.isRefreshing = true
+        loadData(true)
+    }
+
+    override fun loadDateCompleted() = mAdapter.data.isNotEmpty()
+
+    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)
+            }
+        }
+    }
+
+    protected fun loadData(refresh: Boolean) {
+        if (refresh) {
+            mCurrentPage = FIRST_PAGE
+            // 这里的作用是防止下拉刷新的时候还可以上拉加载
+            mAdapter.loadMoreModule.isEnableLoadMore = false
+        }
+        mRefresh = refresh
+        showLoadingView()
+        queryData()?.let { addDisposable(it) }
+    }
+
+    private fun showLoadingView() {
+//        showLoading()
+    }
+
+    /**
+     * 数据加载成功的处理逻辑
+     *
+     * @param data 从服务端查询的数据
+     */
+    protected open fun dispatchLoadDataSuccess(data: List<T>?) {
+//        dismissLoading()
+        if (isDetached) return
+
+        if (mRefresh) {
+            // 停止刷新
+            mSrlRefresh.isRefreshing = false
+
+            mAdapter.setNewInstance(data?.toMutableList())
+            mAdapter.loadMoreModule.loadMoreComplete()
+            if (data.isNullOrEmpty()) {
+                mAdapter.setEmptyView(R.layout.view_list_empty)
+            }
+            if (null == data || data.size < PAGE_SIZE) {
+                mAdapter.loadMoreModule.loadMoreEnd(goneLoadMoreView())
+            }
+        } else {
+            if (null == data || data.size < PAGE_SIZE) {
+                data?.let { mAdapter.addData(it) }
+                mAdapter.loadMoreModule.loadMoreEnd(goneLoadMoreView())
+            } else {
+                mAdapter.addData(data)
+                mAdapter.loadMoreModule.loadMoreComplete()
+            }
+        }
+    }
+
+    /**
+     * 数据加载失败的处理逻辑
+     */
+    protected open fun dispatchLoadDataFailure(throwable: Throwable) {
+//        dismissLoading()
+        if (isDetached) return
+
+        if (mRefresh) {
+            mSrlRefresh.isRefreshing = false
+            mAdapter.setEmptyView(R.layout.view_list_empty)
+        } else {
+            mCurrentPage = if (mCurrentPage-- < FIRST_PAGE) FIRST_PAGE else mCurrentPage
+            mAdapter.loadMoreModule.loadMoreEnd(goneLoadMoreView())
+        }
+        throwable.printStackTrace()
+        showNetError(throwable)
+    }
+
+    protected open fun goneLoadMoreView() = false
+
+    /**
+     * 查询数据
+     */
+    abstract fun queryData(): Disposable?
+
+    override fun onItemClick(adapter: BaseQuickAdapter<*, *>, view: View, position: Int) {
+    }
+
+    override fun onItemChildClick(adapter: BaseQuickAdapter<*, *>, view: View, position: Int) {
+    }
+
+}

+ 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
+    }
+
+}

+ 33 - 0
RcCore/src/main/java/com/rc/core/ui/widget/SwipeViewPager.kt

@@ -0,0 +1,33 @@
+package com.rc.core.ui.widget
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.MotionEvent
+import androidx.viewpager.widget.ViewPager
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+class SwipeViewPager @JvmOverloads constructor(
+    context: Context,
+    attrs: AttributeSet? = null
+) :
+    ViewPager(context, attrs) {
+
+    private var swipeEnabled: Boolean = false
+
+    fun setSwipeEnabled(enabled: Boolean) {
+        this.swipeEnabled = enabled
+    }
+
+    override fun onTouchEvent(ev: MotionEvent?): Boolean {
+        return swipeEnabled && super.onTouchEvent(ev)
+    }
+
+    override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
+        return swipeEnabled && super.onInterceptTouchEvent(ev)
+    }
+
+}

+ 115 - 0
RcCore/src/main/java/com/rc/core/ui/widget/decoration/GridSpacingItemDecoration.java

@@ -0,0 +1,115 @@
+package com.rc.core.ui.widget.decoration;
+
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * 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; //是否包含边缘
+
+    private Drawable mDivider;
+
+    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
+            }
+        }
+    }
+
+    public void setDrawable(@NotNull Drawable drawable) {
+        this.mDivider = drawable;
+    }
+
+    @Override
+    public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
+        if (parent.getLayoutManager() == null || mDivider == null) return;
+
+        drawVertical(c, parent);
+        drawHorizontal(c, parent);
+    }
+
+    /**
+     * 绘制垂直方向的分割线
+     *
+     * @param c
+     * @param parent
+     */
+    private void drawVertical(Canvas c, RecyclerView parent) {
+        int childCount = parent.getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            View childView = parent.getChildAt(i);
+            RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) childView.getLayoutParams();
+            int top = childView.getTop() - params.topMargin;
+            int bottom = childView.getBottom() + params.bottomMargin;
+            int left = childView.getRight() + params.rightMargin;
+            int right = left + mDivider.getIntrinsicWidth();
+            // 计算水平分割线的位置
+            mDivider.setBounds(left, top, right, bottom);
+            mDivider.draw(c);
+        }
+    }
+
+    /**
+     * 绘制水平方向的分割线
+     *
+     * @param c
+     * @param parent
+     */
+    private void drawHorizontal(Canvas c, RecyclerView parent) {
+        int childCount = parent.getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            View childView = parent.getChildAt(i);
+            RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) childView.getLayoutParams();
+            int left = childView.getLeft() - params.leftMargin;
+            int right = childView.getRight() + mDivider.getIntrinsicWidth() + params.rightMargin;
+            int top = childView.getBottom() + params.bottomMargin;
+            int bottom = top + mDivider.getIntrinsicHeight();
+            // 计算水平分割线的位置
+            mDivider.setBounds(left, top, right, bottom);
+            mDivider.draw(c);
+        }
+    }
+
+}

+ 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.eboard", "com.dlc.eboard.ui.SplashActivity")
+            startApp(appContext, "com.dlc.eboard", "com.dlc.eboard.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.eboard/com.dlc.eboard.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 = "laboratory-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
+    }
+
+}

+ 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));
+//    }
+}

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

@@ -0,0 +1,17 @@
+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 CharSequence?.ifNullOrEmpty(defaultValue: CharSequence = ""): CharSequence {
+    return if (isNullOrEmpty()) defaultValue else this
+}

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


Някои файлове не бяха показани, защото твърде много файлове са промени