Forráskód Böngészése

学习考试一体机

sunqiang 1 éve
commit
5ee1f30418
100 módosított fájl, 5860 hozzáadás és 0 törlés
  1. 10 0
      .gitignore
  2. 1 0
      HttpCoreLibrary/.gitignore
  3. 50 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. 70 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/HttpClient.kt
  8. 33 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/HttpConfig.kt
  9. 331 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/client/ApiRepository.kt
  10. 178 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. 216 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/client/retrofit/ApiService.java
  14. 455 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. 30 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/config/OkHttpDNS.kt
  19. 22 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/converter/NullOnEmptyConverterFactory.kt
  20. 16 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/exception/AICheckException.kt
  21. 9 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/exception/NetException.kt
  22. 43 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/interceptor/HttpLoggingInterceptorLog.kt
  23. 50 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/interceptor/TokenHeaderInterceptor.kt
  24. 26 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/interceptor/formatter/GsonFormatter.kt
  25. 37 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/interceptor/formatter/JSONFormatter.kt
  26. 26 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/interceptor/formatter/OrgJsonFormatter.kt
  27. 14 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/net/DownloadListener.kt
  28. 156 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/net/DownloadTask.kt
  29. 5 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/CommonDataResponse.kt
  30. 17 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/CommonResponse.kt
  31. 11 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/CommonRowsResponse.kt
  32. 16 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/request/AccessTokenReq.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/ControllerCMD.java
  35. 13 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/request/FaceCompareReq.java
  36. 15 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/request/HazardReq.java
  37. 14 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/request/OnLineUserReq.java
  38. 12 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/request/SignInReq.java
  39. 15 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/AccessTokenResp.java
  40. 13 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/ApkInfoResp.java
  41. 12 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/BannerImageBean.java
  42. 58 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/CheckMachineVo.java
  43. 14 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/ContentMachineVo.java
  44. 36 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/HazardBook.java
  45. 27 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/LabBulletinBoardVo.java
  46. 18 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/LabHazardVo.java
  47. 15 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/LabPersonVo.java
  48. 52 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/LaboratoryVo.java
  49. 86 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/LotDeviceVo.java
  50. 33 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/SafeBook.java
  51. 36 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/SignInCheckResp.java
  52. 14 0
      HttpCoreLibrary/src/main/java/com/rc/httpcore/vo/response/SpeakInfo.java
  53. 8 0
      LICENSE
  54. 3 0
      README.md
  55. 1 0
      RcCore/.gitignore
  56. 52 0
      RcCore/build.gradle
  57. 0 0
      RcCore/consumer-rules.pro
  58. BIN
      RcCore/libs/tbs_sdk_v4.3.0.165_20210628_103707.jar
  59. 21 0
      RcCore/proguard-rules.pro
  60. 5 0
      RcCore/src/main/AndroidManifest.xml
  61. 8 0
      RcCore/src/main/java/com/rc/core/event/RefreshEvent.kt
  62. 27 0
      RcCore/src/main/java/com/rc/core/log/RcLog.kt
  63. 71 0
      RcCore/src/main/java/com/rc/core/ui/ActivityCollector.kt
  64. 109 0
      RcCore/src/main/java/com/rc/core/ui/activity/RcBaseActivity.kt
  65. 235 0
      RcCore/src/main/java/com/rc/core/ui/activity/RcRefreshActivity.kt
  66. 36 0
      RcCore/src/main/java/com/rc/core/ui/common/AbsUIDelegate.kt
  67. 25 0
      RcCore/src/main/java/com/rc/core/ui/common/IUIListener.kt
  68. 93 0
      RcCore/src/main/java/com/rc/core/ui/common/UIDelegateImpl.kt
  69. 101 0
      RcCore/src/main/java/com/rc/core/ui/dialog/LoadingDialog.kt
  70. 116 0
      RcCore/src/main/java/com/rc/core/ui/dialog/RcBaseDialog.kt
  71. 83 0
      RcCore/src/main/java/com/rc/core/ui/fragment/RcBaseFragment.kt
  72. 37 0
      RcCore/src/main/java/com/rc/core/ui/fragment/RcLazyFragment.kt
  73. 225 0
      RcCore/src/main/java/com/rc/core/ui/fragment/RcRefreshFragment.kt
  74. 301 0
      RcCore/src/main/java/com/rc/core/ui/widget/MultipleStatusView.kt
  75. 53 0
      RcCore/src/main/java/com/rc/core/ui/widget/decoration/GridSpacingItemDecoration.java
  76. 148 0
      RcCore/src/main/java/com/rc/core/ui/widget/decoration/NoLastLineItemDecoration.java
  77. 195 0
      RcCore/src/main/java/com/rc/core/util/ApkController.kt
  78. 61 0
      RcCore/src/main/java/com/rc/core/util/ApkUpdater.kt
  79. 208 0
      RcCore/src/main/java/com/rc/core/util/CrashHandler.kt
  80. 120 0
      RcCore/src/main/java/com/rc/core/util/CrashHelper.kt
  81. 91 0
      RcCore/src/main/java/com/rc/core/util/DeviceUtils.kt
  82. 68 0
      RcCore/src/main/java/com/rc/core/util/EscapeUnescape.java
  83. 13 0
      RcCore/src/main/java/com/rc/core/util/Extension.kt
  84. 58 0
      RcCore/src/main/java/com/rc/core/util/Format.kt
  85. 162 0
      RcCore/src/main/java/com/rc/core/util/MediaUtils.kt
  86. 128 0
      RcCore/src/main/java/com/rc/core/util/ScreenAdapter.kt
  87. 50 0
      RcCore/src/main/java/com/rc/core/util/VideoFullScreenWebChromeClient.kt
  88. 155 0
      RcCore/src/main/java/com/rc/core/util/WebViewHelper.kt
  89. 35 0
      RcCore/src/main/java/com/rc/core/util/net/NetChangeListener.kt
  90. 20 0
      RcCore/src/main/java/com/rc/core/util/net/NetConnectedListener.kt
  91. 143 0
      RcCore/src/main/java/com/rc/core/util/net/NetWatchdog.kt
  92. 8 0
      RcCore/src/main/res/anim/bottom_enter.xml
  93. 8 0
      RcCore/src/main/res/anim/bottom_exit.xml
  94. 6 0
      RcCore/src/main/res/anim/dialog_enter.xml
  95. 6 0
      RcCore/src/main/res/anim/dialog_exit.xml
  96. 14 0
      RcCore/src/main/res/drawable/loading_green.xml
  97. 19 0
      RcCore/src/main/res/drawable/progressbar.xml
  98. 6 0
      RcCore/src/main/res/drawable/shape_item_divider.xml
  99. 27 0
      RcCore/src/main/res/layout/dialog_loading.xml
  100. 0 0
      RcCore/src/main/res/layout/view_list_empty.xml

+ 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

+ 50 - 0
HttpCoreLibrary/build.gradle

@@ -0,0 +1,50 @@
+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
+    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>

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

@@ -0,0 +1,70 @@
+package com.rc.httpcore
+
+import android.content.Context
+import com.rc.httpcore.client.factory.ClientFactory
+import com.rc.httpcore.client.factory.RetrofitFactory
+import com.rc.httpcore.config.OkHttpDNS
+import com.rc.httpcore.converter.NullOnEmptyConverterFactory
+import com.rc.httpcore.interceptor.HttpLoggingInterceptorLog
+import com.rc.httpcore.interceptor.TokenHeaderInterceptor
+import okhttp3.OkHttpClient
+import 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"
+
+    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 {
+        return OkHttpClient.Builder()
+            .readTimeout(TIMEOUT_DEFAULT, TimeUnit.SECONDS) // 设置读取超时时间
+            .connectTimeout(TIMEOUT_DEFAULT, TimeUnit.SECONDS) // 设置请求超时时间
+            .writeTimeout(TIMEOUT_DEFAULT, TimeUnit.SECONDS) // 设置写入超时时间
+            .addNetworkInterceptor(TokenHeaderInterceptor())
+                .addInterceptor(HttpLoggingInterceptorLog())//添加请求日志
+            .retryOnConnectionFailure(true) // 设置出现错误进行重新连接
+            .dns(OkHttpDNS())
+            .build()
+    }
+
+}

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

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

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

@@ -0,0 +1,331 @@
+package com.rc.httpcore.client
+
+import com.rc.httpcore.HttpClient
+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
+
+object ApiRepository {
+
+    private val mClientFactory by lazy { HttpClient.createClientFactory() }
+
+    /**
+     * 登录获取token
+     */
+    fun authOneLogin(): Observable<Boolean> {
+        return mClientFactory.createLabClient()
+            .authOneLogin()
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    /**
+     * 查询APK版本
+     *
+     * @param id 设备唯一编码
+     */
+    fun apkVersion(id: String): Observable<ApkInfoResp> {
+        return mClientFactory.createLabClient()
+            .apkVersion(id)
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    /**
+     * 上传APK更新状态
+     *
+     * @param state 0:升级失败; 1:升级成功; 2:升级中
+     */
+    fun onepcApkUpdate(id: String, state: String): Observable<Boolean> {
+        return mClientFactory.createLabClient()
+            .onepcApkUpdate(id, state)
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    /**
+     * 实验室信息
+     *
+     * @param id 设备唯一编码
+     */
+    fun laboratoryInfo(id: String): Observable<LaboratoryVo> {
+        return mClientFactory.createLabClient()
+            .laboratoryInfo(id)
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    /**
+     * 危险源信息
+     *
+     * @param param 实验室Id、分页信息
+     */
+    fun hazardlist(param: HazardReq): Observable<List<LabHazardVo>> {
+        return mClientFactory.createLabClient()
+            .hazardlist(param)
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    /**
+     * 签到验证(进入)
+     *
+     * @param subId 实验室id
+     * @param username 学生卡编号
+     */
+    fun signInCheck(subId: String, username: String): Observable<SignInCheckResp> {
+        return mClientFactory.createLabClient()
+            .signInCheck(subId, username)
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    /**
+     * 签到提交-人脸验证
+     */
+    fun signInFace(code: String, faceFeature: SignInReq? = null): Observable<String> {
+        return mClientFactory.createLabClient()
+            .signInFace(code, faceFeature)
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    /**
+     * 签到-安全准入检测三合一
+     */
+    fun checkInAll(param: CheckInAllReq): Observable<Boolean> {
+        return mClientFactory.createLabClient()
+            .checkInAll(param)
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    /**
+     * 签到提交
+     */
+    fun signIn(id: String): Observable<Boolean> {
+        return mClientFactory.createLabClient()
+            .signIn(id)
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    /**
+     * 签到提交-有跳过安全准入检测时使用
+     */
+    fun signInJump(id: String, code: String): Observable<Boolean> {
+        return mClientFactory.createLabClient()
+            .signInJump(id, code)
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    fun signIn(jumpSafetyCheck: Boolean, id: String, code: String): Observable<Boolean> {
+        return if (jumpSafetyCheck) {
+            signInJump(id, code)
+        } else {
+            signIn(id)
+        }
+    }
+
+    /**
+     * 签到验证(离开)
+     *
+     * @param subId 实验室id
+     * @param username 学生卡编号
+     */
+    fun signOutCheck(subId: String, username: String): Observable<SignInCheckResp> {
+        return mClientFactory.createLabClient()
+            .signOutCheck(subId, username)
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    /**
+     * 离开提交
+     */
+    fun signOut(code: String): Observable<Boolean> {
+        return mClientFactory.createLabClient()
+            .signOut(code)
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    /**
+     * 获取实验室一体机可控制设备
+     *
+     * @param subId 实验室id
+     */
+    fun controllerList(subId: String): Observable<List<LotDeviceVo>> {
+        return mClientFactory.createLabClient()
+            .controllerList(subId)
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    /**
+     * 控制设备
+     *
+     * @param param 设备编号、命令
+     */
+    fun sendControllerCMD(param: ControllerCMD): Observable<Boolean> {
+        return mClientFactory.createLabClient()
+            .sendControllerCMD(param)
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    /**
+     * 实验室测点功能列表(首页-左侧看板)
+     *
+     * @param subId 实验室id
+     */
+    fun functionList(subId: String): Observable<List<LabBulletinBoardVo>> {
+        return mClientFactory.createLabClient()
+            .functionList(subId)
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    /**
+     * 实验室预警测点
+     *
+     * @param subId 实验室id
+     */
+    fun warnList(subId: String): Observable<List<LabBulletinBoardVo>> {
+        return mClientFactory.createLabClient()
+            .warnList(subId)
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    /**
+     * 人脸比对
+     */
+    fun faceCompare(param: FaceCompareReq): Observable<Boolean> {
+        return mClientFactory.createLabClient()
+            .faceCompare(param)
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    /**
+     * 心跳
+     *
+     * @param num 设备唯一编码
+     */
+    fun heartbeat(num: String): Observable<Boolean> {
+        return mClientFactory.createLabClient()
+            .heartbeat(num)
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    /**
+     * 查询实验室安全制度列表
+     *
+     * @param type 1:学校制度 2:学院制度 3: 中心制度
+     */
+    fun safeBookList(type: String): Observable<List<SafeBook>> {
+        return mClientFactory.createLabClient()
+            .safeBookList(type)
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    /**
+     * 获取实验室安全制度详细信息
+     */
+    fun safeBookDetail(id: String): Observable<SafeBook> {
+        return mClientFactory.createLabClient()
+            .safeBookDetail(id)
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    /**
+     * 一体机查询危化品
+     */
+    fun hazardBookList(): Observable<List<HazardBook>> {
+        return mClientFactory.createLabClient()
+            .hazardBookList()
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    /**
+     * 获取危化品安全技术说明书详细信息
+     */
+    fun hazardBookDetail(id: String): Observable<HazardBook> {
+        return mClientFactory.createLabClient()
+            .hazardBookDetail(id)
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    /**
+     * 查询实验室在线人员
+     */
+    fun onlineUser(param: OnLineUserReq): Observable<CommonRowsResponse<LabPersonVo>> {
+        return mClientFactory.createLabClient()
+            .onlineUser(param)
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    /**
+     * 实验室安全整改信息
+     */
+    fun checkMachineMsgList(subId: String): Observable<List<CheckMachineVo>> {
+        return mClientFactory.createLabClient()
+            .checkMachineMsgList(subId)
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    /**
+     * 实验室通知消息信息
+     */
+    fun contentMachineMsgList(subId: String): Observable<List<ContentMachineVo>> {
+        return mClientFactory.createLabClient()
+            .contentMachineMsgList(subId)
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    /**
+     * 实验室文化图
+     *
+     * @param id 设备唯一编码
+     */
+    fun bannerImages(id: String): Observable<List<BannerImageBean>> {
+        return mClientFactory.createLabClient()
+            .bannerImages(id)
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    /**
+     * 文字转语音
+     */
+    fun textParseVideo(text: String): Observable<SpeakInfo> {
+        return mClientFactory.createLabClient()
+            .textParseVideo(text)
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    /**
+     * 物联控制权限验证
+     */
+    fun lotInCheck(subId: String, username: String): Observable<String> {
+        return mClientFactory.createLabClient()
+            .lotInCheck(subId, username)
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+}

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

@@ -0,0 +1,178 @@
+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
+
+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 subId 实验室id
+     * @param username 学生卡编号
+     */
+    fun signInCheck(subId: String, username: String): Observable<SignInCheckResp>
+
+    /**
+     * 签到提交-人脸验证
+     */
+    fun signInFace(code: String, faceFeature: SignInReq? = null): Observable<String>
+
+    /**
+     * 签到-安全准入检测三合一
+     */
+    fun checkInAll(param: CheckInAllReq): Observable<Boolean>
+
+    /**
+     * 签到提交
+     */
+    fun signIn(id: String): Observable<Boolean>
+
+    /**
+     * 签到提交-有跳过安全准入检测时使用
+     */
+    fun signInJump(id: String, code: String): Observable<Boolean>
+
+    /**
+     * 签到验证(离开)
+     *
+     * @param subId 实验室id
+     * @param username 学生卡编号
+     */
+    fun signOutCheck(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>
+
+}

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

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

@@ -0,0 +1,216 @@
+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.FaceCompareReq;
+import com.rc.httpcore.vo.request.OnLineUserReq;
+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.HazardBook;
+import com.rc.httpcore.vo.response.LabBulletinBoardVo;
+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.SafeBook;
+import com.rc.httpcore.vo.response.SignInCheckResp;
+import com.rc.httpcore.vo.response.SpeakInfo;
+
+import java.util.List;
+import java.util.Map;
+
+import io.reactivex.Observable;
+import okhttp3.MultipartBody;
+import okhttp3.RequestBody;
+import retrofit2.http.Body;
+import retrofit2.http.GET;
+import retrofit2.http.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);
+
+    /**
+     * 签到提交-人脸验证
+     */
+    @POST("laboratory/onemachine/{code}/SignIn2")
+    Observable<CommonDataResponse<String>> signInFace(@Path("code") String code, @Body SignInReq data);
+
+    /**
+     * 签到-安全准入检测三合一
+     */
+    @Multipart
+    @POST("signIn/check/checkInAll")
+    Observable<CommonDataResponse<String>> checkInAll(@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);
+
+    /**
+     * 离开提交
+     */
+    @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);
+
+}

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

@@ -0,0 +1,455 @@
+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
+
+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 subId 实验室id
+     * @param username 学生卡编号
+     */
+    override fun signInCheck(subId: String, username: String): Observable<SignInCheckResp> {
+        return apiService.signInCheck(subId, username)
+            .map { response ->
+                if (!response.isSuccess()) {
+                    throw NetException(response.code, response.msg)
+                }
+                return@map response.data
+            }
+    }
+
+    /**
+     * 签到提交-人脸验证
+     */
+    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
+            }
+    }
+
+    /**
+     * 签到-安全准入检测三合一
+     */
+    override fun checkInAll(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)
+        }
+
+        return signInCheckService.checkInAll(bodyPartMap, filePart)
+            .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 subId 实验室id
+     * @param username 学生卡编号
+     */
+    override fun signOutCheck(subId: String, username: String): Observable<SignInCheckResp> {
+        return apiService.signOutCheck(subId, username)
+            .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
+            }
+    }
+
+}

+ 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://pc44sory.xiaomy.net:31738/")
+            .httpStrategy(HttpConfig.HTTP_STRATEGY_Retrofit)
+            .mqttServerUri("tcp://180.76.134.43:18830")
+            .mqttUName("dlc")
+            .mqttUPwd("123456")
+            .build()
+
+        val buildReleaseConfig = ConfigParam.Builder()
+            .baseUrl("https://lab.sxitdlc.com/labSystem/")
+            .signInCheckBaseUrl("http://180.76.134.43:9319/")
+            .httpStrategy(HttpConfig.HTTP_STRATEGY_Retrofit)
+            .mqttServerUri("tcp://180.76.134.43:1883")
+            .mqttUName("mqtt")
+            .mqttUPwd("mqtt@zd1883")
+            .build()
+    }
+
+}

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

@@ -0,0 +1,68 @@
+package com.rc.httpcore.config
+
+import com.rc.httpcore.HttpConfig
+
+class ConfigParam(builder: Builder) {
+
+    val baseUrl: String = builder.baseUrl
+    val 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)
+        }
+
+    }
+}

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

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

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

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

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

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

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

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

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

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

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

@@ -0,0 +1,15 @@
+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;
+    public int returnTime; // 自动返回时间 秒
+}

+ 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; // 消息内容
+
+}

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

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

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

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

@@ -0,0 +1,15 @@
+package com.rc.httpcore.vo.response;
+
+/**
+ * 在线人员
+ *
+ * @author ReiChin_
+ */
+public class LabPersonVo {
+
+    public String avatar; // 人员照片
+    public String nickName; // 姓名
+    public String userName; // 工号
+    public String createTime; // 签到时间
+
+}

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

@@ -0,0 +1,52 @@
+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<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 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;
+    }
+
+}

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

+ 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(数据权限)
+
+}

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

@@ -0,0 +1,36 @@
+package com.rc.httpcore.vo.response;
+
+import java.util.List;
+
+/**
+ * 签到验证 Response
+ *
+ * @author ReiChin_
+ */
+public class SignInCheckResp {
+
+    public String code; // 验证码
+    public List<String> message; // 验证信息
+    public SingInUser singInUser; // 签到用户信息
+    public SingInViolation singInViolation; // 签到违规信息
+    public boolean state;
+    public String subId;
+    public List<String> tips; // 一般提示信息
+
+    public static class SingInUser {
+        public String deptId; // 部门id
+        public String expirationDate; // 失效时间
+        public String nickName; // 用户姓名
+        public String type; // 用户类型
+        public String userId; // 用户id
+        public String userName; // 用户账户
+        public String avatar; // 用户头像
+    }
+
+    public static class SingInViolation {
+        public String createTime; // 违约时间
+        public String subjectName; // 实验室名称
+        public String violationContent; // 违规内容
+    }
+
+}

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

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

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

@@ -0,0 +1,27 @@
+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)
+
+    fun warn(message: String, throwable: Throwable? = null) {
+        printLog(Level.WARNING, message, throwable)
+    }
+
+    fun info(message: String, throwable: Throwable? = null) {
+        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()
+    }
+
+}

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

@@ -0,0 +1,109 @@
+package com.rc.core.ui.activity
+
+import android.graphics.Color
+import android.os.*
+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 com.rc.httpcore.vo.response.AccessTokenResp
+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)
+    }
+
+
+
+
+}

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

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

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

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

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

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

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

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

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

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

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

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

@@ -0,0 +1,195 @@
+package com.rc.core.util
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import android.os.Build
+import android.os.Process
+import androidx.core.content.FileProvider
+import com.rc.core.ui.ActivityCollector
+import java.io.*
+import java.nio.charset.Charset
+import kotlin.system.exitProcess
+
+/**
+ * info
+ *
+ * @author ReiChin_
+ */
+object ApkController {
+
+    fun installAPK(
+        apkFile: File,
+        appContext: Context,
+        applicationId: String
+    ) {
+        if (isRootPermission()) {
+            install(apkFile.path)
+//            startApp("com.dlc.laboratory", "com.dlc.laboratory.ui.SplashActivity")
+            startApp(appContext, "com.dlc.laboratory", "com.dlc.laboratory.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.laboratory/com.dlc.laboratory.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));
+//    }
+}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

+ 143 - 0
RcCore/src/main/java/com/rc/core/util/net/NetWatchdog.kt

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

+ 8 - 0
RcCore/src/main/res/anim/bottom_enter.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<translate xmlns:android="http://schemas.android.com/apk/res/android"
+    android:duration="300"
+    android:fromXDelta="0"
+    android:fromYDelta="100%"
+    android:toXDelta="0"
+    android:toYDelta="0">
+</translate>

+ 8 - 0
RcCore/src/main/res/anim/bottom_exit.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<translate xmlns:android="http://schemas.android.com/apk/res/android"
+    android:duration="300"
+    android:fromXDelta="0"
+    android:fromYDelta="0"
+    android:toXDelta="0"
+    android:toYDelta="100%">
+</translate>

+ 6 - 0
RcCore/src/main/res/anim/dialog_enter.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <alpha
+        android:fromAlpha="0.2"
+        android:toAlpha="1.0" />
+</set>

+ 6 - 0
RcCore/src/main/res/anim/dialog_exit.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <alpha
+        android:fromAlpha="1.0"
+        android:toAlpha="0" />
+</set>

+ 14 - 0
RcCore/src/main/res/drawable/loading_green.xml

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item>
+        <rotate
+            android:drawable="@mipmap/ic_loading_green"
+            android:fromDegrees="0.0"
+            android:pivotX="50.0%"
+            android:pivotY="50.0%"
+            android:toDegrees="360.0" />
+        <!-- 其中360.0值越大,转的圈圈越快 -->
+    </item>
+
+</layer-list>

+ 19 - 0
RcCore/src/main/res/drawable/progressbar.xml

@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@android:id/background">
+        <shape>
+            <solid android:color="#00f0f0f0" />
+        </shape>
+    </item>
+    <item android:id="@android:id/progress">
+        <clip android:clipOrientation="horizontal">
+            <shape>
+                <gradient
+                    android:angle="0"
+                    android:centerX="0.5"
+                    android:endColor="#326DB1"
+                    android:startColor="#326DB1" />
+            </shape>
+        </clip>
+    </item>
+</layer-list>

+ 6 - 0
RcCore/src/main/res/drawable/shape_item_divider.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <solid android:color="#E0E0E0" />
+    <size android:height="1px" />
+</shape>

+ 27 - 0
RcCore/src/main/res/layout/dialog_loading.xml

@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="@android:color/white"
+    android:baselineAligned="false"
+    android:orientation="horizontal"
+    android:padding="13dp">
+
+    <ProgressBar
+        android:id="@+id/loading"
+        style="@style/RcProgressDialogStyle"
+        android:layout_width="50dp"
+        android:layout_height="50dp"
+        android:layout_margin="7dp" />
+
+    <TextView
+        android:id="@+id/message"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:layout_margin="7dp"
+        android:textSize="14sp"
+        tools:text="Loading..." />
+
+</LinearLayout>

+ 0 - 0
RcCore/src/main/res/layout/view_list_empty.xml


Nem az összes módosított fájl került megjelenítésre, mert túl sok fájl változott