소스 검색

第一次上传

ccc 6 달 전
부모
커밋
beca0eff87
100개의 변경된 파일6373개의 추가작업 그리고 0개의 파일을 삭제
  1. 66 0
      .gitignore
  2. 1 0
      app/.gitignore
  3. 121 0
      app/build.gradle
  4. BIN
      app/libs/arm64-v8a/libjSerialComm.so
  5. BIN
      app/libs/arm64-v8a/libserial_port.so
  6. BIN
      app/libs/armeabi-v7a/libjSerialComm.so
  7. BIN
      app/libs/armeabi-v7a/libserial_port.so
  8. BIN
      app/libs/armeabi/libjSerialComm.so
  9. BIN
      app/libs/armeabi/libserial_port.so
  10. BIN
      app/libs/mips/libjSerialComm.so
  11. BIN
      app/libs/mips64/libjSerialComm.so
  12. BIN
      app/libs/x86/libjSerialComm.so
  13. BIN
      app/libs/x86/libserial_port.so
  14. BIN
      app/libs/x86_64/libjSerialComm.so
  15. BIN
      app/libs/x86_64/libserial_port.so
  16. 1 0
      app/multidexKeep.pro
  17. 14 0
      app/proguard-rules.pro
  18. BIN
      app/release/app-release.aab
  19. 30 0
      app/src/main/AndroidManifest.xml
  20. 147 0
      app/src/main/java/com/sunzee/app/AppApplication.kt
  21. 30 0
      app/src/main/res/drawable-v24/ic_launcher_foreground.xml
  22. 170 0
      app/src/main/res/drawable/ic_launcher_background.xml
  23. 5 0
      app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
  24. 5 0
      app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
  25. BIN
      app/src/main/res/mipmap-xxhdpi/ic_launcher.png
  26. BIN
      app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
  27. 4 0
      app/src/main/res/values-en/strings.xml
  28. 3 0
      app/src/main/res/values/strings.xml
  29. 8 0
      app/src/main/res/xml/network_security_config.xml
  30. 56 0
      base_lib.gradle
  31. 89 0
      base_module.gradle
  32. 8 0
      build.gradle.kts
  33. 2 0
      buildSrc/build.gradle.kts
  34. 172 0
      buildSrc/gradlew
  35. 84 0
      buildSrc/gradlew.bat
  36. 203 0
      buildSrc/src/main/kotlin/com/quyunshuo/androidbaseframemvvm/buildsrc/DependencyConfig.kt
  37. 31 0
      buildSrc/src/main/kotlin/com/quyunshuo/androidbaseframemvvm/buildsrc/ProjectBuildConfig.kt
  38. 14 0
      buildSrc/src/main/kotlin/com/quyunshuo/androidbaseframemvvm/buildsrc/SDKKeyConfig.kt
  39. 26 0
      gradle.properties
  40. 41 0
      gradle/libs.versions.toml
  41. BIN
      gradle/wrapper/gradle-wrapper.jar
  42. 6 0
      gradle/wrapper/gradle-wrapper.properties
  43. 172 0
      gradlew
  44. 84 0
      gradlew.bat
  45. BIN
      img/img2.png
  46. 1 0
      lib_base/.gitignore
  47. 90 0
      lib_base/build.gradle
  48. 0 0
      lib_base/consumer-rules.pro
  49. 21 0
      lib_base/proguard-rules.pro
  50. 1 0
      lib_base/src/main/AndroidManifest.xml
  51. 78 0
      lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/BaseApplication.kt
  52. 34 0
      lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/addressenum/PayEnum.kt
  53. 14 0
      lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/addressenum/PlcD2StatusEnum.kt
  54. 51 0
      lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/addressenum/PlcDebugAddressEnum.kt
  55. 10 0
      lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/addressenum/PlcHomeAddressEnum.kt
  56. 16 0
      lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/addressenum/PlcParamAddressEnum.kt
  57. 7 0
      lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/addressenum/PlcSettingAddressEnum.kt
  58. 26 0
      lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/addressenum/ProTypeEnum.kt
  59. 51 0
      lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/app/ActivityLifecycleCallbacksImpl.kt
  60. 42 0
      lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/app/ApplicationLifecycle.kt
  61. 63 0
      lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/app/LoadModuleProxy.kt
  62. 16 0
      lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/constant/VersionStatus.kt
  63. 25 0
      lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/ktx/ActivityKtx.kt
  64. 21 0
      lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/ktx/EditTextKtx.kt
  65. 30 0
      lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/ktx/LifecycleOwnerKtx.kt
  66. 22 0
      lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/ktx/NetUtils.kt
  67. 21 0
      lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/ktx/PopupWindowKtx.kt
  68. 75 0
      lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/ktx/SizeUnitKtx.kt
  69. 42 0
      lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/ktx/VideoViewKtx.kt
  70. 226 0
      lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/ktx/ViewKtx.kt
  71. 104 0
      lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/ktx/ViewModelKtx.kt
  72. 38 0
      lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/ktx/ViewPager2Ktx.kt
  73. 31 0
      lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/mvvm/m/BaseRepository.kt
  74. 106 0
      lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/mvvm/v/BaseFrameActivity.kt
  75. 108 0
      lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/mvvm/v/BaseFrameDialog.kt
  76. 119 0
      lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/mvvm/v/BaseFrameDialogFragment.kt
  77. 76 0
      lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/mvvm/v/BaseFrameFragment.kt
  78. 33 0
      lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/mvvm/v/FrameView.kt
  79. 60 0
      lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/mvvm/vm/BaseViewModel.kt
  80. 13 0
      lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/mvvm/vm/EmptyViewModel.kt
  81. 108 0
      lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/ActivityStackManager.kt
  82. 47 0
      lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/AndroidBugFixUtils.kt
  83. 45 0
      lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/AppUtils.kt
  84. 674 0
      lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/BarUtils.java
  85. 28 0
      lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/ClipboardUtils.kt
  86. 26 0
      lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/CoilGIFImageLoader.kt
  87. 186 0
      lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/DateUtils.kt
  88. 54 0
      lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/EventBusUtils.kt
  89. 84 0
      lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/ForegroundBackgroundHelper.kt
  90. 73 0
      lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/ProcessUtils.kt
  91. 48 0
      lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/RegisterEventBus.kt
  92. 8 0
      lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/ResourcesFun.kt
  93. 63 0
      lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/SpUtils.kt
  94. 1497 0
      lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/SpannableStringUtils.java
  95. 14 0
      lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/StateLayoutEnum.kt
  96. 24 0
      lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/ThreadUtils.kt
  97. 46 0
      lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/ToastUtils.kt
  98. 43 0
      lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/Utils.kt
  99. 41 0
      lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/network/AutoRegisterNetListener.kt
  100. 0 0
      lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/network/NetworkCallbackImpl.kt

+ 66 - 0
.gitignore

@@ -0,0 +1,66 @@
+# Built application files
+*.apk
+*.ap_
+
+# Files for the ART/Dalvik VM
+*.dex
+
+# Java class files
+*.class
+
+# Generated files
+bin/
+gen/
+out/
+
+# Gradle files
+.gradle/
+build/
+
+# Local configuration file (sdk path, etc)
+local.properties
+
+# Proguard folder generated by Eclipse
+proguard/
+
+# Log Files
+*.log
+
+# Android Studio Navigation editor temp files
+.navigation/
+
+# Android Studio captures folder
+captures/
+
+# IntelliJ
+*.iml
+.idea/workspace.xml
+.idea/tasks.xml
+.idea/gradle.xml
+.idea/assetWizardSettings.xml
+.idea/dictionaries
+.idea/libraries
+.idea/caches
+.idea/*
+
+# Keystore files
+# Uncomment the following line if you do not want to check your keystore files in.
+#*.jks
+
+# External native build folder generated in Android Studio 2.2 and later
+.externalNativeBuild
+
+# Google Services (e.g. APIs or Firebase)
+google-services.json
+
+# Freeline
+freeline.py
+freeline/
+freeline_project_description.json
+
+# fastlane
+fastlane/report.xml
+fastlane/Preview.html
+fastlane/screenshots
+fastlane/test_output
+fastlane/readme.md

+ 1 - 0
app/.gitignore

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

+ 121 - 0
app/build.gradle

@@ -0,0 +1,121 @@
+//****************************************
+//************ app 壳的配置文件 ************
+//****************************************
+
+import com.quyunshuo.androidbaseframemvvm.buildsrc.*
+
+plugins {
+    alias(libs.plugins.application)
+    alias(libs.plugins.kotlin)
+    alias(libs.plugins.hilt)
+    id "kotlin-kapt"
+}
+
+android {
+    namespace 'com.quyunshuo.androidbaseframemvvm'
+    compileSdk ProjectBuildConfig.compileSdkVersion
+
+    defaultConfig {
+        applicationId ProjectBuildConfig.applicationId
+        minSdk ProjectBuildConfig.minSdkVersion
+        targetSdk ProjectBuildConfig.targetSdkVersion
+        versionCode ProjectBuildConfig.versionCode
+        versionName ProjectBuildConfig.versionName
+
+        testInstrumentationRunner DependencyConfig.AndroidX.AndroidJUnitRunner
+        multiDexKeepProguard file("multidexKeep.pro")
+        manifestPlaceholders = [
+                GETUI_APP_ID    : "GKa6qa12heALjEXZlAn1U3",
+                GETUI_APP_KEY   : "89WV8dfjRg6RlxEchgmnS6",
+                GETUI_APP_SECRET: "fLvPjR8hni7VFMkgjh8lx2"
+        ]
+        ndk {
+            // 设置支持的SO库架构
+//            abiFilters 'armeabi', 'x86', 'armeabi-v7a', 'x86_64', 'arm64-v8a'
+            abiFilters 'armeabi-v7a', 'arm64-v8a'
+        }
+
+        resConfig 'en'
+
+        packagingOptions {
+            exclude 'META-INF/io.netty.versions.properties'
+        }
+    }
+
+    signingConfigs {
+        release {
+            keyAlias 'sc'
+            keyPassword '1234567890'
+            storeFile file('../snowcone.jks')
+            storePassword '1234567890'
+        }
+    }
+    sourceSets {
+        main {
+            jniLibs.srcDirs = ['libs']
+        }
+    }
+    buildTypes {
+        // 对应 ALPHA 版本
+        debug {
+            buildConfigField "String", "VERSION_TYPE", "\"${ProjectBuildConfig.Version.ALPHA}\""
+//            signingConfig signingConfigs.releaseConfig
+            minifyEnabled false //为true,则对代码进行混淆和压缩
+//            shrinkResources true //为true, 则对资源进行缩减
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+        beta {
+            buildConfigField "String", "VERSION_TYPE", "\"${ProjectBuildConfig.Version.BETA}\""
+//            signingConfig signingConfigs.releaseConfig
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+        release {
+            buildConfigField "String", "VERSION_TYPE", "\"${ProjectBuildConfig.Version.RELEASE}\""
+//            signingConfig signingConfigs.releaseConfig
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+            signingConfig signingConfigs.release
+        }
+    }
+
+    // 自定义打包apk的文件名
+    android.applicationVariants.all { variant ->
+        variant.outputs.all { output ->
+            if (outputFileName != null && outputFileName.endsWith('.apk')) {
+                outputFileName = "${ProjectBuildConfig.applicationId}" +
+                        "_${ProjectBuildConfig.versionCode}" +
+                        "_${ProjectBuildConfig.versionName}" +
+                        "_${variant.buildType.name}" +
+                        ".apk"
+            }
+        }
+    }
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_17
+        targetCompatibility JavaVersion.VERSION_17
+    }
+
+    kotlinOptions {
+        jvmTarget = '17'
+    }
+}
+
+dependencies {
+    implementation fileTree(dir: "libs", include: ["*.jar"])
+
+    if (!ProjectBuildConfig.isAppMode) {
+        implementation project(path: ':module_home')
+        implementation project(path: ':module_backstage')
+        implementation project(path: ':module_pay')
+        implementation project(path: ':module_database')
+        implementation project(path: ':serialport-api')
+    } else {
+        implementation project(path: ':lib_common')
+    }
+    implementation DependencyConfig.JetPack.HiltCore
+
+    kapt DependencyConfig.GitHub.AutoServiceAnnotations
+    kapt DependencyConfig.JetPack.HiltApt
+}

BIN
app/libs/arm64-v8a/libjSerialComm.so


BIN
app/libs/arm64-v8a/libserial_port.so


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


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


BIN
app/libs/armeabi/libjSerialComm.so


BIN
app/libs/armeabi/libserial_port.so


BIN
app/libs/mips/libjSerialComm.so


BIN
app/libs/mips64/libjSerialComm.so


BIN
app/libs/x86/libjSerialComm.so


BIN
app/libs/x86/libserial_port.so


BIN
app/libs/x86_64/libjSerialComm.so


BIN
app/libs/x86_64/libserial_port.so


+ 1 - 0
app/multidexKeep.pro

@@ -0,0 +1 @@
+-keep public class com.tencent.bugly.**{*;}

+ 14 - 0
app/proguard-rules.pro

@@ -0,0 +1,14 @@
+# AGP 8.x 警告生成
+# Please add these rules to your existing keep rules in order to suppress warnings.
+# This is generated automatically by the Android Gradle plugin.
+-dontwarn dalvik.system.VMStack
+-dontwarn javax.lang.model.element.Element
+-dontwarn org.bouncycastle.jsse.BCSSLParameters
+-dontwarn org.bouncycastle.jsse.BCSSLSocket
+-dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider
+-dontwarn org.conscrypt.Conscrypt$Version
+-dontwarn org.conscrypt.Conscrypt
+-dontwarn org.conscrypt.ConscryptHostnameVerifier
+-dontwarn org.openjsse.javax.net.ssl.SSLParameters
+-dontwarn org.openjsse.javax.net.ssl.SSLSocket
+-dontwarn org.openjsse.net.ssl.OpenJSSE

BIN
app/release/app-release.aab


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

@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools">
+
+    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> <!-- ⾃定义权限  全局对话框 -->
+    <uses-permission
+        android:name="android.permission.READ_LOGS"
+        tools:ignore="ProtectedPermissions" />
+
+    <application
+        android:name="com.sunzee.app.AppApplication"
+        android:allowBackup="false"
+        android:icon="@drawable/pic_sc_01"
+        android:label="@string/app_name"
+        android:networkSecurityConfig="@xml/network_security_config"
+        android:roundIcon="@mipmap/ic_launcher_round"
+        android:supportsRtl="true"
+        android:theme="@style/base_AppTheme"
+        tools:ignore="UnusedAttribute">
+
+        <service android:name="com.quyunshuo.module.home.service.GlobalService" />
+        <service android:name="com.quyunshuo.module.home.service.MqService" />
+
+    </application>
+
+</manifest>

+ 147 - 0
app/src/main/java/com/sunzee/app/AppApplication.kt

@@ -0,0 +1,147 @@
+package com.sunzee.app
+
+import android.app.ActivityManager
+import android.content.Context
+import android.content.Intent
+import android.os.Build
+import android.os.Process
+import android.text.TextUtils
+import android.util.Log
+import com.hboxs.serialport.sbc.VboxSerialPortDevice
+import com.hboxs.serialport.sbc.VboxSerialPortManager
+import com.hjq.http.EasyConfig
+import com.hjq.http.config.IRequestHandler
+import com.hjq.http.request.HttpRequest
+import com.hjq.toast.Toaster
+import com.quyunshuo.androidbaseframemvvm.base.BaseApplication
+import com.quyunshuo.androidbaseframemvvm.common.constant.Heartbeat
+import com.quyunshuo.androidbaseframemvvm.common.util.FileUtil
+import com.quyunshuo.androidbaseframemvvm.common.util.XLogUtil
+import com.quyunshuo.module.home.service.GlobalService
+import com.quyunshuo.module.home.service.MqService
+import dagger.hilt.android.HiltAndroidApp
+import okhttp3.OkHttpClient
+import okhttp3.Response
+import org.greenrobot.eventbus.EventBus
+import java.lang.reflect.Type
+
+
+/**
+ * App壳
+ *
+ * @author Qu Yunshuo
+ * @since 4/23/21 6:08 PM
+ */
+@HiltAndroidApp
+class AppApplication : BaseApplication() {
+    private val TAG = "AppApplication"
+    private var instances: AppApplication? = null
+
+    override fun onCreate() {
+        instances = this
+        if (isAppMainProcess()) {
+            // 开启EventBusAPT,优化反射效率 当组件作为App运行时需要将添加的Index注释掉 因为找不到对应的类了
+            EventBus
+                .builder()
+//            .addIndex(MainEventIndex())
+                .installDefaultEventBus()
+
+            // 初始化 Toast 框架
+            Toaster.init(this)
+            //打开串口
+            connectDevice()
+            //创建设备编号
+            Heartbeat.deviceId = FileUtil.getDeviceId()
+            initEasyOkHttp()
+            val intent = Intent(this, GlobalService::class.java)
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+                startForegroundService(intent)
+            } else {
+                startService(intent)
+            }
+
+            //开启MQ
+            startService(Intent(this, MqService::class.java))
+            XLogUtil.init()
+        }
+
+        super.onCreate()
+    }
+
+    private fun initEasyOkHttp() {
+
+        /**
+         * 初始哈EasyHttp
+         */
+        var okHttpClient: OkHttpClient? = OkHttpClient.Builder().build()
+
+        // 网络请求框架初始化
+        EasyConfig.with(okHttpClient)
+            .setServer { "http://192.168.1.105:8080/" } // 设置服务器配置(必须设置)
+            .setHandler(object : IRequestHandler {
+                override fun requestSuccess(p0: HttpRequest<*>, result: Response, p2: Type): Any {
+                    return result
+                }
+                override fun requestFail(p0: HttpRequest<*>, p1: Throwable): Throwable {
+                    return p1
+                }
+            })
+            .setRetryCount(1) // 设置请求重试次数
+            .setRetryTime(2000) // 设置请求重试时间
+            .into()
+
+    }
+
+    private fun connectDevice() {
+        //对plc进行连接
+//        val device = SerialPortDevice("/dev/ttyS4", "9600")
+//        try {
+//            SerialPortManager.getInstance().open(device)
+//        } catch (e: Exception) {
+//            Log.d(TAG, "connectDevice 串口打开失败: ")
+//        }
+//        Log.d(TAG, "connectDevice 串口打开成功: ")
+
+        //对单片机进行连接
+        val device: VboxSerialPortDevice = VboxSerialPortDevice("dev/ttyS7", "115200")
+        try {
+            VboxSerialPortManager.getInstance().open(device)
+        } catch (e: Exception) {
+            Log.d(TAG, "connectDevice 串口打开失败: ")
+        }
+        Log.d(TAG, "connectDevice 串口打开成功: ")
+    }
+
+    /**
+     * 判断是不是UI主进程,因为有些东西只能在UI主进程初始化
+     */
+    fun isAppMainProcess(): Boolean {
+        try {
+            val pid = Process.myPid()
+            val process: String = getAppNameByPID(instances!!.baseContext,pid)
+            return if (TextUtils.isEmpty(process)) {
+                true
+            } else if ("com.quyunshuo.androidbaseframemvvm".equals(process, ignoreCase = true)) {
+                true
+            } else {
+                false
+            }
+        } catch (e: java.lang.Exception) {
+            e.printStackTrace()
+            return true
+        }
+    }
+
+    /**
+     * 根据Pid得到进程名
+     */
+    fun getAppNameByPID(context: Context, pid: Int): String {
+        val manager = context.getSystemService(ACTIVITY_SERVICE) as ActivityManager
+        for (processInfo in manager.runningAppProcesses) {
+            if (processInfo.pid == pid) {
+                return processInfo.processName
+            }
+        }
+        return ""
+    }
+}

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 30 - 0
app/src/main/res/drawable-v24/ic_launcher_foreground.xml


+ 170 - 0
app/src/main/res/drawable/ic_launcher_background.xml

@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+    <path
+        android:fillColor="#3DDC84"
+        android:pathData="M0,0h108v108h-108z" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M9,0L9,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,0L19,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,0L29,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,0L39,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,0L49,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,0L59,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,0L69,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,0L79,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M89,0L89,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M99,0L99,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,9L108,9"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,19L108,19"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,29L108,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,39L108,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,49L108,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,59L108,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,69L108,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,79L108,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,89L108,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,99L108,99"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,29L89,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,39L89,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,49L89,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,59L89,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,69L89,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,79L89,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,19L29,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,19L39,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,19L49,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,19L59,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,19L69,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,19L79,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+</vector>

+ 5 - 0
app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>

+ 5 - 0
app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>

BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher.png


BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png


+ 4 - 0
app/src/main/res/values-en/strings.xml

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="app_name">Snow Cone Machine</string>
+</resources>

+ 3 - 0
app/src/main/res/values/strings.xml

@@ -0,0 +1,3 @@
+<resources>
+    <string name="app_name">P10</string>
+</resources>

+ 8 - 0
app/src/main/res/xml/network_security_config.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+    <domain-config cleartextTrafficPermitted="true">
+        <domain includeSubdomains="true">android.bugly.qq.com</domain>
+        <domain includeSubdomains="true">sz.sunzee.com.cn</domain>
+        <domain includeSubdomains="true">qiniuyun.sunzee.com.cn</domain>
+    </domain-config>
+</network-security-config>

+ 56 - 0
base_lib.gradle

@@ -0,0 +1,56 @@
+//****************************************
+//********* lib 模块的公共脚本配置 **********
+//****************************************
+
+import com.quyunshuo.androidbaseframemvvm.buildsrc.*
+
+android {
+    compileSdkVersion ProjectBuildConfig.compileSdkVersion
+
+    defaultConfig {
+        minSdkVersion ProjectBuildConfig.minSdkVersion
+        targetSdkVersion ProjectBuildConfig.targetSdkVersion
+
+        consumerProguardFiles "consumer-rules.pro"
+
+        ndk {
+            // 设置支持的SO库架构
+            abiFilters 'armeabi', 'x86', 'armeabi-v7a', 'x86_64', 'arm64-v8a'
+//            abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86'
+        }
+    }
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_17
+        targetCompatibility JavaVersion.VERSION_17
+    }
+
+    kotlinOptions {
+        jvmTarget = "17"
+    }
+
+    buildTypes {
+        // 对应 ALPHA 版本
+        debug {
+            buildConfigField "String", "VERSION_TYPE", "\"${ProjectBuildConfig.Version.ALPHA}\""
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+        beta {
+            buildConfigField "String", "VERSION_TYPE", "\"${ProjectBuildConfig.Version.BETA}\""
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+        release {
+            buildConfigField "String", "VERSION_TYPE", "\"${ProjectBuildConfig.Version.RELEASE}\""
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+    }
+}
+
+kapt {
+    arguments {
+        arg("AROUTER_MODULE_NAME", project.getName())
+    }
+}

+ 89 - 0
base_module.gradle

@@ -0,0 +1,89 @@
+//****************************************
+//******** module模块的公共脚本配置 *********
+//****************************************
+
+import com.quyunshuo.androidbaseframemvvm.buildsrc.*
+
+android {
+    compileSdk ProjectBuildConfig.compileSdkVersion
+
+    defaultConfig {
+        minSdk ProjectBuildConfig.minSdkVersion
+        targetSdk ProjectBuildConfig.targetSdkVersion
+        versionCode ProjectBuildConfig.versionCode
+        versionName ProjectBuildConfig.versionName
+        testInstrumentationRunner DependencyConfig.AndroidX.AndroidJUnitRunner
+
+        ndk {
+            // 设置支持的SO库架构
+            //abiFilters 'armeabi', 'x86', 'armeabi-v7a', 'x86_64', 'arm64-v8a'
+            abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86'
+        }
+    }
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_17
+        targetCompatibility JavaVersion.VERSION_17
+    }
+
+    kotlinOptions {
+        jvmTarget = "17"
+    }
+
+    buildFeatures {
+        viewBinding = true
+    }
+
+    sourceSets {
+        main {
+            manifest.srcFile 'src/main/AndroidManifest.xml'
+            java {
+                //排除debug文件夹下的所有文件
+                exclude 'debug/**'
+            }
+        }
+    }
+
+    buildTypes {
+        // 对应 ALPHA 版本
+        debug {
+            buildConfigField "String", "VERSION_TYPE", "\"${ProjectBuildConfig.Version.ALPHA}\""
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+        beta {
+            buildConfigField "String", "VERSION_TYPE", "\"${ProjectBuildConfig.Version.BETA}\""
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+        release {
+            buildConfigField "String", "VERSION_TYPE", "\"${ProjectBuildConfig.Version.RELEASE}\""
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+    }
+}
+
+kapt {
+    arguments {
+        arg("AROUTER_MODULE_NAME", project.name)
+        arg("eventBusIndex", "${ProjectBuildConfig.applicationId}.eventbus.index.${project.name}EventIndex")
+    }
+}
+
+dependencies {
+    implementation fileTree(dir: 'libs', include: ['*.jar'])
+
+    api project(path: ':lib_common')
+
+    testImplementation DependencyConfig.Android.Junit
+    androidTestImplementation DependencyConfig.AndroidX.TestExtJunit
+    androidTestImplementation DependencyConfig.AndroidX.TestEspresso
+    implementation DependencyConfig.JetPack.HiltCore
+
+    kapt DependencyConfig.GitHub.ARouteCompiler
+    kapt DependencyConfig.GitHub.EventBusAPT
+    kapt DependencyConfig.GitHub.AutoServiceAnnotations
+    kapt DependencyConfig.JetPack.HiltApt
+    kapt DependencyConfig.JetPack.LifecycleCompilerAPT
+}

+ 8 - 0
build.gradle.kts

@@ -0,0 +1,8 @@
+@Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed
+plugins {
+    alias(libs.plugins.application) apply false
+    alias(libs.plugins.library) apply false
+    alias(libs.plugins.kotlin) apply false
+    alias(libs.plugins.hilt) apply false
+}
+true // Needed to make the Suppress annotation work for the plugins block

+ 2 - 0
buildSrc/build.gradle.kts

@@ -0,0 +1,2 @@
+plugins { `kotlin-dsl` }
+repositories { mavenCentral() }

+ 172 - 0
buildSrc/gradlew

@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+    echo "$*"
+}
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+  NONSTOP* )
+    nonstop=true
+    ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Escape application args
+save () {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+    echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+  cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"

+ 84 - 0
buildSrc/gradlew.bat

@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega

+ 203 - 0
buildSrc/src/main/kotlin/com/quyunshuo/androidbaseframemvvm/buildsrc/DependencyConfig.kt

@@ -0,0 +1,203 @@
+package com.quyunshuo.androidbaseframemvvm.buildsrc
+
+/**
+ * 项目依赖版本统一管理
+ *
+ * @author Qu Yunshuo
+ * @since 4/24/21 4:00 PM
+ */
+object DependencyConfig {
+
+    /**
+     * 依赖版本号
+     *
+     * @author Qu Yunshuo
+     * @since 4/24/21 4:01 PM
+     */
+    object Version {
+
+
+        // AndroidX--------------------------------------------------------------
+        const val AppCompat = "1.3.1"
+        const val CoreKtx = "1.7.0"
+        const val ConstraintLayout = "2.1.3"                // 约束布局
+        const val TestExtJunit = "1.1.2"
+        const val TestEspresso = "3.3.0"
+        const val ActivityKtx = "1.5.1"
+        const val FragmentKtx = "1.5.2"
+        const val MultiDex = "2.0.1"
+        const val Navigation = "2.3.0"
+        const val Room = "2.6.0"
+        const val Work = "2.8.1"                           //后台管理器
+
+        // Android---------------------------------------------------------------
+        const val Junit = "4.13"
+        const val Material = "1.2.0"                        // 材料设计UI套件
+
+        // Kotlin----------------------------------------------------------------
+        const val Kotlin = "1.9.0"
+        const val Coroutines = "1.6.1"                      // 协程
+
+        // JetPack---------------------------------------------------------------
+        const val Lifecycle = "2.4.1"                       // Lifecycle
+        const val Hilt = "2.44"                             // DI框架-Hilt
+
+        // GitHub----------------------------------------------------------------
+        const val OkHttp = "4.9.0"                          // OkHttp
+        const val OkHttpInterceptorLogging = "4.9.1"        // OkHttp 请求Log拦截器
+        const val Retrofit = "2.9.0"                        // Retrofit
+        const val RetrofitConverterGson = "2.9.0"           // Retrofit Gson 转换器
+        const val RetrofitConverterScalars = "2.9.0"           // Retrofit String 转换器
+        const val Gson = "2.8.7"                            // Gson
+        const val MMKV = "1.2.9"                            // 腾讯 MMKV 替代SP
+        const val AutoSize = "v1.2.1"                        // 屏幕适配
+        const val ARoute = "1.5.2"                          // 阿里路由
+        const val ARouteCompiler = "1.5.2"                  // 阿里路由 APT
+        const val RecyclerViewAdapter = "3.0.4"             // RecyclerViewAdapter
+        const val EventBus = "3.2.0"                        // 事件总线
+        const val PermissionX = "1.8.0"                     // 权限申请
+        const val LeakCanary = "2.7"                        // 检测内存泄漏
+        const val AutoService = "1.0"                       // 自动生成SPI暴露服务文件
+        const val Coil = "1.3.0"                            // Kotlin图片加载框架
+        const val Toaster = "12.6"                            //Toast框架
+        const val MagicIndicator = "1.7.0"                            //导航条
+        const val Okhttputils = "12.8"                     //下载更新apk https://github.com/getActivity/EasyHttp
+        const val MPAndroidChart ="v3.1.0"
+        const val BaseRecyclerViewAdapterHelper4 = "4.1.4" //Rv适配器
+        const val Serialport = "1.0.0"                     //串口支付设备
+        const val XLog = "1.11.1"                          // xlog
+
+        // 第三方SDK--------------------------------------------------------------
+        const val TencentBugly = "3.3.9"                    // 腾讯Bugly 异常上报
+        const val TencentBuglyNative = "3.8.0"              // Bugly native异常上报
+        const val TencentTBSX5 = "43939"                    // 腾讯X5WebView
+        const val GeTui = "2.13.1.0"                        // 个推
+        const val RabbitMq = "4.12.0"                       //RabbitMq
+    }
+
+    /**
+     * AndroidX相关依赖
+     *
+     * @author Qu Yunshuo
+     * @since 4/24/21 4:01 PM
+     */
+    object AndroidX {
+        const val AndroidJUnitRunner = "androidx.test.runner.AndroidJUnitRunner"
+        const val AppCompat = "androidx.appcompat:appcompat:${Version.AppCompat}"
+        const val CoreKtx = "androidx.core:core-ktx:${Version.CoreKtx}"
+        const val ConstraintLayout =
+            "androidx.constraintlayout:constraintlayout:${Version.ConstraintLayout}"
+        const val TestExtJunit = "androidx.test.ext:junit:${Version.TestExtJunit}"
+        const val TestEspresso = "androidx.test.espresso:espresso-core:${Version.TestEspresso}"
+        const val ActivityKtx = "androidx.activity:activity-ktx:${Version.ActivityKtx}"
+        const val FragmentKtx = "androidx.fragment:fragment-ktx:${Version.FragmentKtx}"
+        const val MultiDex = "androidx.multidex:multidex:${Version.MultiDex}"
+        const val Navigation = "androidx.navigation:navigation-fragment-ktx:${Version.Navigation}"
+        const val NavigationUI = "androidx.navigation:navigation-ui-ktx:${Version.Navigation}"
+        const val Room = "androidx.room:room-runtime:${Version.Room}"
+        const val RoomCompiler = "androidx.room:room-compiler:${Version.Room}"
+        const val RoomKtx = "androidx.room:room-ktx:${Version.Room}"
+        const val WorkKtx = "androidx.work:work-runtime-ktx:${Version.Work}"
+    }
+
+    /**
+     * Android相关依赖
+     *
+     * @author Qu Yunshuo
+     * @since 4/24/21 4:02 PM
+     */
+    object Android {
+        const val Junit = "junit:junit:${Version.Junit}"
+        const val Material = "com.google.android.material:material:${Version.Material}"
+    }
+
+    /**
+     * JetPack相关依赖
+     *
+     * @author Qu Yunshuo
+     * @since 4/24/21 4:02 PM
+     */
+    object JetPack {
+        const val ViewModel = "androidx.lifecycle:lifecycle-viewmodel-ktx:${Version.Lifecycle}"
+        const val ViewModelSavedState =
+            "androidx.lifecycle:lifecycle-viewmodel-savedstate:${Version.Lifecycle}"
+        const val LiveData = "androidx.lifecycle:lifecycle-livedata-ktx:${Version.Lifecycle}"
+        const val Lifecycle = "androidx.lifecycle:lifecycle-runtime-ktx:${Version.Lifecycle}"
+        const val LifecycleCompilerAPT =
+            "androidx.lifecycle:lifecycle-compiler:${Version.Lifecycle}"
+        const val HiltCore = "com.google.dagger:hilt-android:${Version.Hilt}"
+        const val HiltApt = "com.google.dagger:hilt-compiler:${Version.Hilt}"
+    }
+
+    /**
+     * Kotlin相关依赖
+     *
+     * @author Qu Yunshuo
+     * @since 4/24/21 4:02 PM
+     */
+    object Kotlin {
+        const val Kotlin = "org.jetbrains.kotlin:kotlin-stdlib:${Version.Kotlin}"
+        const val CoroutinesCore =
+            "org.jetbrains.kotlinx:kotlinx-coroutines-core:${Version.Coroutines}"
+        const val CoroutinesAndroid =
+            "org.jetbrains.kotlinx:kotlinx-coroutines-android:${Version.Coroutines}"
+    }
+
+    /**
+     * GitHub及其他相关依赖
+     *
+     * @author Qu Yunshuo
+     * @since 4/24/21 4:02 PM
+     */
+    object GitHub {
+        const val OkHttp = "com.squareup.okhttp3:okhttp:${Version.OkHttp}"
+        const val OkHttpInterceptorLogging =
+            "com.squareup.okhttp3:logging-interceptor:${Version.OkHttpInterceptorLogging}"
+        const val Retrofit = "com.squareup.retrofit2:retrofit:${Version.Retrofit}"
+        const val RetrofitConverterGson =
+            "com.squareup.retrofit2:converter-gson:${Version.RetrofitConverterGson}"
+        const val RetrofitConverterScalars =
+            "com.squareup.retrofit2:converter-scalars:${Version.RetrofitConverterScalars}"
+        const val Gson = "com.google.code.gson:gson:${Version.Gson}"
+        const val MMKV = "com.tencent:mmkv-static:${Version.MMKV}"
+        const val AutoSize = "com.github.JessYanCoding:AndroidAutoSize:${Version.AutoSize}"
+        const val ARoute = "com.alibaba:arouter-api:${Version.ARoute}"
+        const val ARouteCompiler = "com.alibaba:arouter-compiler:${Version.ARouteCompiler}"
+        const val RecyclerViewAdapter =
+            "com.github.CymChad:BaseRecyclerViewAdapterHelper:${Version.RecyclerViewAdapter}"
+        const val EventBus = "org.greenrobot:eventbus:${Version.EventBus}"
+        const val EventBusAPT = "org.greenrobot:eventbus-annotation-processor:${Version.EventBus}"
+        const val PermissionX = "com.guolindev.permissionx:permissionx:${Version.PermissionX}"
+        const val LeakCanary = "com.squareup.leakcanary:leakcanary-android:${Version.LeakCanary}"
+        const val AutoService = "com.google.auto.service:auto-service:${Version.AutoService}"
+        const val AutoServiceAnnotations =
+            "com.google.auto.service:auto-service-annotations:${Version.AutoService}"
+        const val Coil = "io.coil-kt:coil:${Version.Coil}"
+        const val CoilGIF = "io.coil-kt:coil-gif:${Version.Coil}"
+        const val CoilSVG = "io.coil-kt:coil-svg:${Version.Coil}"
+        const val CoilVideo = "io.coil-kt:coil-video:${Version.Coil}"
+        const val Toaster = "com.github.getActivity:Toaster:${Version.Toaster}"
+        const val MagicIndicator = "com.github.hackware1993:MagicIndicator:${Version.MagicIndicator}"
+        const val Okhttputils = "com.github.getActivity:EasyHttp:${Version.Okhttputils}"
+        const val MPAndroidChart = "com.github.PhilJay:MPAndroidChart:${Version.MPAndroidChart}"
+        const val BaseRecyclerViewAdapterHelper4= "io.github.cymchad:BaseRecyclerViewAdapterHelper4:${Version.BaseRecyclerViewAdapterHelper4}"
+        const val Serialport ="com.azhon:serialport:${Version.Serialport}"
+        const val XLog = "com.elvishew:xlog:${Version.XLog}"
+    }
+
+    /**
+     * SDK相关依赖
+     *
+     * @author Qu Yunshuo
+     * @since 4/24/21 4:02 PM
+     */
+    object SDK {
+        const val TencentBugly = "com.tencent.bugly:crashreport:${Version.TencentBugly}"
+        const val TencentBuglyNative =
+            "com.tencent.bugly:nativecrashreport:${Version.TencentBuglyNative}"
+        const val TencentTBSX5 = "com.tencent.tbs.tbssdk:sdk:${Version.TencentTBSX5}"
+        const val GeTui = "com.getui:sdk:${Version.GeTui}"
+        const val RabbitMq = "com.rabbitmq:amqp-client:${Version.RabbitMq}"
+
+    }
+}

+ 31 - 0
buildSrc/src/main/kotlin/com/quyunshuo/androidbaseframemvvm/buildsrc/ProjectBuildConfig.kt

@@ -0,0 +1,31 @@
+package com.quyunshuo.androidbaseframemvvm.buildsrc
+
+/**
+ * 项目相关参数配置
+ *
+ * @author Qu Yunshuo
+ * @since 4/24/21 5:56 PM
+ */
+object ProjectBuildConfig {
+    const val compileSdkVersion = 34
+    const val applicationId = "com.quyunshuo.androidbaseframemvvm"
+    const val minSdkVersion = 21
+    const val targetSdkVersion = 34
+    const val versionCode = 1
+    const val versionName = "1.0"
+    const val isAppMode = false
+
+    /**
+     * 项目当前的版本状态
+     * 该状态直接反映当前App是测试版 还是正式版 或者预览版
+     * 正式版:RELEASE、预览版(α)-内部测试版:ALPHA、测试版(β)-公开测试版:BETA
+     */
+    object Version {
+
+        const val RELEASE = "VERSION_STATUS_RELEASE"
+
+        const val ALPHA = "VERSION_STATUS_ALPHA"
+
+        const val BETA = "VERSION_STATUS_BETA"
+    }
+}

+ 14 - 0
buildSrc/src/main/kotlin/com/quyunshuo/androidbaseframemvvm/buildsrc/SDKKeyConfig.kt

@@ -0,0 +1,14 @@
+package com.quyunshuo.androidbaseframemvvm.buildsrc
+
+/**
+ * 存放需要存在本地的SDK的密钥
+ * 这种方式并不安全
+ */
+object SDKKeyConfig {
+
+    /**
+     * Bugly APP_ID
+     * 正式环境需要与测试环境分开 正式ID:""、测试ID:""
+     */
+    const val BUGLY_APP_ID = ""
+}

+ 26 - 0
gradle.properties

@@ -0,0 +1,26 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app"s APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Automatically convert third-party libraries to use AndroidX
+android.enableJetifier=true
+# Kotlin code style for this project: "official" or "obsolete":
+kotlin.code.style=official
+android.defaults.buildfeatures.buildconfig=true
+android.nonTransitiveRClass=false
+android.nonFinalResIds=false
+android.enableR8.fullMode=false
+android.suppressUnsupportedCompileSdk=34

+ 41 - 0
gradle/libs.versions.toml

@@ -0,0 +1,41 @@
+[versions]
+# plugin
+agp = "8.4.0"
+kotlin-android = "1.8.0"
+hilt = "2.44"
+core-ktx = "1.10.1"
+junit = "4.13.2"
+junit-version = "1.1.5"
+espresso-core = "3.5.1"
+lifecycle-runtime-ktx = "2.6.1"
+activity-compose = "1.8.0"
+compose-bom = "2023.08.00"
+
+#lib
+
+
+[libraries]
+androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "core-ktx" }
+junit = { group = "junit", name = "junit", version.ref = "junit" }
+androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junit-version" }
+androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espresso-core" }
+androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle-runtime-ktx" }
+androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activity-compose" }
+androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" }
+androidx-ui = { group = "androidx.compose.ui", name = "ui" }
+androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
+androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
+androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
+androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
+androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
+androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
+
+
+[plugins]
+application = { id = "com.android.application", version.ref = "agp" }
+library = { id = "com.android.library", version.ref = "agp" }
+kotlin = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin-android" }
+hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }
+
+
+[bundles]

BIN
gradle/wrapper/gradle-wrapper.jar


+ 6 - 0
gradle/wrapper/gradle-wrapper.properties

@@ -0,0 +1,6 @@
+#Sat May 22 08:57:40 CST 2021
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.6-bin.zip

+ 172 - 0
gradlew

@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+    echo "$*"
+}
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+  NONSTOP* )
+    nonstop=true
+    ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Escape application args
+save () {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+    echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+  cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"

+ 84 - 0
gradlew.bat

@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega

BIN
img/img2.png


+ 1 - 0
lib_base/.gitignore

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

+ 90 - 0
lib_base/build.gradle

@@ -0,0 +1,90 @@
+//****************************************
+//********** lib_base 的配置文件 ***********
+//****************************************
+
+plugins {
+    alias(libs.plugins.library)
+    alias(libs.plugins.kotlin)
+    alias(libs.plugins.hilt)
+    id "kotlin-kapt"
+}
+
+apply from: '../base_lib.gradle'
+
+import com.quyunshuo.androidbaseframemvvm.buildsrc.*
+
+android {
+
+    buildFeatures {
+        viewBinding = true
+    }
+
+    resourcePrefix "base_"
+    namespace 'com.quyunshuo.androidbaseframemvvm.base'
+}
+
+dependencies {
+    implementation fileTree(dir: "libs", include: ["*.jar"])
+
+    api DependencyConfig.AndroidX.CoreKtx
+    api DependencyConfig.AndroidX.AppCompat
+    api DependencyConfig.AndroidX.ConstraintLayout
+    api DependencyConfig.AndroidX.ActivityKtx
+    api DependencyConfig.AndroidX.FragmentKtx
+    api DependencyConfig.AndroidX.MultiDex
+    api DependencyConfig.AndroidX.Navigation
+    api DependencyConfig.AndroidX.NavigationUI
+
+
+    api DependencyConfig.Android.Material
+
+    api DependencyConfig.Kotlin.Kotlin
+    api DependencyConfig.Kotlin.CoroutinesCore
+    api DependencyConfig.Kotlin.CoroutinesAndroid
+
+    api DependencyConfig.JetPack.ViewModel
+    api DependencyConfig.JetPack.ViewModelSavedState
+    api DependencyConfig.JetPack.LiveData
+    api DependencyConfig.JetPack.Lifecycle
+    api DependencyConfig.JetPack.HiltCore
+
+    api DependencyConfig.GitHub.Gson
+    api DependencyConfig.GitHub.MMKV
+    api DependencyConfig.GitHub.AutoSize
+    api DependencyConfig.GitHub.ARoute
+    api DependencyConfig.GitHub.RecyclerViewAdapter
+    api DependencyConfig.GitHub.EventBus
+    api DependencyConfig.GitHub.PermissionX
+    api DependencyConfig.GitHub.AutoService
+    api DependencyConfig.GitHub.OkHttp
+    api DependencyConfig.GitHub.OkHttpInterceptorLogging
+    api DependencyConfig.GitHub.Retrofit
+    api DependencyConfig.GitHub.RetrofitConverterGson
+    api DependencyConfig.GitHub.RetrofitConverterScalars
+    api DependencyConfig.GitHub.Coil
+    api DependencyConfig.GitHub.CoilGIF
+    api DependencyConfig.GitHub.CoilSVG
+    api DependencyConfig.GitHub.CoilVideo
+    api DependencyConfig.GitHub.Toaster
+    api DependencyConfig.GitHub.MagicIndicator
+    api DependencyConfig.GitHub.Okhttputils
+    api DependencyConfig.GitHub.MPAndroidChart
+    api DependencyConfig.GitHub.BaseRecyclerViewAdapterHelper4
+    api DependencyConfig.GitHub.Serialport
+    api DependencyConfig.GitHub.XLog
+
+    api DependencyConfig.SDK.TencentBugly
+    api DependencyConfig.SDK.TencentBuglyNative
+//    api DependencyConfig.SDK.TencentTBSX5
+    api DependencyConfig.SDK.GeTui
+    api DependencyConfig.SDK.RabbitMq
+
+
+    kapt DependencyConfig.GitHub.ARouteCompiler
+    kapt DependencyConfig.GitHub.EventBusAPT
+    kapt DependencyConfig.GitHub.AutoServiceAnnotations
+    kapt DependencyConfig.JetPack.HiltApt
+    kapt DependencyConfig.JetPack.LifecycleCompilerAPT
+
+    debugApi DependencyConfig.GitHub.LeakCanary
+}

+ 0 - 0
lib_base/consumer-rules.pro


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

+ 1 - 0
lib_base/src/main/AndroidManifest.xml

@@ -0,0 +1 @@
+<manifest />

+ 78 - 0
lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/BaseApplication.kt

@@ -0,0 +1,78 @@
+package com.quyunshuo.androidbaseframemvvm.base
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.util.Log
+import androidx.multidex.MultiDexApplication
+import com.quyunshuo.androidbaseframemvvm.base.app.ActivityLifecycleCallbacksImpl
+import com.quyunshuo.androidbaseframemvvm.base.app.LoadModuleProxy
+import kotlinx.coroutines.*
+import kotlin.system.measureTimeMillis
+
+/**
+ * Application 基类
+ *
+ * @author Qu Yunshuo
+ * @since 4/24/21 5:30 PM
+ */
+open class BaseApplication : MultiDexApplication() {
+
+    private val mCoroutineScope by lazy(mode = LazyThreadSafetyMode.NONE) { MainScope() }
+
+    private val mLoadModuleProxy by lazy(mode = LazyThreadSafetyMode.NONE) { LoadModuleProxy() }
+
+    companion object {
+        // 全局Context
+        @SuppressLint("StaticFieldLeak")
+        lateinit var context: Context
+
+        @SuppressLint("StaticFieldLeak")
+        lateinit var application: BaseApplication
+    }
+
+    override fun attachBaseContext(base: Context) {
+        super.attachBaseContext(base)
+        context = base
+        application = this
+        mLoadModuleProxy.onAttachBaseContext(base)
+    }
+
+    override fun onCreate() {
+        super.onCreate()
+
+        // 全局监听 Activity 生命周期
+        registerActivityLifecycleCallbacks(ActivityLifecycleCallbacksImpl())
+
+        mLoadModuleProxy.onCreate(this)
+
+        // 策略初始化第三方依赖
+        initDepends()
+    }
+
+    /**
+     * 初始化第三方依赖
+     */
+    private fun initDepends() {
+        // 开启一个 Default Coroutine 进行初始化不会立即使用的第三方
+        mCoroutineScope.launch(Dispatchers.Default) {
+            mLoadModuleProxy.initByBackstage()
+        }
+
+        // 前台初始化
+        val allTimeMillis = measureTimeMillis {
+            val depends = mLoadModuleProxy.initByFrontDesk()
+            var dependInfo: String
+            depends.forEach {
+                val dependTimeMillis = measureTimeMillis { dependInfo = it() }
+                Log.d("BaseApplication", "initDepends: $dependInfo : $dependTimeMillis ms")
+            }
+        }
+        Log.d("BaseApplication", "初始化完成 $allTimeMillis ms")
+    }
+
+    override fun onTerminate() {
+        super.onTerminate()
+        mLoadModuleProxy.onTerminate(this)
+        mCoroutineScope.cancel()
+    }
+}

+ 34 - 0
lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/addressenum/PayEnum.kt

@@ -0,0 +1,34 @@
+package com.quyunshuo.androidbaseframemvvm.base.addressenum
+
+import com.quyunshuo.androidbaseframemvvm.base.R
+
+/**
+ * 支付枚举
+ * code:唯一标识
+ * remark:中文名称
+ * nameId:名字id
+ * imgId:图片id
+ * navId:对应的导航
+ */
+enum class PayEnum(val code:String,val remark:String,val nameId:Int,val imgId:Int,val payType:Int) {
+    TWO_CODE("TwoCode","微信支付宝", R.string.base_two_code,R.drawable.quibinary,99),
+    BILL_COIN("BillCoin","纸币硬币",R.string.base_bill_coin,R.drawable.pic_toubi_02,3),
+    NAYAX("Nayax","Nayax",R.string.base_nayax,R.drawable.pic_shuakazhifu,4),//闲置模式,非闲置模式
+    MDB_NO_CASH("MdbNoCash","MDB信用卡",R.string.base_mdb_no_cash,R.drawable.pic_shuakazhifu,4),//级别2,级别3
+    SOEPAY("Soepay","Soepay",R.string.base_soepay,R.drawable.pic_shuakazhifu,4),//Soepay
+    FREE("Free","免费制作", R.string.base_free_pay,R.drawable.pic_shuakazhifu,0);//无需支付
+
+
+    companion object {
+        fun getEnumByValue(code: String): PayEnum {
+
+            for (anEnum in PayEnum.values()) {
+                if (anEnum.code == code) {
+                    return anEnum
+                }
+            }
+            return PayEnum.TWO_CODE
+        }
+
+    }
+}

+ 14 - 0
lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/addressenum/PlcD2StatusEnum.kt

@@ -0,0 +1,14 @@
+package com.quyunshuo.androidbaseframemvvm.base.addressenum
+
+enum class PlcD2StatusEnum(val address: String, val aName: String) {
+    D2_99("99", "整机初次上电"),
+
+    D2_98("98", "开机完毕"),
+    D2_0("0", "开机中或待机"),
+
+    D2_1("1", "做冰沙中"),
+    D2_2("2", "冰沙制作完成"),
+
+    D2_3("3", "PLC报警"),
+
+}

+ 51 - 0
lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/addressenum/PlcDebugAddressEnum.kt

@@ -0,0 +1,51 @@
+package com.quyunshuo.androidbaseframemvvm.base.addressenum
+
+import com.quyunshuo.androidbaseframemvvm.base.R
+
+enum class PlcDebugAddressEnum(val address: String, val aName: String,val aNameId:Int) {
+    IO_SE_01("000101","次核点位1",R.string.base_plc_debug_1),
+    IO_SE_02("000102","次核点位2",R.string.base_plc_debug_2),
+    IO_SE_03("000103","次核点位3",R.string.base_plc_debug_3),
+    IO_SE_04("000104","次核点位4",R.string.base_plc_debug_4),
+    IO_SE_05("000105","次核点位5",R.string.base_plc_debug_5),
+    IO_SE_06("000106","次核点位6",R.string.base_plc_debug_6),
+    IO_SE_07("000107","次核点位7",R.string.base_plc_debug_7),
+    IO_SE_08("000108","次核点位8",R.string.base_plc_debug_8),
+    IO_SE_09("000109","次核点位9",R.string.base_plc_debug_9),
+    IO_SE_10("000110","次核点位10",R.string.base_plc_debug_10),
+    IO_SE_11("000111","次核点位11",R.string.base_plc_debug_11),
+    IO_SE_12("000112","次核点位12",R.string.base_plc_debug_12),
+    IO_SE_13("000113","次核点位13",R.string.base_plc_debug_13),
+    IO_SE_14("000114","次核点位14",R.string.base_plc_debug_14),
+    IO_SE_15("000115","次核点位15",R.string.base_plc_debug_15),
+    IO_SE_16("000116","次核点位16",R.string.base_plc_debug_16),
+    IO_SE_17("000117","次核点位17",R.string.base_plc_debug_17),
+    IO_SE_18("000118","次核点位18",R.string.base_plc_debug_18),
+    IO_SE_19("000119","次核点位19",R.string.base_plc_debug_19),
+    IO_SE_20("000120","次核点位20",R.string.base_plc_debug_20),
+
+}
+
+//    M111("M111", "蒸发器风机", R.string.base_plc_debug_1),
+//    M116("M116", "蒸发器压缩机",R.string.base_plc_debug_2),
+//    M166("M166", "蒸发器电机",R.string.base_plc_debug_3),
+//    M182("M182", "取餐门",R.string.base_plc_debug_4),
+//    M174("M174", "口味皮带1",R.string.base_plc_debug_5),
+//    M175("M175", "口味皮带2",R.string.base_plc_debug_6),
+//    M176("M176", "口味皮带3",R.string.base_plc_debug_7),
+//    M177("M177", "口味皮带4",R.string.base_plc_debug_8),
+//    M173("M173", "落杯",R.string.base_plc_debug_9),
+//    M171("M171", "转杯",R.string.base_plc_debug_10),
+//    M184("M184", "清洗蠕动泵",R.string.base_plc_debug_11),
+//    M178("M178", "口味泵1",R.string.base_plc_debug_12),
+//    M179("M179", "口味泵2",R.string.base_plc_debug_13),
+//    M180("M180", "口味泵3",R.string.base_plc_debug_14),
+//    M181("M181", "口味泵4",R.string.base_plc_debug_15),
+//    M199("M199", "拔针1",R.string.base_plc_debug_16),
+//    M198("M198", "插针1",R.string.base_plc_debug_17),
+//    M209("M209", "拔针2",R.string.base_plc_debug_18),
+//    M208("M208", "插针2",R.string.base_plc_debug_19),
+//    M219("M219", "拔针3",R.string.base_plc_debug_20),
+//    M218("M218", "插针3",R.string.base_plc_debug_21),
+//    M229("M229", "拔针4",R.string.base_plc_debug_22),
+//    M228("M228", "插针4",R.string.base_plc_debug_23)

+ 10 - 0
lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/addressenum/PlcHomeAddressEnum.kt

@@ -0,0 +1,10 @@
+package com.quyunshuo.androidbaseframemvvm.base.addressenum
+
+enum class PlcHomeAddressEnum(val address: String, val aName: String,) {
+    D170("D170","蒸汽温度"),
+    D2("D2","机器状态"),
+    D91("D91","口味"),
+    M1("M1","开始做冰沙"),
+    RM1("RM1","复位 开始做冰沙")
+
+}

+ 16 - 0
lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/addressenum/PlcParamAddressEnum.kt

@@ -0,0 +1,16 @@
+package com.quyunshuo.androidbaseframemvvm.base.addressenum
+
+import com.quyunshuo.androidbaseframemvvm.base.R
+
+enum class PlcParamAddressEnum(val address: String, val aName: String,val aNameId:Int) {
+    D232("D232", "关闭制冷时间", R.string.base_plc_param_1),
+    D231("D231", "抽口味时间",R.string.base_plc_param_2),
+    D230("D230", "蒸发器温度到达才抽水",R.string.base_plc_param_3),
+    D233("D233", "皮带1延时时间",R.string.base_plc_param_4),
+    D234("D234", "拔针时间",R.string.base_plc_param_5),
+    D235("D235", "一瓶饮料所抽的时间",R.string.base_plc_param_6),
+    D236("D236", "制作一次的用量",R.string.base_plc_param_7),
+    D447("D447", "蒸发器保温下限",R.string.base_plc_param_8),
+    D448("D448", "蒸发器保温上限",R.string.base_plc_param_9)
+
+}

+ 7 - 0
lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/addressenum/PlcSettingAddressEnum.kt

@@ -0,0 +1,7 @@
+package com.quyunshuo.androidbaseframemvvm.base.addressenum
+
+enum class PlcSettingAddressEnum(val address: String, val aName: String,) {
+    M3("M3","开机复位"),
+    RM3("RM3","开机复位"),
+
+}

+ 26 - 0
lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/addressenum/ProTypeEnum.kt

@@ -0,0 +1,26 @@
+package com.quyunshuo.androidbaseframemvvm.base.addressenum
+
+/**
+ * 商品:
+ */
+enum class ProTypeEnum(var proValue: Int, var chineseName: String) {
+    PRO_1(1, "玫瑰精灵"),
+    PRO_2(2, "五彩缤纷"),
+    PRO_3(3, "小棉袄"),
+    PRO_4(4, "彩色精灵");
+
+    companion object {
+        fun getEnumByValue(chineseName: String): ProTypeEnum {
+
+            for (anEnum in ProTypeEnum.values()) {
+                if (anEnum.chineseName == chineseName) {
+                    return anEnum
+                }
+            }
+            return PRO_1
+        }
+
+    }
+
+
+}

+ 51 - 0
lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/app/ActivityLifecycleCallbacksImpl.kt

@@ -0,0 +1,51 @@
+package com.quyunshuo.androidbaseframemvvm.base.app
+
+import android.app.Activity
+import android.app.Application
+import android.os.Bundle
+import android.util.Log
+import com.quyunshuo.androidbaseframemvvm.base.utils.ActivityStackManager
+import com.quyunshuo.androidbaseframemvvm.base.utils.ForegroundBackgroundHelper
+
+/**
+ * Activity生命周期监听
+ *
+ * @author Qu Yunshuo
+ * @since 4/20/21 9:10 AM
+ */
+class ActivityLifecycleCallbacksImpl : Application.ActivityLifecycleCallbacks {
+
+    private val TAG = "ActivityLifecycle"
+
+    override fun onActivityCreated(activity: Activity, bundle: Bundle?) {
+        ActivityStackManager.addActivityToStack(activity)
+        Log.e(TAG, "${activity.javaClass.simpleName} --> onActivityCreated")
+    }
+
+    override fun onActivityStarted(activity: Activity) {
+        Log.e(TAG, "${activity.javaClass.simpleName} --> onActivityStarted")
+        ForegroundBackgroundHelper.onActivityStarted()
+    }
+
+    override fun onActivityResumed(activity: Activity) {
+        Log.e(TAG, "${activity.javaClass.simpleName} --> onActivityResumed")
+    }
+
+    override fun onActivityPaused(activity: Activity) {
+        Log.e(TAG, "${activity.javaClass.simpleName} --> onActivityPaused")
+    }
+
+    override fun onActivityStopped(activity: Activity) {
+        Log.e(TAG, "${activity.javaClass.simpleName} --> onActivityStopped")
+        ForegroundBackgroundHelper.onActivityStopped()
+    }
+
+    override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
+        Log.e(TAG, "${activity.javaClass.simpleName} --> onActivitySaveInstanceState")
+    }
+
+    override fun onActivityDestroyed(activity: Activity) {
+        ActivityStackManager.popActivityToStack(activity)
+        Log.e(TAG, "${activity.javaClass.simpleName} --> onActivityDestroyed")
+    }
+}

+ 42 - 0
lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/app/ApplicationLifecycle.kt

@@ -0,0 +1,42 @@
+package com.quyunshuo.androidbaseframemvvm.base.app
+
+import android.app.Application
+import android.content.Context
+
+/**
+ * Application 生命周期 用于初始化各个组件
+ *
+ * @author Qu Yunshuo
+ * @since 4/23/21 5:22 PM
+ */
+interface ApplicationLifecycle {
+
+    /**
+     * 同[Application.attachBaseContext]
+     * @param context Context
+     */
+    fun onAttachBaseContext(context: Context)
+
+    /**
+     * 同[Application.onCreate]
+     * @param application Application
+     */
+    fun onCreate(application: Application)
+
+    /**
+     * 同[Application.onTerminate]
+     * @param application Application
+     */
+    fun onTerminate(application: Application)
+
+    /**
+     * 主线程前台初始化
+     * @return MutableList<() -> String> 初始化方法集合
+     */
+    fun initByFrontDesk(): MutableList<() -> String>
+
+    /**
+     * 不需要立即初始化的放在这里进行后台初始化
+     */
+    fun initByBackstage()
+}

+ 63 - 0
lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/app/LoadModuleProxy.kt

@@ -0,0 +1,63 @@
+package com.quyunshuo.androidbaseframemvvm.base.app
+
+import android.app.Application
+import android.content.Context
+import android.util.Log
+import java.util.*
+
+/**
+ * 加载组件代理类
+ * 组件初始化的工作将由该代理类代理实现
+ *
+ * @author Qu Yunshuo
+ * @since 4/23/21 5:37 PM
+ */
+class LoadModuleProxy : ApplicationLifecycle {
+
+    private var mLoader: ServiceLoader<ApplicationLifecycle> =
+        ServiceLoader.load(ApplicationLifecycle::class.java)
+
+    /**
+     * 同[Application.attachBaseContext]
+     * @param context Context
+     */
+    override fun onAttachBaseContext(context: Context) {
+        mLoader.forEach {
+            Log.d("ApplicationInit", it.toString())
+            it.onAttachBaseContext(context)
+        }
+    }
+
+    /**
+     * 同[Application.onCreate]
+     * @param application Application
+     */
+    override fun onCreate(application: Application) {
+        mLoader.forEach { it.onCreate(application) }
+    }
+
+    /**
+     * 同[Application.onTerminate]
+     * @param application Application
+     */
+    override fun onTerminate(application: Application) {
+        mLoader.forEach { it.onTerminate(application) }
+    }
+
+    /**
+     * 主线程前台初始化
+     * @return MutableList<() -> String> 初始化方法集合
+     */
+    override fun initByFrontDesk(): MutableList<() -> String> {
+        val list: MutableList<() -> String> = mutableListOf()
+        mLoader.forEach { list.addAll(it.initByFrontDesk()) }
+        return list
+    }
+
+    /**
+     * 不需要立即初始化的放在这里进行后台初始化
+     */
+    override fun initByBackstage() {
+        mLoader.forEach { it.initByBackstage() }
+    }
+}

+ 16 - 0
lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/constant/VersionStatus.kt

@@ -0,0 +1,16 @@
+package com.quyunshuo.androidbaseframemvvm.base.constant
+
+/**
+ * 版本状态
+ *
+ * @author Qu Yunshuo
+ * @since 4/20/21 9:05 AM
+ */
+object VersionStatus {
+
+    const val RELEASE = "VERSION_STATUS_RELEASE"
+
+    const val ALPHA = "VERSION_STATUS_ALPHA"
+
+    const val BETA = "VERSION_STATUS_BETA"
+}

+ 25 - 0
lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/ktx/ActivityKtx.kt

@@ -0,0 +1,25 @@
+package com.quyunshuo.androidbaseframemvvm.base.ktx
+
+import android.app.Activity
+import android.view.WindowManager
+import androidx.appcompat.app.AppCompatActivity
+import androidx.lifecycle.Lifecycle
+
+/**
+ * 设置当前 [Activity] 是否允许截屏操作
+ *
+ * @receiver [Activity]
+ * @param isAllow Boolean 是否允许截屏
+ */
+fun Activity.isAllowScreenCapture(isAllow: Boolean) {
+    if (isAllow) {
+        window?.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
+    } else {
+        window?.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
+    }
+}
+
+/**
+ * 判断当前 Activity 是否是 [Lifecycle.State.RESUMED]
+ */
+fun AppCompatActivity.isResumed(): Boolean = lifecycle.currentState == Lifecycle.State.RESUMED

+ 21 - 0
lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/ktx/EditTextKtx.kt

@@ -0,0 +1,21 @@
+package com.quyunshuo.androidbaseframemvvm.base.ktx
+
+import android.text.InputFilter
+import android.widget.EditText
+
+/**
+ * EditText相关扩展方法
+ *
+ * @author Qu Yunshuo
+ * @since 2020/9/17
+ */
+
+/**
+ * 过滤掉空格和回车
+ */
+fun EditText.filterBlankAndCarriageReturn() {
+    val filterList = mutableListOf<InputFilter>()
+    filterList.addAll(filters)
+    filterList.add(InputFilter { source, _, _, _, _, _ -> if (source == " " || source == "\n") "" else null })
+    filters = filterList.toTypedArray()
+}

+ 30 - 0
lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/ktx/LifecycleOwnerKtx.kt

@@ -0,0 +1,30 @@
+package com.quyunshuo.androidbaseframemvvm.base.ktx
+
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.LiveData
+
+/**
+ * 对LiveData订阅的简化封装
+ *
+ * 使用示例
+ * ```
+ *  override fun initObserve() {
+ *      observeLiveData(mViewModel.stateViewLD, ::processStateViewLivaData)
+ *  }
+ *
+ *  private fun processStateViewLivaData(data: StateLayoutEnum) {
+ *      ...
+ *  }
+ * ```
+ *
+ * @receiver LifecycleOwner
+ * @param liveData LiveData<T> 需要进行订阅的LiveData
+ * @param action action: (t: T) -> Unit 处理订阅内容的方法
+ * @return Unit
+ */
+inline fun <T> LifecycleOwner.observeLiveData(
+    liveData: LiveData<T>,
+    crossinline action: (t: T) -> Unit
+) {
+    liveData.observe(this) { it?.let { t -> action(t) } }
+}

+ 22 - 0
lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/ktx/NetUtils.kt

@@ -0,0 +1,22 @@
+package com.ad.newad
+
+import android.content.Context
+import android.net.ConnectivityManager
+import android.net.NetworkCapabilities
+
+/**
+ * 网络相关工具类
+ */
+object NetUtils {
+
+    /**
+     * 当前网络是否是 Wi-Fi
+     */
+    fun currentNetIsWiFi(context: Context): Boolean {
+        val connectivityManager =
+            context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
+        val networkCapabilities =
+            connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)
+        return networkCapabilities?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) ?: false
+    }
+}

+ 21 - 0
lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/ktx/PopupWindowKtx.kt

@@ -0,0 +1,21 @@
+package com.quyunshuo.androidbaseframemvvm.base.ktx
+
+import android.view.View
+import android.view.ViewGroup
+import android.widget.PopupWindow
+
+/**
+ * PopupWindow相关扩展
+ *
+ * @author Qu Yunshuo
+ * @since 1/4/21 10:48 AM
+ */
+
+/**
+ * 测量view宽高
+ */
+fun PopupWindow.makeDropDownMeasureSpec(measureSpec: Int): Int {
+    val mode =
+        if (measureSpec == ViewGroup.LayoutParams.WRAP_CONTENT) View.MeasureSpec.UNSPECIFIED else View.MeasureSpec.EXACTLY
+    return View.MeasureSpec.makeMeasureSpec(View.MeasureSpec.getSize(measureSpec), mode)
+}

+ 75 - 0
lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/ktx/SizeUnitKtx.kt

@@ -0,0 +1,75 @@
+package com.quyunshuo.androidbaseframemvvm.base.ktx
+
+import android.content.Context
+import androidx.fragment.app.Fragment
+
+/**
+ * @Author: QuYunShuo
+ * @Time: 2020/9/17
+ * @Class: SizeUnitKtx
+ * @Remark: 尺寸单位换算相关扩展属性
+ */
+
+/**
+ * dp 转 px
+ */
+fun Context.dp2px(dpValue: Float): Int {
+    val scale = resources.displayMetrics.density
+    return (dpValue * scale + 0.5f).toInt()
+}
+
+/**
+ * px 转 dp
+ */
+fun Context.px2dp(pxValue: Float): Int {
+    val scale = resources.displayMetrics.density
+    return (pxValue / scale + 0.5f).toInt()
+}
+
+/**
+ * sp 转 px
+ */
+fun Context.sp2px(spValue: Float): Int {
+    val scale = resources.displayMetrics.scaledDensity
+    return (spValue * scale + 0.5f).toInt()
+}
+
+/**
+ * px 转 sp
+ */
+fun Context.px2sp(pxValue: Float): Int {
+    val scale = resources.displayMetrics.scaledDensity
+    return (pxValue / scale + 0.5f).toInt()
+}
+
+/**
+ * dp 转 px
+ */
+fun Fragment.dp2px(dpValue: Float): Int {
+    val scale = resources.displayMetrics.density
+    return (dpValue * scale + 0.5f).toInt()
+}
+
+/**
+ * px 转 dp
+ */
+fun Fragment.px2dp(pxValue: Float): Int {
+    val scale = resources.displayMetrics.density
+    return (pxValue / scale + 0.5f).toInt()
+}
+
+/**
+ * sp 转 px
+ */
+fun Fragment.sp2px(spValue: Float): Int {
+    val scale = resources.displayMetrics.scaledDensity
+    return (spValue * scale + 0.5f).toInt()
+}
+
+/**
+ * px 转 sp
+ */
+fun Fragment.px2sp(pxValue: Float): Int {
+    val scale = resources.displayMetrics.scaledDensity
+    return (pxValue / scale + 0.5f).toInt()
+}

+ 42 - 0
lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/ktx/VideoViewKtx.kt

@@ -0,0 +1,42 @@
+package com.quyunshuo.androidbaseframemvvm.base.ktx
+
+import android.media.MediaPlayer
+import android.view.ViewGroup
+import android.widget.VideoView
+
+/**
+ * 根据视频的尺寸与容器尺寸比例,动态调整 [VideoView] 的尺寸以适应视频的尺寸
+ * 解决 [VideoView] 尺寸比例与视频尺寸比例不一致导致视频拉伸的问题
+ *
+ * 容器可以是屏幕或者 [VideoView]
+ *
+ * 使用方式:
+ * 1. 通过 [VideoView.setOnPreparedListener] 向 [VideoView] 设置 [MediaPlayer.OnPreparedListener] 监听
+ * 2. 通过 [MediaPlayer.OnPreparedListener] 回调获取到视频的真实宽高,调用该方法传入参数进行适配
+ * 3. 如果需要考虑横竖屏切换,请在横竖屏改变监听回调中再次调用该方法进行适配
+ *
+ * @receiver [VideoView]
+ * @param containerW Float 容器的真实宽
+ * @param containerH Float 容器的真实高
+ * @param videoW Float 视频的真实宽
+ * @param videoH Float 视频的真实高
+ */
+fun VideoView.resetVideoViewDimensions(
+    containerW: Float,
+    containerH: Float,
+    videoW: Float,
+    videoH: Float,
+) {
+    // 计算宽高比进行调整宽高
+    this.layoutParams = if (videoW / containerW < videoH / containerH) {
+        this.layoutParams.apply {
+            width = ViewGroup.LayoutParams.WRAP_CONTENT
+            height = ViewGroup.LayoutParams.MATCH_PARENT
+        }
+    } else {
+        this.layoutParams.apply {
+            width = ViewGroup.LayoutParams.MATCH_PARENT
+            height = ViewGroup.LayoutParams.WRAP_CONTENT
+        }
+    }
+}

+ 226 - 0
lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/ktx/ViewKtx.kt

@@ -0,0 +1,226 @@
+package com.quyunshuo.androidbaseframemvvm.base.ktx
+
+import android.animation.Animator
+import android.animation.IntEvaluator
+import android.animation.ValueAnimator
+import android.view.View
+import android.view.ViewGroup
+import com.quyunshuo.androidbaseframemvvm.base.view.OnSingleClickListener
+
+/**
+ * @Author: QuYunShuo
+ * @Time: 2020/9/1
+ * @Class: ViewKtx
+ * @Remark: View相关的扩展方法
+ */
+
+/*************************************** View可见性相关 ********************************************/
+/**
+ * 隐藏View
+ * @receiver View
+ */
+fun View.gone() {
+    visibility = View.GONE
+}
+
+/**
+ * 显示View
+ * @receiver View
+ */
+fun View.visible() {
+    visibility = View.VISIBLE
+}
+
+/**
+ * View不可见但存在原位置
+ * @receiver View
+ */
+fun View.invisible() {
+    visibility = View.INVISIBLE
+}
+
+/**
+ * 设置 View 为 [View.VISIBLE]
+ * 如果 [isVisible] 值为true,将 [View.setVisibility] 设置为 [View.VISIBLE],反之为 [View.GONE]
+ *
+ * @receiver View
+ * @param isVisible Boolean 是否显示
+ */
+fun View.setVisible(isVisible: Boolean) {
+    if (isVisible) visible() else gone()
+}
+
+/**
+ * 设置 View 为 [View.GONE]
+ * 如果 [isGone] 值为true,将 [View.setVisibility] 设置为 [View.GONE],反之为 [View.VISIBLE]
+ *
+ * @receiver View
+ * @param isGone Boolean 是否隐藏
+ */
+fun View.setGone(isGone: Boolean) {
+    if (isGone) visible() else gone()
+}
+
+/*************************************** View宽高相关 ********************************************/
+/**
+ * 设置 View 的高度
+ * @receiver View
+ * @param height Int 目标高度
+ * @return View
+ */
+fun View.height(height: Int): View {
+    val params = layoutParams ?: ViewGroup.LayoutParams(
+        ViewGroup.LayoutParams.MATCH_PARENT,
+        ViewGroup.LayoutParams.WRAP_CONTENT
+    )
+    params.height = height
+    layoutParams = params
+    return this
+}
+
+/**
+ * 设置View的宽度
+ * @receiver View
+ * @param width Int 目标宽度
+ * @return View
+ */
+fun View.width(width: Int): View {
+    val params = layoutParams ?: ViewGroup.LayoutParams(
+        ViewGroup.LayoutParams.MATCH_PARENT,
+        ViewGroup.LayoutParams.WRAP_CONTENT
+    )
+    params.width = width
+    layoutParams = params
+    return this
+}
+
+/**
+ * 设置View的宽度和高度
+ * @receiver View
+ * @param width Int 要设置的宽度
+ * @param height Int 要设置的高度
+ * @return View
+ */
+fun View.widthAndHeight(width: Int, height: Int): View {
+    val params = layoutParams ?: ViewGroup.LayoutParams(
+        ViewGroup.LayoutParams.MATCH_PARENT,
+        ViewGroup.LayoutParams.WRAP_CONTENT
+    )
+    params.width = width
+    params.height = height
+    layoutParams = params
+    return this
+}
+
+/**
+ * 设置宽度,带有过渡动画
+ * @param targetValue 目标宽度
+ * @param duration 时长
+ * @param action 可选行为
+ * @return 动画
+ */
+fun View.animateWidth(
+    targetValue: Int, duration: Long = 400, listener: Animator.AnimatorListener? = null,
+    action: ((Float) -> Unit)? = null
+): ValueAnimator? {
+    var animator: ValueAnimator? = null
+    post {
+        animator = ValueAnimator.ofInt(width, targetValue).apply {
+            addUpdateListener {
+                width(it.animatedValue as Int)
+                action?.invoke((it.animatedFraction))
+            }
+            if (listener != null) addListener(listener)
+            setDuration(duration)
+            start()
+        }
+    }
+    return animator
+}
+
+/**
+ * 设置高度,带有过渡动画
+ * @param targetValue 目标高度
+ * @param duration 时长
+ * @param action 可选行为
+ * @return 动画
+ */
+fun View.animateHeight(
+    targetValue: Int,
+    duration: Long = 400,
+    listener: Animator.AnimatorListener? = null,
+    action: ((Float) -> Unit)? = null
+): ValueAnimator? {
+    var animator: ValueAnimator? = null
+    post {
+        animator = ValueAnimator.ofInt(height, targetValue).apply {
+            addUpdateListener {
+                height(it.animatedValue as Int)
+                action?.invoke((it.animatedFraction))
+            }
+            if (listener != null) addListener(listener)
+            setDuration(duration)
+            start()
+        }
+    }
+    return animator
+}
+
+/**
+ * 设置宽度和高度,带有过渡动画
+ * @param targetWidth 目标宽度
+ * @param targetHeight 目标高度
+ * @param duration 时长
+ * @param action 可选行为
+ * @return 动画
+ */
+fun View.animateWidthAndHeight(
+    targetWidth: Int,
+    targetHeight: Int,
+    duration: Long = 400,
+    listener: Animator.AnimatorListener? = null,
+    action: ((Float) -> Unit)? = null
+): ValueAnimator? {
+    var animator: ValueAnimator? = null
+    post {
+        val startHeight = height
+        val evaluator = IntEvaluator()
+        animator = ValueAnimator.ofInt(width, targetWidth).apply {
+            addUpdateListener {
+                widthAndHeight(
+                    it.animatedValue as Int,
+                    evaluator.evaluate(it.animatedFraction, startHeight, targetHeight)
+                )
+                action?.invoke((it.animatedFraction))
+            }
+            if (listener != null) addListener(listener)
+            setDuration(duration)
+            start()
+        }
+    }
+    return animator
+}
+
+/*************************************** View其他 ********************************************/
+/**
+ * 获取View id
+ */
+fun View.getViewId(): Int {
+    var id = id
+    if (id == View.NO_ID) {
+        id = View.generateViewId()
+    }
+    return id
+}
+
+/**
+ * 给 [View] 设置带有防抖效果的点击事件
+ *
+ * @receiver [View]
+ * @param delayTime Int 防抖间隔时间,单位是毫秒,默认值 500ms
+ * @param listener (v: View) -> Unit 具体的点击事件
+ * @see OnSingleClickListener
+ */
+fun View.setOnSingleClickListener(delayTime: Int = 500, listener: (v: View) -> Unit) {
+    setOnClickListener(OnSingleClickListener(delayTime, listener))
+}

+ 104 - 0
lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/ktx/ViewModelKtx.kt

@@ -0,0 +1,104 @@
+package com.quyunshuo.androidbaseframemvvm.base.ktx
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import kotlinx.coroutines.*
+
+/**
+ * 开启一个线程调度模式为[Dispatchers.IO]的协程 有默认的异常处理器
+ *
+ * **sample:**
+ * ```
+ * class SampleViewModel : ViewModel() {
+ *
+ *     fun sample() {
+ *         launchIO {
+ *             // 协程体
+ *         }
+ *         launchIO(exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable ->
+ *             // exception handling
+ *         }) {
+ *             // 协程体
+ *         }
+ *     }
+ * }
+ * ```
+ *
+ * @receiver ViewModel
+ *
+ * @param exceptionHandler CoroutineExceptionHandler 异常处理器
+ * @param block suspend CoroutineScope.() -> Unit 协程体
+ * @return Job
+ */
+fun ViewModel.launchIO(
+    exceptionHandler: CoroutineExceptionHandler = CoroutineExceptionHandler { _, throwable ->
+        throwable.printStackTrace()
+    },
+    block: suspend CoroutineScope.() -> Unit
+): Job = viewModelScope.launch(Dispatchers.IO + exceptionHandler, block = block)
+
+/**
+ * 开启一个线程调度模式为[Dispatchers.Default]的协程 有默认的异常处理器
+ *
+ * **sample:**
+ * ```
+ * class SampleViewModel : ViewModel() {
+ *
+ *     fun sample() {
+ *         launchDefault {
+ *             // 协程体
+ *         }
+ *         launchDefault(exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable ->
+ *             // exception handling
+ *         }) {
+ *             // 协程体
+ *         }
+ *     }
+ * }
+ * ```
+ *
+ * @receiver ViewModel
+ *
+ * @param exceptionHandler CoroutineExceptionHandler 异常处理器
+ * @param block suspend CoroutineScope.() -> Unit 协程体
+ * @return Job
+ */
+fun ViewModel.launchDefault(
+    exceptionHandler: CoroutineExceptionHandler = CoroutineExceptionHandler { _, throwable ->
+        throwable.printStackTrace()
+    },
+    block: suspend CoroutineScope.() -> Unit
+): Job = viewModelScope.launch(Dispatchers.Default + exceptionHandler, block = block)
+
+/**
+ * 开启一个线程调度模式为[Dispatchers.Main]的协程 有默认的异常处理器
+ *
+ * **sample:**
+ * ```
+ * class SampleViewModel : ViewModel() {
+ *
+ *     fun sample() {
+ *         launchMain {
+ *             // 协程体
+ *         }
+ *         launchMain(exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable ->
+ *             // exception handling
+ *         }) {
+ *             // 协程体
+ *         }
+ *     }
+ * }
+ * ```
+ *
+ * @receiver ViewModel
+ *
+ * @param exceptionHandler CoroutineExceptionHandler 异常处理器
+ * @param block suspend CoroutineScope.() -> Unit 协程体
+ * @return Job
+ */
+fun ViewModel.launchMain(
+    exceptionHandler: CoroutineExceptionHandler = CoroutineExceptionHandler { _, throwable ->
+        throwable.printStackTrace()
+    },
+    block: suspend CoroutineScope.() -> Unit
+): Job = viewModelScope.launch(Dispatchers.Main + exceptionHandler, block = block)

+ 38 - 0
lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/ktx/ViewPager2Ktx.kt

@@ -0,0 +1,38 @@
+package com.quyunshuo.androidbaseframemvvm.base.ktx
+
+import android.view.View
+import androidx.recyclerview.widget.RecyclerView
+import androidx.viewpager2.widget.ViewPager2
+
+/**
+ * 设置ViewPager2的过度滚动模式为绝不允许用户过度滚动此视图
+ * @receiver ViewPager2
+ */
+fun ViewPager2.setOverScrollModeToNever() {
+    val childView: View = this.getChildAt(0)
+    if (childView is RecyclerView) {
+        childView.overScrollMode = RecyclerView.OVER_SCROLL_NEVER
+    }
+}
+
+/**
+ * 设置ViewPager2的过度滚动模式为始终允许用户过度滚动此视图,前提是它是可以滚动的视图
+ * @receiver ViewPager2
+ */
+fun ViewPager2.setOverScrollModeToAlways() {
+    val childView: View = this.getChildAt(0)
+    if (childView is RecyclerView) {
+        childView.overScrollMode = RecyclerView.OVER_SCROLL_ALWAYS
+    }
+}
+
+/**
+ * 设置ViewPager2的过度滚动模式为仅当内容大到足以有意义地滚动时,才允许用户过度滚动此视图,前提是它是可以滚动的视图。
+ * @receiver ViewPager2
+ */
+fun ViewPager2.setOverScrollModeToIfContentScrolls() {
+    val childView: View = this.getChildAt(0)
+    if (childView is RecyclerView) {
+        childView.overScrollMode = RecyclerView.OVER_SCROLL_IF_CONTENT_SCROLLS
+    }
+}

+ 31 - 0
lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/mvvm/m/BaseRepository.kt

@@ -0,0 +1,31 @@
+package com.quyunshuo.androidbaseframemvvm.base.mvvm.m
+
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.FlowCollector
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOn
+
+/**
+ * 仓库层 Repository 基类
+ *
+ * @author Qu Yunshuo
+ * @since 8/27/20
+ */
+open class BaseRepository {
+
+    /**
+     * 发起请求封装
+     * 该方法将flow的执行切换至IO线程
+     *
+     * @param requestBlock 请求的整体逻辑
+     * @return Flow<T> @BuilderInference block: suspend FlowCollector<T>.() -> Unit
+     */
+    protected fun <T> request(
+        requestBlock: suspend FlowCollector<T>.() -> Unit
+    ): Flow<T> {
+        return flow(block = requestBlock).flowOn(Dispatchers.IO)
+    }
+
+
+}

+ 106 - 0
lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/mvvm/v/BaseFrameActivity.kt

@@ -0,0 +1,106 @@
+package com.quyunshuo.androidbaseframemvvm.base.mvvm.v
+
+import android.content.res.Resources
+import android.os.Bundle
+import android.os.Debug
+import android.os.Looper
+import android.util.Log
+import androidx.appcompat.app.AppCompatActivity
+import androidx.viewbinding.ViewBinding
+import com.alibaba.android.arouter.launcher.ARouter
+import com.quyunshuo.androidbaseframemvvm.base.R
+import com.quyunshuo.androidbaseframemvvm.base.mvvm.vm.BaseViewModel
+import com.quyunshuo.androidbaseframemvvm.base.utils.*
+import com.quyunshuo.androidbaseframemvvm.base.utils.network.AutoRegisterNetListener
+import com.quyunshuo.androidbaseframemvvm.base.utils.network.NetworkStateChangeListener
+import com.quyunshuo.androidbaseframemvvm.base.utils.network.NetworkTypeEnum
+import me.jessyan.autosize.AutoSizeCompat
+
+/**
+ * Activity基类
+ *
+ * @author Qu Yunshuo
+ * @since 8/27/20
+ */
+abstract class BaseFrameActivity<VB : ViewBinding, VM : BaseViewModel> : AppCompatActivity(),
+    FrameView<VB>, NetworkStateChangeListener {
+
+    protected abstract val mViewModel: VM
+
+    protected val mBinding: VB by lazy(mode = LazyThreadSafetyMode.NONE) { createVB() }
+
+    /**
+     * 是否有 [RegisterEventBus] 注解,避免重复调用 [Class.isAnnotation]
+     */
+    private var mHaveRegisterEventBus = false
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(mBinding.root)
+        // ARouter 依赖注入
+        ARouter.getInstance().inject(this)
+
+        // 根据子类是否有 RegisterEventBus 注解決定是否进行注册 EventBus
+        if (javaClass.isAnnotationPresent(RegisterEventBus::class.java)) {
+            mHaveRegisterEventBus = true
+            EventBusUtils.register(this)
+        }
+
+        setStatusBar()
+        mBinding.initView()
+        initNetworkListener()
+        initObserve()
+        initRequestData()
+    }
+
+    /**
+     * 初始化网络状态监听
+     * @return Unit
+     */
+    private fun initNetworkListener() {
+        lifecycle.addObserver(AutoRegisterNetListener(this))
+    }
+
+    /**
+     * 设置状态栏
+     * 子类需要自定义时重写该方法即可
+     * @return Unit
+     */
+    open fun setStatusBar() {}
+
+    /**
+     * 网络类型更改回调
+     * @param type Int 网络类型
+     * @return Unit
+     */
+    override fun networkTypeChange(type: NetworkTypeEnum) {}
+
+    /**
+     * 网络连接状态更改回调
+     * @param isConnected Boolean 是否已连接
+     * @return Unit
+     */
+    override fun networkConnectChange(isConnected: Boolean) {
+        toast(if (isConnected) getString(R.string.base_network_connected) else getString(R.string.base_network_disconnected))
+        Log.d("2", "networkConnectChange: "+isConnected)
+    }
+
+    override fun onDestroy() {
+        // 根据子类是否有 RegisterEventBus 注解决定是否进行注册 EventBus
+        if (mHaveRegisterEventBus) {
+            EventBusUtils.unRegister(this)
+        }
+
+        super.onDestroy()
+    }
+
+    override fun getResources(): Resources {
+        // 主要是为了解决 AndroidAutoSize 在横屏切换时导致适配失效的问题
+        // 但是 AutoSizeCompat.autoConvertDensity() 对线程做了判断 导致Coil等图片加载框架在子线程访问的时候会异常
+        // 所以在这里加了线程的判断 如果是非主线程 就取消单独的适配
+        if (Looper.myLooper() == Looper.getMainLooper()) {
+            AutoSizeCompat.autoConvertDensityOfGlobal((super.getResources()))
+        }
+        return super.getResources()
+    }
+}

+ 108 - 0
lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/mvvm/v/BaseFrameDialog.kt

@@ -0,0 +1,108 @@
+package com.quyunshuo.androidbaseframemvvm.base.mvvm.v
+
+import android.app.Activity
+import android.app.Dialog
+import android.content.Context
+import android.content.res.Resources
+import android.os.Looper
+import android.util.DisplayMetrics
+import android.view.Gravity
+import androidx.viewbinding.ViewBinding
+import com.alibaba.android.arouter.launcher.ARouter
+import com.quyunshuo.androidbaseframemvvm.base.R
+import com.quyunshuo.androidbaseframemvvm.base.mvvm.vm.BaseViewModel
+import com.quyunshuo.androidbaseframemvvm.base.utils.EventBusUtils
+import com.quyunshuo.androidbaseframemvvm.base.utils.RegisterEventBus
+import com.quyunshuo.androidbaseframemvvm.base.utils.network.AutoRegisterNetListener
+import me.jessyan.autosize.AutoSizeCompat
+
+abstract class BaseFrameDialog<VB : ViewBinding> (
+    protected var mContext: Context,
+) :
+    Dialog(mContext, R.style.base_DialogBgD),FrameView<VB> {
+
+
+    protected val mBinding: VB by lazy(mode = LazyThreadSafetyMode.NONE) { createVB() }
+
+    /**
+     * 是否有 [RegisterEventBus] 注解,避免重复调用 [Class.isAnnotation]
+     */
+    private var mHaveRegisterEventBus = false
+
+    init {
+        setContentView(mBinding.root)
+        // ARouter 依赖注入
+        ARouter.getInstance().inject(this)
+
+        // 根据子类是否有 RegisterEventBus 注解決定是否进行注册 EventBus
+        if (javaClass.isAnnotationPresent(RegisterEventBus::class.java)) {
+            mHaveRegisterEventBus = true
+            EventBusUtils.register(this)
+        }
+
+        mBinding.initView()
+        initObserve()
+        initRequestData()
+    }
+
+
+
+    override fun onStart() {
+        super.onStart()
+        val window = window
+        if (window != null) {
+            //dialog宽高(横向是屏幕宽度,纵向是布局高度)
+            val displayMetrics = DisplayMetrics()
+            window.windowManager.defaultDisplay.getMetrics(displayMetrics)
+            window.setLayout(displayMetrics.widthPixels, window.attributes.height)
+            //            dialog位置、动画
+            val animType = 0
+            when (animType) {
+                ANIM_CENTER_IN_OUT -> {
+                    window.setGravity(Gravity.CENTER)
+//                    window.setWindowAnimations(R.style.center_in_out_dialog_style)
+                }
+
+                ANIM_BOTTOM_IN_OUT -> {
+                    window.setGravity(Gravity.BOTTOM)
+//                    window.setWindowAnimations(R.style.bottom_int_out_dialog_style)
+                }
+
+                ANIM_LEFT_IN_OUT -> {
+                    window.setGravity(Gravity.LEFT)
+//                    window.setWindowAnimations(R.style.left_int_out_dialog_style)
+                }
+            }
+        }
+    }
+
+
+    fun stopTimeDown() {
+    }
+
+
+    companion object {
+        /**
+         * 中间进入进出动画(默认动画)
+         */
+        const val ANIM_CENTER_IN_OUT: Int = 0
+
+        /**
+         * 底部进入进出动画
+         */
+        const val ANIM_BOTTOM_IN_OUT: Int = 1
+
+        /**
+         * 从左边进入进出动画
+         */
+        const val ANIM_LEFT_IN_OUT: Int = 2
+    }
+
+    override fun onDetachedFromWindow() {
+        if (mHaveRegisterEventBus) {
+            EventBusUtils.unRegister(this)
+        }
+        super.onDetachedFromWindow()
+    }
+
+}

+ 119 - 0
lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/mvvm/v/BaseFrameDialogFragment.kt

@@ -0,0 +1,119 @@
+package com.quyunshuo.androidbaseframemvvm.base.mvvm.v
+
+import android.content.Context
+import android.os.Build
+import android.os.Bundle
+import android.util.Log
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.annotation.RequiresApi
+import androidx.fragment.app.DialogFragment
+import androidx.viewbinding.ViewBinding
+import com.alibaba.android.arouter.launcher.ARouter
+import com.quyunshuo.androidbaseframemvvm.base.R
+import com.quyunshuo.androidbaseframemvvm.base.mvvm.vm.BaseViewModel
+import com.quyunshuo.androidbaseframemvvm.base.utils.EventBusUtils
+import com.quyunshuo.androidbaseframemvvm.base.utils.RegisterEventBus
+
+abstract class BaseFrameDialogFragment<VB : ViewBinding, VM : BaseViewModel> : DialogFragment(),FrameView<VB> {
+    private  val TAG = "BaseFrameDialogFragment"
+    protected lateinit var mContext: Context
+
+    private var _binding: VB? = null
+    protected abstract val mViewModel: VM
+
+    protected val mBinding get() = _binding!!
+
+    /**
+     * 是否有 [RegisterEventBus] 注解,避免重复调用 [Class.isAnnotationPresent]
+     */
+    private var mHaveRegisterEventBus = false
+
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+        mContext = requireContext()
+        // ARouter 依赖注入
+        ARouter.getInstance().inject(this)
+
+        // 根据子类是否有 RegisterEventBus 注解決定是否进行注册 EventBus
+        if (javaClass.isAnnotationPresent(RegisterEventBus::class.java)) {
+            mHaveRegisterEventBus = true
+            EventBusUtils.register(this)
+        }
+        _binding?.initView()
+        initObserve()
+        initRequestData()
+    }
+
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?,
+    ): View? {
+        _binding = createVB()
+        return _binding?.root
+    }
+
+    @RequiresApi(Build.VERSION_CODES.R)
+    override fun onStart() {
+        super.onStart()
+        val dialog = dialog ?: return
+        val window = dialog.window
+        if (window != null) {
+            // 设置对话框宽高和位置
+
+            val display = window.windowManager.defaultDisplay
+            window.setLayout(display.width, ViewGroup.LayoutParams.MATCH_PARENT)
+            Log.d(TAG, "display.width: "+display.width)
+//            val animType = getAnimationType()
+//            when (animType) {
+//                ANIM_CENTER_IN_OUT -> {
+                    window.setGravity(Gravity.CENTER)
+            window.setWindowAnimations(R.style.center_in_out_dialog_style)
+
+//                    // 设置动画(如果需要)
+//                }
+//
+//                ANIM_BOTTOM_IN_OUT -> {
+//                    window.setGravity(Gravity.BOTTOM)
+//                    // 设置动画(如果需要)
+//                }
+//
+//                ANIM_LEFT_IN_OUT -> {
+//                    window.setGravity(Gravity.LEFT)
+//                    // 设置动画(如果需要)
+//                }
+//            }
+        }
+    }
+
+    // 抽象方法,用于子类指定动画类型
+    protected abstract fun getAnimationType(): Int
+
+    // 其他方法...
+
+    override fun onDestroyView() {
+        super.onDestroyView()
+        // 清理资源
+        if (mHaveRegisterEventBus) {
+            EventBusUtils.unRegister(this)
+        }
+    }
+
+    companion object {
+        // 动画类型常量
+        const val ANIM_CENTER_IN_OUT: Int = 0
+        const val ANIM_BOTTOM_IN_OUT: Int = 1
+        const val ANIM_LEFT_IN_OUT: Int = 2
+    }
+
+    override fun getTheme(): Int {
+        return R.style.base_DialogBgD
+    }
+
+
+
+}

+ 76 - 0
lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/mvvm/v/BaseFrameFragment.kt

@@ -0,0 +1,76 @@
+package com.quyunshuo.androidbaseframemvvm.base.mvvm.v
+
+import android.os.Bundle
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.Fragment
+import androidx.viewbinding.ViewBinding
+import com.alibaba.android.arouter.launcher.ARouter
+import com.quyunshuo.androidbaseframemvvm.base.mvvm.vm.BaseViewModel
+import com.quyunshuo.androidbaseframemvvm.base.utils.RegisterEventBus
+import com.quyunshuo.androidbaseframemvvm.base.utils.EventBusUtils
+
+/**
+ * Fragment基类
+ *
+ * @author Qu Yunshuo
+ * @since 8/27/20
+ */
+abstract class BaseFrameFragment<VB : ViewBinding, VM : BaseViewModel> : Fragment(),
+    FrameView<VB> {
+
+    /**
+     * 私有的 ViewBinding 此写法来自 Google Android 官方
+     */
+    private var _binding: VB? = null
+
+    protected val mBinding get() = _binding!!
+
+    protected abstract val mViewModel: VM
+
+    /**
+     * 是否有 [RegisterEventBus] 注解,避免重复调用 [Class.isAnnotation]
+     */
+    private var mHaveRegisterEventBus = false
+
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        _binding = createVB()
+
+        return _binding?.root
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+        // ARouter 依赖注入
+        ARouter.getInstance().inject(this)
+
+        // 根据子类是否有 RegisterEventBus 注解決定是否进行注册 EventBus
+        if (javaClass.isAnnotationPresent(RegisterEventBus::class.java)) {
+            mHaveRegisterEventBus = true
+            EventBusUtils.register(this)
+        }
+        _binding?.initView()
+        initObserve()
+        initRequestData()
+    }
+
+    override fun onDestroyView() {
+        // 根据子类是否有 RegisterEventBus 注解决定是否进行注册 EventBus
+        if (mHaveRegisterEventBus) {
+            EventBusUtils.unRegister(this)
+        }
+        super.onDestroyView()
+        _binding = null
+    }
+
+    override fun onDestroy() {
+
+        super.onDestroy()
+    }
+}

+ 33 - 0
lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/mvvm/v/FrameView.kt

@@ -0,0 +1,33 @@
+package com.quyunshuo.androidbaseframemvvm.base.mvvm.v
+
+import androidx.lifecycle.LiveData
+import androidx.viewbinding.ViewBinding
+
+/**
+ * View层基类抽象
+ *
+ * @author Qu Yunshuo
+ * @since 10/13/20
+ */
+interface FrameView<VB : ViewBinding> {
+
+    /**
+     * 创建 [ViewBinding] 实例
+     */
+    fun createVB(): VB
+
+    /**
+     * 初始化 View
+     */
+    fun VB.initView()
+
+    /**
+     * 订阅 [LiveData]
+     */
+    fun initObserve()
+
+    /**
+     * 用于在页面创建时进行请求接口
+     */
+    fun initRequestData()
+}

+ 60 - 0
lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/mvvm/vm/BaseViewModel.kt

@@ -0,0 +1,60 @@
+package com.quyunshuo.androidbaseframemvvm.base.mvvm.vm
+
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import com.quyunshuo.androidbaseframemvvm.base.utils.StateLayoutEnum
+import kotlin.jvm.Throws
+
+/**
+ * ViewModel 基类
+ *
+ * @author Qu Yunshuo
+ * @since 8/27/20
+ */
+abstract class BaseViewModel : ViewModel() {
+
+    /**
+     * 控制状态视图的LiveData
+     */
+    val stateViewLD = MutableLiveData<StateLayoutEnum>()
+
+    /**
+     * 更改状态视图的状态
+     *
+     * @param hide Boolean 是否进行隐藏状态视图
+     * @param loading Boolean 是否显示加载中视图
+     * @param error Boolean 是否显示错误视图
+     * @param noData Boolean 是否显示没有数据视图
+     * @return Unit
+     * @throws IllegalArgumentException 如果入参没有传入任何参数或者为true的参数 >1 时,会抛出[IllegalArgumentException]
+     */
+    @Throws(IllegalArgumentException::class)
+    protected fun changeStateView(
+        hide: Boolean = false,
+        loading: Boolean = false,
+        error: Boolean = false,
+        noData: Boolean = false
+    ) {
+        // 对参数进行校验
+        var count = 0
+        if (hide) count++
+        if (loading) count++
+        if (error) count++
+        if (noData) count++
+        when {
+            count == 0 -> throw IllegalArgumentException("必须设置一个参数为true")
+            count > 1 -> throw IllegalArgumentException("只能有一个参数为true")
+        }
+
+        // 修改状态
+        when {
+            hide -> stateViewLD.postValue(StateLayoutEnum.HIDE)
+            loading -> stateViewLD.postValue(StateLayoutEnum.LOADING)
+            error -> stateViewLD.postValue(StateLayoutEnum.ERROR)
+            noData -> stateViewLD.postValue(StateLayoutEnum.NO_DATA)
+        }
+    }
+
+
+
+}

+ 13 - 0
lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/mvvm/vm/EmptyViewModel.kt

@@ -0,0 +1,13 @@
+package com.quyunshuo.androidbaseframemvvm.base.mvvm.vm
+
+import dagger.hilt.android.lifecycle.HiltViewModel
+import javax.inject.Inject
+
+/**
+ * 空的ViewModel 主要给现阶段不需要ViewModel的界面使用
+ *
+ * @author Qu Yunshuo
+ * @since 2021/7/10 11:04 上午
+ */
+@HiltViewModel
+class EmptyViewModel @Inject constructor() : BaseViewModel()

+ 108 - 0
lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/ActivityStackManager.kt

@@ -0,0 +1,108 @@
+package com.quyunshuo.androidbaseframemvvm.base.utils
+
+import android.app.Activity
+import java.util.*
+
+/**
+ * @Author: QuYunShuo
+ * @Time: 2020/9/11
+ * @Class: ActivityStackManager
+ * @Remark: Activity 栈管理类
+ */
+object ActivityStackManager {
+
+    // 管理栈
+    val activityStack by lazy { Stack<Activity>() }
+
+    /**
+     * 添加 Activity 到管理栈
+     * @param activity Activity
+     */
+    fun addActivityToStack(activity: Activity) {
+        activityStack.push(activity)
+    }
+
+    /**
+     * 弹出栈内指定Activity 不finish
+     * @param activity Activity
+     */
+    fun popActivityToStack(activity: Activity) {
+        if (!activityStack.empty()) {
+            activityStack.forEach {
+                if (it == activity) {
+                    activityStack.remove(activity)
+                    return
+                }
+            }
+        }
+    }
+
+    /**
+     * 返回到上一个 Activity 并结束当前 Activity
+     */
+    fun backToPreviousActivity() {
+        if (!activityStack.empty()) {
+            val activity = activityStack.pop()
+            if (!activity.isFinishing) activity.finish()
+        }
+    }
+
+    /**
+     * 根据类名 判断是否是当前的 Activity
+     * @param cls Class<*> 类名
+     * @return Boolean
+     */
+    fun isCurrentActivity(cls: Class<*>): Boolean {
+        val currentActivity = getCurrentActivity()
+        return if (currentActivity != null) currentActivity.javaClass == cls else false
+    }
+
+    /**
+     * 获取当前的 Activity
+     */
+    fun getCurrentActivity(): Activity? =
+        if (!activityStack.empty()) activityStack.lastElement() else null
+
+    /**
+     * 结束一个栈内指定类名的 Activity
+     * @param cls Class<*>
+     */
+    fun finishActivity(cls: Class<*>) {
+        activityStack.forEach {
+            if (it.javaClass == cls) {
+                if (!it.isFinishing) it.finish()
+                return
+            }
+        }
+    }
+
+    /**
+     * 弹出其他 Activity
+     */
+    fun popOtherActivity() {
+        val activityList = activityStack.toList()
+        getCurrentActivity()?.run {
+            activityList.forEach { activity ->
+                if (this != activity) {
+                    activityStack.remove(activity)
+                    activity.finish()
+                }
+            }
+        }
+    }
+
+    /**
+     * 返回到指定 Activity
+     */
+    fun backToSpecifyActivity(activityClass: Class<*>) {
+        val activityList = activityStack.toList().reversed()
+        activityList.forEach {
+            if (it.javaClass == activityClass) {
+                return
+            } else {
+                activityStack.pop()
+                it.finish()
+            }
+        }
+    }
+}

+ 47 - 0
lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/AndroidBugFixUtils.kt

@@ -0,0 +1,47 @@
+package com.quyunshuo.androidbaseframemvvm.base.utils
+
+import android.app.Activity
+import android.content.Context
+import android.view.View
+import android.view.inputmethod.InputMethodManager
+import com.quyunshuo.androidbaseframemvvm.base.BaseApplication
+import java.lang.reflect.Field
+
+/**
+ * 解决 Android 自身的 Bug
+ *
+ * @author Qu Yunshuo
+ * @since 2020/10/22
+ */
+class AndroidBugFixUtils {
+
+    /**
+     * 解决 InputMethodManager 造成的内存泄露
+     *
+     * 使用方式:
+     * ```
+     * override fun onDestroy() {
+     *     AndroidBugFixUtils().fixSoftInputLeaks(this)
+     *     super.onDestroy()
+     * }
+     * ```
+     */
+    fun fixSoftInputLeaks(activity: Activity) {
+        val imm =
+            BaseApplication.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
+        val leakViews = arrayOf("mLastSrvView", "mCurRootView", "mServedView", "mNextServedView")
+        for (leakView in leakViews) {
+            try {
+                val leakViewField: Field =
+                    InputMethodManager::class.java.getDeclaredField(leakView) ?: continue
+                if (!leakViewField.isAccessible) leakViewField.isAccessible = true
+                val view: Any? = leakViewField.get(imm)
+                if (view !is View) continue
+                if (view.rootView == activity.window.decorView.rootView) {
+                    leakViewField.set(imm, null)
+                }
+            } catch (t: Throwable) {
+            }
+        }
+    }
+}

+ 45 - 0
lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/AppUtils.kt

@@ -0,0 +1,45 @@
+package com.quyunshuo.androidbaseframemvvm.base.utils
+
+import android.content.pm.PackageInfo
+import android.os.Build
+import com.quyunshuo.androidbaseframemvvm.base.BaseApplication
+
+/**
+ * App 相关工具类
+ *
+ * @author Qu Yunshuo
+ * @sine 2023/2/13 23:15
+ */
+class AppUtils {
+
+    /**
+     * 获取当前 App 版本号
+     *
+     * @return Long
+     */
+    fun getAppVersionCode(): Long {
+        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+            getAppPackageInfo().longVersionCode
+        } else {
+            getAppPackageInfo().versionCode.toLong()
+        }
+    }
+
+    /**
+     * 获取当前 App 版本名
+     *
+     * @return String
+     */
+    fun getAppVersionName(): String = getAppPackageInfo().versionName
+
+    /**
+     * 获取当前 App 的 [PackageInfo]
+     *
+     * @return PackageInfo
+     */
+    fun getAppPackageInfo(): PackageInfo {
+        return BaseApplication.context
+            .packageManager
+            .getPackageInfo(BaseApplication.context.packageName, 0)
+    }
+}

+ 674 - 0
lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/BarUtils.java

@@ -0,0 +1,674 @@
+package com.quyunshuo.androidbaseframemvvm.base.utils;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.graphics.Point;
+import android.os.Build;
+import android.util.TypedValue;
+import android.view.Display;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.ViewGroup.MarginLayoutParams;
+import android.view.Window;
+import android.view.WindowManager;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.annotation.RequiresPermission;
+
+import java.lang.reflect.Method;
+
+import static android.Manifest.permission.EXPAND_STATUS_BAR;
+
+import com.quyunshuo.androidbaseframemvvm.base.BaseApplication;
+
+/**
+ * 各种栏的工具类
+ * <p>
+ * getStatusBarHeight                   : 获取状态栏高度(px)
+ * setStatusBarVisibility               : 设置状态栏是否可见
+ * isStatusBarVisible                   : 判断状态栏是否可见
+ * setStatusBarLightMode                : 设置状态栏是否为浅色模式
+ * isStatusBarLightMode                 : 判断状态栏是否为浅色模式
+ * addMarginTopEqualStatusBarHeight     : 为 view 增加 MarginTop 为状态栏高度
+ * subtractMarginTopEqualStatusBarHeight: 为 view 减少 MarginTop 为状态栏高度
+ * setStatusBarColor                    : 设置状态栏颜色
+ * setStatusBarColor4Drawer             : 为 DrawerLayout 设置状态栏颜色
+ * transparentStatusBar                 : 透明状态栏
+ * getActionBarHeight                   : 获取 ActionBar 高度
+ * setNotificationBarVisibility         : 设置通知栏是否可见
+ * getNavBarHeight                      : 获取导航栏高度
+ * setNavBarVisibility                  : 设置导航栏是否可见
+ * isNavBarVisible                      : 判断导航栏是否可见
+ * setNavBarColor                       : 设置导航栏颜色
+ * getNavBarColor                       : 获取导航栏颜色
+ * isSupportNavBar                      : 判断是否支持导航栏
+ * setNavBarLightMode                   : 设置导航栏是否为浅色模式
+ * isNavBarLightMode                    : 判断导航栏是否为浅色模式
+ *
+ * @author Qu Yunshuo
+ * @since 2021/7/15 10:42 上午
+ */
+public final class BarUtils {
+
+    ///////////////////////////////////////////////////////////////////////////
+    // status bar
+    ///////////////////////////////////////////////////////////////////////////
+
+    private static final String TAG_STATUS_BAR = "TAG_STATUS_BAR";
+    private static final String TAG_OFFSET = "TAG_OFFSET";
+    private static final int KEY_OFFSET = -123;
+
+    private BarUtils() {
+        throw new UnsupportedOperationException("u can't instantiate me...");
+    }
+
+    /**
+     * Return the status bar's height.
+     *
+     * @return the status bar's height
+     */
+    public static int getStatusBarHeight() {
+        Resources resources = BaseApplication.context.getResources();
+        int resourceId = resources.getIdentifier("status_bar_height", "dimen", "android");
+        return resources.getDimensionPixelSize(resourceId);
+    }
+
+    /**
+     * Set the status bar's visibility.
+     *
+     * @param activity  The activity.
+     * @param isVisible True to set status bar visible, false otherwise.
+     */
+    public static void setStatusBarVisibility(@NonNull final Activity activity,
+                                              final boolean isVisible) {
+        setStatusBarVisibility(activity.getWindow(), isVisible);
+    }
+
+    /**
+     * Set the status bar's visibility.
+     *
+     * @param window    The window.
+     * @param isVisible True to set status bar visible, false otherwise.
+     */
+    public static void setStatusBarVisibility(@NonNull final Window window,
+                                              final boolean isVisible) {
+        if (isVisible) {
+            window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+            showStatusBarView(window);
+            addMarginTopEqualStatusBarHeight(window);
+        } else {
+            window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+            hideStatusBarView(window);
+            subtractMarginTopEqualStatusBarHeight(window);
+        }
+    }
+
+    /**
+     * Return whether the status bar is visible.
+     *
+     * @param activity The activity.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isStatusBarVisible(@NonNull final Activity activity) {
+        int flags = activity.getWindow().getAttributes().flags;
+        return (flags & WindowManager.LayoutParams.FLAG_FULLSCREEN) == 0;
+    }
+
+    /**
+     * Set the status bar's light mode.
+     *
+     * @param activity    The activity.
+     * @param isLightMode True to set status bar light mode, false otherwise.
+     */
+    public static void setStatusBarLightMode(@NonNull final Activity activity,
+                                             final boolean isLightMode) {
+        setStatusBarLightMode(activity.getWindow(), isLightMode);
+    }
+
+    /**
+     * Set the status bar's light mode.
+     *
+     * @param window      The window.
+     * @param isLightMode True to set status bar light mode, false otherwise.
+     */
+    public static void setStatusBarLightMode(@NonNull final Window window,
+                                             final boolean isLightMode) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            View decorView = window.getDecorView();
+            int vis = decorView.getSystemUiVisibility();
+            if (isLightMode) {
+                vis |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
+            } else {
+                vis &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
+            }
+            decorView.setSystemUiVisibility(vis);
+        }
+    }
+
+    /**
+     * Is the status bar light mode.
+     *
+     * @param activity The activity.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isStatusBarLightMode(@NonNull final Activity activity) {
+        return isStatusBarLightMode(activity.getWindow());
+    }
+
+    /**
+     * Is the status bar light mode.
+     *
+     * @param window The window.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isStatusBarLightMode(@NonNull final Window window) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            View decorView = window.getDecorView();
+            int vis = decorView.getSystemUiVisibility();
+            return (vis & View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) != 0;
+        }
+        return false;
+    }
+
+    /**
+     * Add the top margin size equals status bar's height for view.
+     *
+     * @param view The view.
+     */
+    public static void addMarginTopEqualStatusBarHeight(@NonNull View view) {
+
+        view.setTag(TAG_OFFSET);
+        Object haveSetOffset = view.getTag(KEY_OFFSET);
+        if (haveSetOffset != null && (Boolean) haveSetOffset) return;
+        MarginLayoutParams layoutParams = (MarginLayoutParams) view.getLayoutParams();
+        layoutParams.setMargins(layoutParams.leftMargin,
+                layoutParams.topMargin + getStatusBarHeight(),
+                layoutParams.rightMargin,
+                layoutParams.bottomMargin);
+        view.setTag(KEY_OFFSET, true);
+    }
+
+    /**
+     * Subtract the top margin size equals status bar's height for view.
+     *
+     * @param view The view.
+     */
+    public static void subtractMarginTopEqualStatusBarHeight(@NonNull View view) {
+
+        Object haveSetOffset = view.getTag(KEY_OFFSET);
+        if (haveSetOffset == null || !(Boolean) haveSetOffset) return;
+        MarginLayoutParams layoutParams = (MarginLayoutParams) view.getLayoutParams();
+        layoutParams.setMargins(layoutParams.leftMargin,
+                layoutParams.topMargin - getStatusBarHeight(),
+                layoutParams.rightMargin,
+                layoutParams.bottomMargin);
+        view.setTag(KEY_OFFSET, false);
+    }
+
+    private static void addMarginTopEqualStatusBarHeight(@NonNull final Window window) {
+        View withTag = window.getDecorView().findViewWithTag(TAG_OFFSET);
+        if (withTag == null) return;
+        addMarginTopEqualStatusBarHeight(withTag);
+    }
+
+    private static void subtractMarginTopEqualStatusBarHeight(@NonNull final Window window) {
+        View withTag = window.getDecorView().findViewWithTag(TAG_OFFSET);
+        if (withTag == null) return;
+        subtractMarginTopEqualStatusBarHeight(withTag);
+    }
+
+    /**
+     * Set the status bar's color.
+     *
+     * @param activity The activity.
+     * @param color    The status bar's color.
+     */
+    public static View setStatusBarColor(@NonNull final Activity activity,
+                                         @ColorInt final int color) {
+        return setStatusBarColor(activity, color, false);
+    }
+
+    /**
+     * Set the status bar's color.
+     *
+     * @param activity The activity.
+     * @param color    The status bar's color.
+     * @param isDecor  True to add fake status bar in DecorView,
+     *                 false to add fake status bar in ContentView.
+     */
+    public static View setStatusBarColor(@NonNull final Activity activity,
+                                         @ColorInt final int color,
+                                         final boolean isDecor) {
+        transparentStatusBar(activity);
+        return applyStatusBarColor(activity, color, isDecor);
+    }
+
+
+    /**
+     * Set the status bar's color.
+     *
+     * @param window The window.
+     * @param color  The status bar's color.
+     */
+    public static View setStatusBarColor(@NonNull final Window window,
+                                         @ColorInt final int color) {
+        return setStatusBarColor(window, color, false);
+    }
+
+    /**
+     * Set the status bar's color.
+     *
+     * @param window  The window.
+     * @param color   The status bar's color.
+     * @param isDecor True to add fake status bar in DecorView,
+     *                false to add fake status bar in ContentView.
+     */
+    public static View setStatusBarColor(@NonNull final Window window,
+                                         @ColorInt final int color,
+                                         final boolean isDecor) {
+        transparentStatusBar(window);
+        return applyStatusBarColor(window, color, isDecor);
+    }
+
+    /**
+     * Set the status bar's color.
+     *
+     * @param fakeStatusBar The fake status bar view.
+     * @param color         The status bar's color.
+     */
+    public static void setStatusBarColor(Activity activity, @NonNull final View fakeStatusBar,
+                                         @ColorInt final int color) {
+        if (activity == null) return;
+        transparentStatusBar(activity);
+        fakeStatusBar.setVisibility(View.VISIBLE);
+        ViewGroup.LayoutParams layoutParams = fakeStatusBar.getLayoutParams();
+        layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
+        layoutParams.height = getStatusBarHeight();
+        fakeStatusBar.setBackgroundColor(color);
+    }
+
+    /**
+     * Set the custom status bar.
+     *
+     * @param fakeStatusBar The fake status bar view.
+     */
+    public static void setStatusBarCustom(Activity activity, @NonNull final View fakeStatusBar) {
+        if (activity == null) return;
+        transparentStatusBar(activity);
+        fakeStatusBar.setVisibility(View.VISIBLE);
+        ViewGroup.LayoutParams layoutParams = fakeStatusBar.getLayoutParams();
+        if (layoutParams == null) {
+            layoutParams = new ViewGroup.LayoutParams(
+                    ViewGroup.LayoutParams.MATCH_PARENT,
+                    getStatusBarHeight()
+            );
+            fakeStatusBar.setLayoutParams(layoutParams);
+        } else {
+            layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
+            layoutParams.height = getStatusBarHeight();
+        }
+    }
+
+    private static View applyStatusBarColor(@NonNull final Activity activity,
+                                            final int color,
+                                            boolean isDecor) {
+        return applyStatusBarColor(activity.getWindow(), color, isDecor);
+    }
+
+    private static View applyStatusBarColor(@NonNull final Window window,
+                                            final int color,
+                                            boolean isDecor) {
+        ViewGroup parent = isDecor ?
+                (ViewGroup) window.getDecorView() :
+                (ViewGroup) window.findViewById(android.R.id.content);
+        View fakeStatusBarView = parent.findViewWithTag(TAG_STATUS_BAR);
+        if (fakeStatusBarView != null) {
+            if (fakeStatusBarView.getVisibility() == View.GONE) {
+                fakeStatusBarView.setVisibility(View.VISIBLE);
+            }
+            fakeStatusBarView.setBackgroundColor(color);
+        } else {
+            fakeStatusBarView = createStatusBarView(window.getContext(), color);
+            parent.addView(fakeStatusBarView);
+        }
+        return fakeStatusBarView;
+    }
+
+    private static void hideStatusBarView(@NonNull final Activity activity) {
+        hideStatusBarView(activity.getWindow());
+    }
+
+    private static void hideStatusBarView(@NonNull final Window window) {
+        ViewGroup decorView = (ViewGroup) window.getDecorView();
+        View fakeStatusBarView = decorView.findViewWithTag(TAG_STATUS_BAR);
+        if (fakeStatusBarView == null) return;
+        fakeStatusBarView.setVisibility(View.GONE);
+    }
+
+    private static void showStatusBarView(@NonNull final Window window) {
+        ViewGroup decorView = (ViewGroup) window.getDecorView();
+        View fakeStatusBarView = decorView.findViewWithTag(TAG_STATUS_BAR);
+        if (fakeStatusBarView == null) return;
+        fakeStatusBarView.setVisibility(View.VISIBLE);
+    }
+
+    private static View createStatusBarView(@NonNull final Context context,
+                                            final int color) {
+        View statusBarView = new View(context);
+        statusBarView.setLayoutParams(new ViewGroup.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT, getStatusBarHeight()));
+        statusBarView.setBackgroundColor(color);
+        statusBarView.setTag(TAG_STATUS_BAR);
+        return statusBarView;
+    }
+
+    public static void transparentStatusBar(@NonNull final Activity activity) {
+        transparentStatusBar(activity.getWindow());
+    }
+
+    public static void transparentStatusBar(@NonNull final Window window) {
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+            window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+            window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
+            int option = View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
+            int vis = window.getDecorView().getSystemUiVisibility();
+            window.getDecorView().setSystemUiVisibility(option | vis);
+            window.setStatusBarColor(Color.TRANSPARENT);
+        } else {
+            window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+        }
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // action bar
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Return the action bar's height.
+     *
+     * @return the action bar's height
+     */
+    public static int getActionBarHeight() {
+        TypedValue tv = new TypedValue();
+        if (BaseApplication.context.getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true)) {
+            return TypedValue.complexToDimensionPixelSize(
+                    tv.data, BaseApplication.context.getResources().getDisplayMetrics()
+            );
+        }
+        return 0;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // notification bar
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Set the notification bar's visibility.
+     * <p>Must hold {@code <uses-permission android:name="android.permission.EXPAND_STATUS_BAR" />}</p>
+     *
+     * @param isVisible True to set notification bar visible, false otherwise.
+     */
+    @RequiresPermission(EXPAND_STATUS_BAR)
+    public static void setNotificationBarVisibility(final boolean isVisible) {
+        String methodName;
+        if (isVisible) {
+            methodName = (Build.VERSION.SDK_INT <= 16) ? "expand" : "expandNotificationsPanel";
+        } else {
+            methodName = (Build.VERSION.SDK_INT <= 16) ? "collapse" : "collapsePanels";
+        }
+        invokePanels(methodName);
+    }
+
+    private static void invokePanels(final String methodName) {
+        try {
+            @SuppressLint("WrongConstant")
+            Object service = BaseApplication.context.getSystemService("statusbar");
+            @SuppressLint("PrivateApi")
+            Class<?> statusBarManager = Class.forName("android.app.StatusBarManager");
+            Method expand = statusBarManager.getMethod(methodName);
+            expand.invoke(service);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // navigation bar
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Return the navigation bar's height.
+     *
+     * @return the navigation bar's height
+     */
+    public static int getNavBarHeight() {
+        Resources res = BaseApplication.context.getResources();
+        int resourceId = res.getIdentifier("navigation_bar_height", "dimen", "android");
+        if (resourceId != 0) {
+            return res.getDimensionPixelSize(resourceId);
+        } else {
+            return 0;
+        }
+    }
+
+    /**
+     * Set the navigation bar's visibility.
+     *
+     * @param activity  The activity.
+     * @param isVisible True to set navigation bar visible, false otherwise.
+     */
+    public static void setNavBarVisibility(@NonNull final Activity activity, boolean isVisible) {
+
+        setNavBarVisibility(activity.getWindow(), isVisible);
+
+    }
+
+    /**
+     * Set the navigation bar's visibility.
+     *
+     * @param window    The window.
+     * @param isVisible True to set navigation bar visible, false otherwise.
+     */
+    public static void setNavBarVisibility(@NonNull final Window window, boolean isVisible) {
+
+        final ViewGroup decorView = (ViewGroup) window.getDecorView();
+        for (int i = 0, count = decorView.getChildCount(); i < count; i++) {
+            final View child = decorView.getChildAt(i);
+            final int id = child.getId();
+            if (id != View.NO_ID) {
+                String resourceEntryName = getResNameById(id);
+                if ("navigationBarBackground".equals(resourceEntryName)) {
+                    child.setVisibility(isVisible ? View.VISIBLE : View.INVISIBLE);
+                }
+            }
+        }
+        final int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+                | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
+        if (isVisible) {
+            decorView.setSystemUiVisibility(decorView.getSystemUiVisibility() & ~uiOptions);
+        } else {
+            decorView.setSystemUiVisibility(decorView.getSystemUiVisibility() | uiOptions);
+        }
+    }
+
+    /**
+     * Return whether the navigation bar visible.
+     * <p>Call it in onWindowFocusChanged will get right result.</p>
+     *
+     * @param activity The activity.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isNavBarVisible(@NonNull final Activity activity) {
+        return isNavBarVisible(activity.getWindow());
+    }
+
+    /**
+     * Return whether the navigation bar visible.
+     * <p>Call it in onWindowFocusChanged will get right result.</p>
+     *
+     * @param window The window.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isNavBarVisible(@NonNull final Window window) {
+        boolean isVisible = false;
+        ViewGroup decorView = (ViewGroup) window.getDecorView();
+        for (int i = 0, count = decorView.getChildCount(); i < count; i++) {
+            final View child = decorView.getChildAt(i);
+            final int id = child.getId();
+            if (id != View.NO_ID) {
+                String resourceEntryName = getResNameById(id);
+                if ("navigationBarBackground".equals(resourceEntryName)
+                        && child.getVisibility() == View.VISIBLE) {
+                    isVisible = true;
+                    break;
+                }
+            }
+        }
+        if (isVisible) {
+            int visibility = decorView.getSystemUiVisibility();
+            isVisible = (visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0;
+        }
+
+        return isVisible;
+    }
+
+    private static String getResNameById(int id) {
+        try {
+            return BaseApplication.context.getResources().getResourceEntryName(id);
+        } catch (Exception ignore) {
+            return "";
+        }
+    }
+
+    /**
+     * Set the navigation bar's color.
+     *
+     * @param activity The activity.
+     * @param color    The navigation bar's color.
+     */
+    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
+    public static void setNavBarColor(@NonNull final Activity activity, @ColorInt final int color) {
+        setNavBarColor(activity.getWindow(), color);
+    }
+
+    /**
+     * Set the navigation bar's color.
+     *
+     * @param window The window.
+     * @param color  The navigation bar's color.
+     */
+    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
+    public static void setNavBarColor(@NonNull final Window window, @ColorInt final int color) {
+        window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
+        window.setNavigationBarColor(color);
+    }
+
+    /**
+     * Return the color of navigation bar.
+     *
+     * @param activity The activity.
+     * @return the color of navigation bar
+     */
+    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
+    public static int getNavBarColor(@NonNull final Activity activity) {
+        return getNavBarColor(activity.getWindow());
+    }
+
+    /**
+     * Return the color of navigation bar.
+     *
+     * @param window The window.
+     * @return the color of navigation bar
+     */
+    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
+    public static int getNavBarColor(@NonNull final Window window) {
+        return window.getNavigationBarColor();
+    }
+
+    /**
+     * Return whether the navigation bar visible.
+     *
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isSupportNavBar() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+            WindowManager wm = (WindowManager) BaseApplication.context.getSystemService(Context.WINDOW_SERVICE);
+            if (wm == null) return false;
+            Display display = wm.getDefaultDisplay();
+            Point size = new Point();
+            Point realSize = new Point();
+            display.getSize(size);
+            display.getRealSize(realSize);
+            return realSize.y != size.y || realSize.x != size.x;
+        }
+        boolean menu = ViewConfiguration.get(BaseApplication.context).hasPermanentMenuKey();
+        boolean back = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK);
+        return !menu && !back;
+    }
+
+    /**
+     * Set the nav bar's light mode.
+     *
+     * @param activity    The activity.
+     * @param isLightMode True to set nav bar light mode, false otherwise.
+     */
+    public static void setNavBarLightMode(@NonNull final Activity activity,
+                                          final boolean isLightMode) {
+        setNavBarLightMode(activity.getWindow(), isLightMode);
+    }
+
+    /**
+     * Set the nav bar's light mode.
+     *
+     * @param window      The window.
+     * @param isLightMode True to set nav bar light mode, false otherwise.
+     */
+    public static void setNavBarLightMode(@NonNull final Window window,
+                                          final boolean isLightMode) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            View decorView = window.getDecorView();
+            int vis = decorView.getSystemUiVisibility();
+            if (isLightMode) {
+                vis |= View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
+            } else {
+                vis &= ~View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
+            }
+            decorView.setSystemUiVisibility(vis);
+        }
+    }
+
+    /**
+     * Is the nav bar light mode.
+     *
+     * @param activity The activity.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isNavBarLightMode(@NonNull final Activity activity) {
+        return isNavBarLightMode(activity.getWindow());
+    }
+
+    /**
+     * Is the nav bar light mode.
+     *
+     * @param window The window.
+     * @return {@code true}: yes<br>{@code false}: no
+     */
+    public static boolean isNavBarLightMode(@NonNull final Window window) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            View decorView = window.getDecorView();
+            int vis = decorView.getSystemUiVisibility();
+            return (vis & View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR) != 0;
+        }
+        return false;
+    }
+}

+ 28 - 0
lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/ClipboardUtils.kt

@@ -0,0 +1,28 @@
+package com.quyunshuo.androidbaseframemvvm.base.utils
+
+import android.content.ClipData
+import android.content.ClipboardManager
+import android.content.Context
+import com.quyunshuo.androidbaseframemvvm.base.BaseApplication
+
+/**
+ * 剪切板工具类
+ *
+ * @author Qu Yunshuo
+ * @since 2023/5/31 10:27
+ */
+object ClipboardUtils {
+
+    /**
+     * 复制内容到剪切板
+     *
+     * @param text String 内容
+     * @param label String 标签,用于区分内容
+     */
+    fun copyToClipboard(text: String, label: String = "") {
+        val clipboard =
+            BaseApplication.context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
+        val clip = ClipData.newPlainText(label, text)
+        clipboard.setPrimaryClip(clip)
+    }
+}

+ 26 - 0
lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/CoilGIFImageLoader.kt

@@ -0,0 +1,26 @@
+package com.quyunshuo.androidbaseframemvvm.base.utils
+
+import android.os.Build.VERSION.SDK_INT
+import coil.ImageLoader
+import coil.decode.GifDecoder
+import coil.decode.ImageDecoderDecoder
+import com.quyunshuo.androidbaseframemvvm.base.BaseApplication
+
+/**
+ * 用于加载 Gif 的 Coil ImageLoader
+ *
+ * @author Qu Yunshuo
+ * @since 2021/9/6 4:26 下午
+ */
+object CoilGIFImageLoader {
+
+    val imageLoader = ImageLoader.Builder(BaseApplication.context)
+        .componentRegistry {
+            if (SDK_INT >= 28) {
+                add(ImageDecoderDecoder(BaseApplication.context))
+            } else {
+                add(GifDecoder())
+            }
+        }
+        .build()
+}

+ 186 - 0
lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/DateUtils.kt

@@ -0,0 +1,186 @@
+package com.quyunshuo.androidbaseframemvvm.base.utils
+
+import java.text.ParseException
+import java.text.SimpleDateFormat
+import java.util.*
+
+/**
+ * 时间工具类
+ * ________________________________________________________________________________________
+ * |字母	|日期或时间元素	       | 表示	           | 示例                                  |
+ * |:--:|:--------------------:|:-----------------:|:------------------------------------:|
+ * |G	|Era 标志符	           | Text	           | AD                                   |
+ * |y	|年	                   | Year              | 1996; 96                             |
+ * |M	|年中的月份	           | Month	           | July; Jul; 07                        |
+ * |w	|年中的周数	           | Number            | 27                                   |
+ * |W	|月份中的周数	           | Number            | 2                                    |
+ * |D	|年中的天数	           | Number            | 189                                  |
+ * |d	|月份中的天数	           | Number            | 10                                   |
+ * |F	|月份中的星期	           | Number            | 2                                    |
+ * |E	|星期中的天数	           | Text	           | Tuesday; Tue                         |
+ * |a	|Am/pm 标记	           | Text	           | PM                                   |
+ * |H	|一天中的小时数(0-23)    |  Number            | 0                                    |
+ * |k	|一天中的小时数(1-24)   |  Number            | 24                                   |
+ * |K	|am/pm 中的小时数(0-11) |  Number            | 0                                    |
+ * |h	|am/pm 中的小时数(1-12) |  Number            | 12                                   |
+ * |m	|小时中的分钟数	       | Number            | 30                                   |
+ * |s	|分钟中的秒数	           | Number            | 55                                   |
+ * |S	|毫秒数	               | Number            | 978                                  |
+ * |z	|时区	               | General time zone | Pacific Standard Time; PST; GMT-08:00|
+ * |Z	|时区	               | RFC 822 time zone | -0800                                |
+ *  ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄
+ * @author Qu Yunshuo
+ * @since 2020/9/8
+ */
+object DateUtils {
+
+    /**
+     * 获取时间格式化String
+     * @param timestamp 时间戳
+     * @param dateFormat 日期格式
+     * @return 格式化后的字符串
+     */
+    fun getDateFormatString(timestamp: Long, dateFormat: String): String =
+        SimpleDateFormat(dateFormat, Locale.CHINESE).format(Date(timestamp))
+
+    /**
+     * 将固定格式[dateFormat]的时间字符串[dateString]转换为时间值
+     */
+    fun getDateStringToDate(dateString: String, dateFormat: String): Long? {
+        val simpleDateFormat = SimpleDateFormat(dateFormat, Locale.CHINESE)
+        var date: Date? = null
+        try {
+            date = simpleDateFormat.parse(dateString)
+        } catch (e: ParseException) {
+            e.printStackTrace()
+        }
+        return date?.time
+    }
+
+    /**
+     * 将计时毫秒值[millisecond]转换为时分秒
+     */
+    fun getGapTime(millisecond: Long): String {
+        val hours = millisecond / (1000 * 60 * 60)
+        val minutes = (millisecond - hours * (1000 * 60 * 60)) / (1000 * 60)
+        val second = (millisecond - hours * (1000 * 60 * 60) - minutes * (1000 * 60)) / 1000
+        var diffTime: String
+        diffTime = if (minutes < 10) {
+            "$hours:0$minutes"
+        } else {
+            "$hours:$minutes"
+        }
+        diffTime = if (second < 10) {
+            "$diffTime:0$second"
+        } else {
+            "$diffTime:$second"
+        }
+        return diffTime
+    }
+
+    /**
+     * 获取以当前日期为基准的某一时间段的日期
+     * @param isFuture Boolean 真为未来时间 假为以前的时间
+     * @param interval Int 间隔时间 以当前时间为基准 距今天前n天或后n天开始 就是n 0是当前日期
+     * @param size String 时间区间长度  比如获取五天的时间 就是5 当前日期也算一天
+     * @return List<String> 日期集合 顺序为日期的新旧程度
+     * @throws RuntimeException 如果[interval]小于0或者[size]小于1会抛出[RuntimeException]
+     *
+     * 示例:获取后天开始 为期七天的时间就是 getExcerptDate(true, 2, 7)
+     *      获取昨天开始再往前7天的时间 getExcerptDate(false, 1, 7)
+     */
+    fun getExcerptDate(
+        isFuture: Boolean,
+        interval: Int,
+        size: Int,
+        dateFormat: String
+    ): List<String> {
+        if (interval < 0) throw RuntimeException("\"interval\" it can't be less than 0")
+        if (size < 1) throw RuntimeException("\"size\" it can't be less than 1")
+        val simpleDateFormat = SimpleDateFormat(dateFormat, Locale.CHINESE)
+        val calendar = Calendar.getInstance()
+        val currentDayOfYear = calendar.get(Calendar.DAY_OF_YEAR)
+        val currentYear = calendar.get(Calendar.YEAR)
+        val dateList = mutableListOf<String>()
+        if (isFuture) {
+            (interval until interval + size).forEach {
+                val timestamp = getSomedayDate(it, calendar, currentDayOfYear, currentYear)
+                dateList.add(simpleDateFormat.format(timestamp))
+            }
+        } else {
+            (-interval downTo -interval - size + 1).forEach {
+                val timestamp = getSomedayDate(it, calendar, currentDayOfYear, currentYear)
+                dateList.add(simpleDateFormat.format(timestamp))
+            }
+        }
+        return dateList
+    }
+
+    /**
+     * 获取距离今天的某一天的时间戳
+     * @param numberOfDaysBetween Int 间隔今天的天数 正数为未来时间 负数为以前的时间
+     * @param calendar Calendar Calendar对象 使用依赖注入方式 提高对象的复用性
+     * @param currentDayOfYear Int 当前时间在当年的天 使用Calendar获取
+     * @param currentYear Int 当前年 使用Calendar获取
+     * @return Long 时间戳
+     */
+    fun getSomedayDate(
+        numberOfDaysBetween: Int,
+        calendar: Calendar,
+        currentDayOfYear: Int,
+        currentYear: Int
+    ): Long {
+        calendar.set(Calendar.DAY_OF_YEAR, currentDayOfYear)
+        calendar.set(Calendar.YEAR, currentYear)
+        calendar.set(
+            Calendar.DAY_OF_YEAR,
+            calendar.get(Calendar.DAY_OF_YEAR) + numberOfDaysBetween
+        )
+        return calendar.time.time
+    }
+
+    /**
+     * String 转化 Calendar
+     * @param string String
+     * @param format String
+     */
+    fun stringToCalendar(string: String, format: String): Calendar? {
+        val sdf = SimpleDateFormat(format, Locale.CHINESE)
+        var calendar: Calendar
+        try {
+            val date: Date = sdf.parse(string) ?: return null
+            calendar = Calendar.getInstance()
+            calendar.time = date
+        } catch (e: ParseException) {
+            e.printStackTrace()
+            calendar = Calendar.getInstance()
+        }
+        return calendar
+    }
+
+    /**
+     * String 转化Date
+     * @param str String
+     * @param format String
+     * @return Date
+     */
+    fun strToDate(str: String, format: String): Date? {
+        val sdf = SimpleDateFormat(format, Locale.CHINESE)
+        return try {
+            sdf.parse(str)
+        } catch (e: ParseException) {
+            e.printStackTrace()
+            null
+        }
+    }
+
+    /**
+     * 判断两个时间是否是同一天
+     * @param cal1 Calendar
+     * @param cal2 Calendar
+     * @return Boolean
+     */
+    fun isSameDay(cal1: Calendar, cal2: Calendar): Boolean {
+        return cal1[0] == cal2[0] && cal1[1] == cal2[1] && cal1[6] == cal2[6]
+    }
+}

+ 54 - 0
lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/EventBusUtils.kt

@@ -0,0 +1,54 @@
+package com.quyunshuo.androidbaseframemvvm.base.utils
+
+import org.greenrobot.eventbus.EventBus
+
+/**
+ * @Author: QuYunShuo
+ * @Time: 2020/8/29
+ * @Class: EventBusUtil
+ * @Remark: EventBus工具类
+ */
+object EventBusUtils {
+
+    /**
+     * 订阅
+     * @param subscriber 订阅者
+     */
+    fun register(subscriber: Any) = EventBus.getDefault().register(subscriber)
+
+    /**
+     * 解除注册
+     * @param subscriber 订阅者
+     */
+    fun unRegister(subscriber: Any) = EventBus.getDefault().unregister(subscriber)
+
+    /**
+     * 发送普通事件
+     * @param event 事件
+     */
+    fun postEvent(event: Any) = EventBus.getDefault().post(event)
+
+    /**
+     * 发送粘性事件
+     * @param stickyEvent 粘性事件
+     */
+    fun postStickyEvent(stickyEvent: Any) = EventBus.getDefault().postSticky(stickyEvent)
+
+    /**
+     * 手动获取粘性事件
+     * @param stickyEventType 粘性事件
+     * @param <T>             事件泛型
+     * @return 返回给定事件类型的最近粘性事件
+     */
+    fun <T> getStickyEvent(stickyEventType: Class<T>): T =
+        EventBus.getDefault().getStickyEvent(stickyEventType)
+
+    /**
+     * 手动删除粘性事件
+     * @param stickyEventType 粘性事件
+     * @param <T>             事件泛型
+     * @return 返回给定事件类型的最近粘性事件
+     */
+    fun <T> removeStickyEvent(stickyEventType: Class<T>): T =
+        EventBus.getDefault().removeStickyEvent(stickyEventType)
+}

+ 84 - 0
lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/ForegroundBackgroundHelper.kt

@@ -0,0 +1,84 @@
+package com.quyunshuo.androidbaseframemvvm.base.utils
+
+/**
+ * 前后台切换帮助类,该类实现了前后台监听以及支持注册变化响应监听
+ *
+ * @see ForegroundBackgroundObserver
+ * @see ForegroundBackgroundSubject
+ *
+ * @author Qu Yunshuo
+ * @since 2023/5/31 14:22
+ */
+object ForegroundBackgroundHelper : ForegroundBackgroundSubject {
+
+    private var mActivityStartCount = 0
+
+    private var mIsForeground = false
+
+    private val mObservers = mutableListOf<ForegroundBackgroundObserver>()
+
+    fun onActivityStarted() {
+        mActivityStartCount++
+        if (mActivityStartCount == 1) {
+            mIsForeground = true
+            notifyObservers()
+        }
+    }
+
+    fun onActivityStopped() {
+        mActivityStartCount--
+        if (mActivityStartCount == 0) {
+            mIsForeground = false
+            notifyObservers()
+        }
+    }
+
+    /**
+     * 通知所有订阅者状态变化
+     */
+    override fun notifyObservers() {
+        mObservers.forEach {
+            it.foregroundBackgroundNotify(mIsForeground)
+        }
+    }
+
+    /**
+     * 添加订阅者
+     *
+     * @param observer ForegroundBackgroundObserver
+     */
+    override fun addObserve(observer: ForegroundBackgroundObserver) {
+        mObservers.add(observer)
+    }
+
+    /**
+     * 移除订阅者
+     *
+     * @param observer ForegroundBackgroundObserver
+     */
+    override fun removeObserver(observer: ForegroundBackgroundObserver) {
+        mObservers.remove(observer)
+    }
+}
+
+/**
+ * 订阅者需要实现的接口
+ *
+ * @author Qu Yunshuo
+ * @since 2023/5/31 14:23
+ */
+interface ForegroundBackgroundObserver {
+    fun foregroundBackgroundNotify(isForeground: Boolean)
+}
+
+/**
+ * 被观察者抽象主题
+ *
+ * @author Qu Yunshuo
+ * @since 2023/5/31 14:24
+ */
+interface ForegroundBackgroundSubject {
+    fun notifyObservers()
+    fun addObserve(observer: ForegroundBackgroundObserver)
+    fun removeObserver(observer: ForegroundBackgroundObserver)
+}

+ 73 - 0
lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/ProcessUtils.kt

@@ -0,0 +1,73 @@
+package com.quyunshuo.androidbaseframemvvm.base.utils
+
+import android.app.ActivityManager
+import android.content.Context
+import android.content.pm.PackageManager
+import android.os.Process
+import kotlin.jvm.Throws
+
+/**
+ * 进程工具类
+ *
+ * @author Qu Yunshuo
+ * @since 3/16/21 9:06 AM
+ */
+object ProcessUtils {
+
+    /**
+     * 获取当前所有进程
+     *
+     * @param context Context 上下文
+     * @return List<ActivityManager.RunningAppProcessInfo> 当前所有进程
+     */
+    fun getRunningAppProcessList(context: Context): List<ActivityManager.RunningAppProcessInfo> {
+        val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
+        return activityManager.runningAppProcesses
+    }
+
+    /**
+     * 判断该进程id是否属于该进程名的进程
+     *
+     * @param context Context 上下文
+     * @param processId Int 进程Id
+     * @param processName String 进程名
+     * @return Boolean
+     */
+    fun isPidOfProcessName(context: Context, processId: Int, processName: String): Boolean {
+        // 遍历所有进程找到该进程id对应的进程
+        for (process in getRunningAppProcessList(context)) {
+            if (process.pid == processId) {
+                // 判断该进程id是否和进程名一致
+                return (process.processName == processName)
+            }
+        }
+        return false
+    }
+
+    /**
+     * 获取主进程名
+     *
+     * @param context Context 上下文
+     * @return String 主进程名
+     * @throws PackageManager.NameNotFoundException if a package with the given name cannot be found on the system.
+     */
+    @Throws(PackageManager.NameNotFoundException::class)
+    fun getMainProcessName(context: Context): String {
+        val applicationInfo = context.packageManager.getApplicationInfo(context.packageName, 0)
+        return applicationInfo.processName
+    }
+
+    /**
+     * 判断当前进程是否是主进程
+     *
+     * @param context Context 上下文
+     * @return Boolean
+     * @throws PackageManager.NameNotFoundException if a package with the given name cannot be found on the system.
+     */
+    @Throws(PackageManager.NameNotFoundException::class)
+    fun isMainProcess(context: Context): Boolean {
+        val processId = Process.myPid()
+        val mainProcessName = getMainProcessName(context)
+        return isPidOfProcessName(context, processId, mainProcessName)
+    }
+}

+ 48 - 0
lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/RegisterEventBus.kt

@@ -0,0 +1,48 @@
+package com.quyunshuo.androidbaseframemvvm.base.utils
+
+/**
+ * 辅助注册 EventBus 的注解
+ *
+ * - **使用方式:**
+ * 在基类中的 `onCreate()`、`onDestroy()` 生命周期回调中去判断当前 Class 对象是否使用了该注解,
+ * 然后根据结果去注册或反注册
+ *
+ * - **为什么不统一注册:**
+ * 统一注册会在 EventBus 内部集合中留存,每次发送事件时,会遍历集合,过多无用的注册会导致速度变慢,
+ * 所以最好的方式就是根据需要进行注册,避免无意义的全部注册
+ *
+ * - **sample:**
+ * ```
+ * abstract class BaseActivity : AppCompatActivity() {
+ *
+ *     //  是否有 [RegisterEventBus] 注解 , 避免重复调用 [Class.isAnnotation]
+ *     private var mHaveRegisterEventBus = false
+ *     override fun onCreate(savedInstanceState: Bundle?) {
+ *         super.onCreate(savedInstanceState)
+ *         // 根据子类是否有 RegisterEventBus 注解決定是否进行注册 EventBus
+ *         if (javaClass.isAnnotationPresent(RegisterEventBus::class.java)) {
+ *             mHaveRegisterEventBus = true
+ *             EventBusUtils.register(this)
+ *         }
+ *     }
+ *
+ *     override fun onDestroy() {
+ *         // 根据子类是否有 RegisterEventBus 注解决定是否进行注册 EventBus
+ *         if (mHaveRegisterEventBus) {
+ *             EventBusUtils.unRegister(this)
+ *         }
+ *         super.onDestroy()
+ *     }
+ * }
+ *
+ * // 子类:
+ * @RegisterEventBus
+ * class SampleActivity : BaseActivity()
+ * ```
+ *
+ * @author Qu Yunshuo
+ * @since 2020/8/29
+ */
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+annotation class RegisterEventBus

+ 8 - 0
lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/ResourcesFun.kt

@@ -0,0 +1,8 @@
+package com.quyunshuo.androidbaseframemvvm.base.utils
+
+import androidx.annotation.StringRes
+import com.quyunshuo.androidbaseframemvvm.base.BaseApplication.Companion.application as app
+
+fun getString(@StringRes stringRes: Int): String {
+    return app.getString(stringRes)
+}

+ 63 - 0
lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/SpUtils.kt

@@ -0,0 +1,63 @@
+package com.quyunshuo.androidbaseframemvvm.base.utils
+
+import android.content.Context
+import com.tencent.mmkv.MMKV
+
+/**
+ * MMKV使用封装
+ *
+ * @author Qu Yunshuo
+ * @since 8/28/20
+ */
+object SpUtils {
+
+    /**
+     * 初始化
+     */
+    fun initMMKV(context: Context): String? = MMKV.initialize(context)
+
+    /**
+     * 保存数据(简化)
+     * 根据value类型自动匹配需要执行的方法
+     */
+    fun put(key: String, value: Any) =
+        when (value) {
+            is Int -> putInt(key, value)
+            is Long -> putLong(key, value)
+            is Float -> putFloat(key, value)
+            is Double -> putDouble(key, value)
+            is String -> putString(key, value)
+            is Boolean -> putBoolean(key, value)
+            else -> false
+        }
+
+    fun putString(key: String, value: String): Boolean? = MMKV.defaultMMKV()?.encode(key, value)
+
+    fun getString(key: String, defValue: String): String? =
+        MMKV.defaultMMKV()?.decodeString(key, defValue)
+
+    fun putInt(key: String, value: Int): Boolean? = MMKV.defaultMMKV()?.encode(key, value)
+
+    fun getInt(key: String, defValue: Int): Int? = MMKV.defaultMMKV()?.decodeInt(key, defValue)
+
+    fun putLong(key: String, value: Long): Boolean? = MMKV.defaultMMKV()?.encode(key, value)
+
+    fun getLong(key: String, defValue: Long): Long? = MMKV.defaultMMKV()?.decodeLong(key, defValue)
+
+    fun putDouble(key: String, value: Double): Boolean? = MMKV.defaultMMKV()?.encode(key, value)
+
+    fun getDouble(key: String, defValue: Double): Double? =
+        MMKV.defaultMMKV()?.decodeDouble(key, defValue)
+
+    fun putFloat(key: String, value: Float): Boolean? = MMKV.defaultMMKV()?.encode(key, value)
+
+    fun getFloat(key: String, defValue: Float): Float? =
+        MMKV.defaultMMKV()?.decodeFloat(key, defValue)
+
+    fun putBoolean(key: String, value: Boolean): Boolean? = MMKV.defaultMMKV()?.encode(key, value)
+
+    fun getBoolean(key: String, defValue: Boolean): Boolean? =
+        MMKV.defaultMMKV()?.decodeBool(key, defValue)
+
+    fun contains(key: String): Boolean? = MMKV.defaultMMKV()?.contains(key)
+}

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1497 - 0
lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/SpannableStringUtils.java


+ 14 - 0
lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/StateLayoutEnum.kt

@@ -0,0 +1,14 @@
+package com.quyunshuo.androidbaseframemvvm.base.utils
+
+/**
+ * 状态视图的状态枚举
+ *
+ * @author Qu Yunshuo
+ * @since 2021/7/10 9:16 上午
+ */
+enum class StateLayoutEnum {
+    HIDE,       // 隐藏
+    LOADING,    // 加载中
+    ERROR,      // 错误
+    NO_DATA     // 没有数据
+}

+ 24 - 0
lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/ThreadUtils.kt

@@ -0,0 +1,24 @@
+package com.quyunshuo.androidbaseframemvvm.base.utils
+
+import android.os.Build
+import android.os.Looper
+
+/**
+ * 线程相关工具类
+ *
+ * @author Qu Yunshuo
+ * @since 2023/3/12 19:29
+ */
+object ThreadUtils {
+
+    /**
+     * 判断当前是否是主线程
+     * 在 [Build.VERSION.SDK_INT] >= [Build.VERSION_CODES.M] 有一个简化方法来判断当前线程是否是主线程
+     * ```
+     * Looper.getMainLooper().isCurrentThread()
+     * ```
+     *
+     * @return Boolean
+     */
+    fun isMainThread(): Boolean = Looper.getMainLooper().thread == Thread.currentThread()
+}

+ 46 - 0
lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/ToastUtils.kt

@@ -0,0 +1,46 @@
+package com.quyunshuo.androidbaseframemvvm.base.utils
+
+import android.os.Build
+import android.os.Handler
+import android.os.Looper
+import android.widget.Toast
+import androidx.annotation.StringRes
+import com.quyunshuo.androidbaseframemvvm.base.BaseApplication
+
+private val mToastHandler by lazy { Handler(Looper.getMainLooper()) }
+
+private var mToast: Toast? = null
+
+@JvmOverloads
+fun toast(text: String, duration: Int = Toast.LENGTH_SHORT) {
+    postToast(text, duration)
+}
+
+@JvmOverloads
+fun toast(@StringRes id: Int, duration: Int = Toast.LENGTH_SHORT) {
+    postToast(getString(id), duration)
+}
+
+private fun postToast(text: String, duration: Int) {
+    mToastHandler.post {
+        setToast(text, duration)
+        mToast?.show()
+    }
+}
+
+private fun setToast(text: String, duration: Int) {
+    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
+        if (mToast == null) {
+            mToast = Toast.makeText(BaseApplication.context, text, duration)
+        } else {
+            mToast?.duration = duration
+            mToast?.setText(text)
+        }
+    } else {
+        if (mToast != null) {
+            mToast?.cancel()
+            mToast = null
+        }
+        mToast = Toast.makeText(BaseApplication.context, text, duration)
+    }
+}

+ 43 - 0
lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/Utils.kt

@@ -0,0 +1,43 @@
+package com.quyunshuo.androidbaseframemvvm.base.utils
+
+import android.util.Log
+import com.alibaba.android.arouter.launcher.ARouter
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.catch
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOn
+
+/**
+ * 使用 Flow 做的简单的轮询
+ * 请使用单独的协程来进行管理该 Flow
+ * Flow 仍有一些操作符是实验性的 使用时需添加 @InternalCoroutinesApi 注解
+ * @param intervals 轮询间隔时间/毫秒
+ * @param block 需要执行的代码块
+ */
+suspend fun startPolling(intervals: Long, block: () -> Unit) {
+    flow {
+        while (true) {
+            delay(intervals)
+            emit(0)
+        }
+    }
+        .catch { Log.e("flow", "startPolling: $it") }
+        .flowOn(Dispatchers.Main)
+        .collect { block.invoke() }
+}
+/**************************************************************************************************/
+
+/**
+ * 发送普通EventBus事件
+ */
+fun sendEvent(event: Any) = EventBusUtils.postEvent(event)
+
+/**************************************************************************************************/
+/**
+ * 阿里路由不带参数跳转
+ * @param routerUrl String 路由地址
+ */
+fun aRouterJump(routerUrl: String) {
+    ARouter.getInstance().build(routerUrl).navigation()
+}

+ 41 - 0
lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/network/AutoRegisterNetListener.kt

@@ -0,0 +1,41 @@
+package com.quyunshuo.androidbaseframemvvm.base.utils.network
+
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleObserver
+import androidx.lifecycle.OnLifecycleEvent
+
+/**
+ * 自动注册网络状态监听
+ * 使用的是[androidx.lifecycle.LifecycleObserver]来同步生命周期
+ *
+ * @author Qu Yunshuo
+ * @since 2021/7/11 4:56 下午
+ */
+class AutoRegisterNetListener constructor(listener: NetworkStateChangeListener) :
+    LifecycleObserver {
+
+    /**
+     * 当前需要自动注册的监听器
+     */
+    private var mListener: NetworkStateChangeListener? = null
+
+    init {
+        mListener = listener
+    }
+
+    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
+    fun register() {
+        mListener?.run { NetworkStateClient.setListener(this) }
+    }
+
+    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
+    fun unregister() {
+        NetworkStateClient.removeListener()
+    }
+
+    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
+    fun clean() {
+        NetworkStateClient.removeListener()
+        mListener = null
+    }
+}

+ 0 - 0
lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/network/NetworkCallbackImpl.kt


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