Browse Source

首次提交

liuxiaolong 2 years ago
commit
7b3f1946f8
81 changed files with 4924 additions and 0 deletions
  1. 51 0
      .gitignore
  2. 1 0
      app/.gitignore
  3. 42 0
      app/build.gradle
  4. 21 0
      app/proguard-rules.pro
  5. 17 0
      app/src/main/AndroidManifest.xml
  6. 30 0
      app/src/main/res/drawable-v24/ic_launcher_foreground.xml
  7. 170 0
      app/src/main/res/drawable/ic_launcher_background.xml
  8. 5 0
      app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
  9. 5 0
      app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
  10. BIN
      app/src/main/res/mipmap-hdpi/ic_launcher.webp
  11. BIN
      app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
  12. BIN
      app/src/main/res/mipmap-mdpi/ic_launcher.webp
  13. BIN
      app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
  14. BIN
      app/src/main/res/mipmap-xhdpi/ic_launcher.webp
  15. BIN
      app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
  16. BIN
      app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
  17. BIN
      app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
  18. BIN
      app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
  19. BIN
      app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
  20. 16 0
      app/src/main/res/values-night/themes.xml
  21. 10 0
      app/src/main/res/values/colors.xml
  22. 3 0
      app/src/main/res/values/strings.xml
  23. 16 0
      app/src/main/res/values/themes.xml
  24. 13 0
      app/src/main/res/xml/backup_rules.xml
  25. 19 0
      app/src/main/res/xml/data_extraction_rules.xml
  26. 11 0
      build.gradle
  27. 23 0
      gradle.properties
  28. BIN
      gradle/wrapper/gradle-wrapper.jar
  29. 6 0
      gradle/wrapper/gradle-wrapper.properties
  30. 185 0
      gradlew
  31. 89 0
      gradlew.bat
  32. 3 0
      lib_common/.gitignore
  33. 3 0
      lib_common/README.md
  34. 116 0
      lib_common/build.gradle
  35. 0 0
      lib_common/consumer-rules.pro
  36. BIN
      lib_common/libs/firebase-crashlytics.aar
  37. 21 0
      lib_common/proguard-rules.pro
  38. 11 0
      lib_common/src/main/AndroidManifest.xml
  39. 38 0
      lib_common/src/main/java/com/kdanmobile/android/common/activitycontracts/BaseActivityResultLauncher.kt
  40. 116 0
      lib_common/src/main/java/com/kdanmobile/android/common/base/ActivitysUtils.kt
  41. 65 0
      lib_common/src/main/java/com/kdanmobile/android/common/base/view/BaseActivity.kt
  42. 31 0
      lib_common/src/main/java/com/kdanmobile/android/common/base/view/BaseActivityHandler.kt
  43. 49 0
      lib_common/src/main/java/com/kdanmobile/android/common/base/view/BaseActivityLifecycleEvents.kt
  44. 55 0
      lib_common/src/main/java/com/kdanmobile/android/common/base/view/BaseDialogFragment.kt
  45. 105 0
      lib_common/src/main/java/com/kdanmobile/android/common/base/view/BaseFragment.kt
  46. 23 0
      lib_common/src/main/java/com/kdanmobile/android/common/base/viewbinding/BaseBindingActivity.kt
  47. 23 0
      lib_common/src/main/java/com/kdanmobile/android/common/base/viewbinding/BaseBindingBottomSheetDialog.kt
  48. 30 0
      lib_common/src/main/java/com/kdanmobile/android/common/base/viewbinding/BaseBindingDialogFragment.kt
  49. 31 0
      lib_common/src/main/java/com/kdanmobile/android/common/base/viewbinding/BaseBindingFragment.kt
  50. 41 0
      lib_common/src/main/java/com/kdanmobile/android/common/base/viewbinding/BindingViewHolder.kt
  51. 34 0
      lib_common/src/main/java/com/kdanmobile/android/common/config/MyPdfBaseModule.kt
  52. 45 0
      lib_common/src/main/java/com/kdanmobile/android/common/extension/ActivityExtensions.kt
  53. 314 0
      lib_common/src/main/java/com/kdanmobile/android/common/extension/AnimExtension.kt
  54. 51 0
      lib_common/src/main/java/com/kdanmobile/android/common/extension/ContextExtensions.kt
  55. 26 0
      lib_common/src/main/java/com/kdanmobile/android/common/extension/DialogFragmentExtenstions.kt
  56. 207 0
      lib_common/src/main/java/com/kdanmobile/android/common/extension/FileExtensions.kt
  57. 17 0
      lib_common/src/main/java/com/kdanmobile/android/common/extension/IntentExtensions.kt
  58. 11 0
      lib_common/src/main/java/com/kdanmobile/android/common/extension/NetExtensions.kt
  59. 72 0
      lib_common/src/main/java/com/kdanmobile/android/common/extension/RecycleViewExtensions.kt
  60. 387 0
      lib_common/src/main/java/com/kdanmobile/android/common/extension/ScreenExtensions.kt
  61. 34 0
      lib_common/src/main/java/com/kdanmobile/android/common/extension/StringExtensions.kt
  62. 71 0
      lib_common/src/main/java/com/kdanmobile/android/common/extension/SystemExtensions.kt
  63. 16 0
      lib_common/src/main/java/com/kdanmobile/android/common/extension/ViewBindingExtension.kt
  64. 127 0
      lib_common/src/main/java/com/kdanmobile/android/common/extension/ViewExtensions.kt
  65. 50 0
      lib_common/src/main/java/com/kdanmobile/android/common/manager/AppStatusManager.kt
  66. 107 0
      lib_common/src/main/java/com/kdanmobile/android/common/manager/GoogleAnalyticsManager.kt
  67. 124 0
      lib_common/src/main/java/com/kdanmobile/android/common/utils/ReflectionUtils.kt
  68. 27 0
      lib_common/src/main/java/com/kdanmobile/android/common/utils/decoration/PaddingItemDecoration.kt
  69. 50 0
      lib_common/src/main/java/com/kdanmobile/android/common/utils/decoration/SpacesItemDecoration.kt
  70. 27 0
      lib_common/src/main/java/com/kdanmobile/android/common/utils/dimens/DimenGenerator.kt
  71. 16 0
      lib_common/src/main/java/com/kdanmobile/android/common/utils/dimens/DimenTypes.kt
  72. 95 0
      lib_common/src/main/java/com/kdanmobile/android/common/utils/dimens/MakeUtils.kt
  73. 63 0
      lib_common/src/main/java/com/kdanmobile/android/common/utils/eventbus/EventBusUtils.kt
  74. 16 0
      lib_common/src/main/java/com/kdanmobile/android/common/utils/eventbus/MessageEvent.kt
  75. 33 0
      lib_common/src/main/java/com/kdanmobile/android/common/utils/file/CloseUtils.kt
  76. 428 0
      lib_common/src/main/java/com/kdanmobile/android/common/utils/file/FileIOUtils.kt
  77. 36 0
      lib_common/src/main/java/com/kdanmobile/android/common/utils/firebase/FirebaseEventUtils.kt
  78. 39 0
      lib_common/src/main/java/com/kdanmobile/android/common/utils/firebase/FirebaseRemoteConfig.kt
  79. 873 0
      lib_common/src/main/java/com/kdanmobile/android/common/utils/image/BitmapUtils.kt
  80. 1 0
      lib_common/src/main/res/values/strings.xml
  81. 33 0
      settings.gradle

+ 51 - 0
.gitignore

@@ -0,0 +1,51 @@
+*.iml
+.gradle
+/.idea
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
+
+# ---> Android
+# Built application files
+*.apk
+*.ap_
+
+# Files for the Dalvik VM
+*.dex
+
+# Java class files
+*.class
+
+# Generated files
+bin/
+gen/
+
+# 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/
+

+ 1 - 0
app/.gitignore

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

+ 42 - 0
app/build.gradle

@@ -0,0 +1,42 @@
+plugins {
+    id 'com.android.application'
+    id 'org.jetbrains.kotlin.android'
+}
+
+android {
+    compileSdk 32
+
+    defaultConfig {
+        applicationId "com.kdanmobile.android.lib"
+        minSdk 21
+        targetSdk 32
+        versionCode 1
+        versionName "1.0"
+
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+    }
+
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+    }
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+    kotlinOptions {
+        jvmTarget = '1.8'
+    }
+}
+
+dependencies {
+
+    implementation 'androidx.core:core-ktx:1.7.0'
+    implementation 'androidx.appcompat:appcompat:1.4.2'
+    implementation 'com.google.android.material:material:1.6.1'
+    testImplementation 'junit:junit:4.13.2'
+    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
+    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
+}

+ 21 - 0
app/proguard-rules.pro

@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile

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

@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    package="com.kdanmobile.android.lib">
+
+    <application
+        android:allowBackup="true"
+        android:dataExtractionRules="@xml/data_extraction_rules"
+        android:fullBackupContent="@xml/backup_rules"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:roundIcon="@mipmap/ic_launcher_round"
+        android:supportsRtl="true"
+        android:theme="@style/Theme.Lib"
+        tools:targetApi="31" />
+
+</manifest>

File diff suppressed because it is too large
+ 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-hdpi/ic_launcher.webp


BIN
app/src/main/res/mipmap-hdpi/ic_launcher_round.webp


BIN
app/src/main/res/mipmap-mdpi/ic_launcher.webp


BIN
app/src/main/res/mipmap-mdpi/ic_launcher_round.webp


BIN
app/src/main/res/mipmap-xhdpi/ic_launcher.webp


BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp


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


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


BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp


BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp


+ 16 - 0
app/src/main/res/values-night/themes.xml

@@ -0,0 +1,16 @@
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <!-- Base application theme. -->
+    <style name="Theme.Lib" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
+        <!-- Primary brand color. -->
+        <item name="colorPrimary">@color/purple_200</item>
+        <item name="colorPrimaryVariant">@color/purple_700</item>
+        <item name="colorOnPrimary">@color/black</item>
+        <!-- Secondary brand color. -->
+        <item name="colorSecondary">@color/teal_200</item>
+        <item name="colorSecondaryVariant">@color/teal_200</item>
+        <item name="colorOnSecondary">@color/black</item>
+        <!-- Status bar color. -->
+        <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
+        <!-- Customize your theme here. -->
+    </style>
+</resources>

+ 10 - 0
app/src/main/res/values/colors.xml

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="purple_200">#FFBB86FC</color>
+    <color name="purple_500">#FF6200EE</color>
+    <color name="purple_700">#FF3700B3</color>
+    <color name="teal_200">#FF03DAC5</color>
+    <color name="teal_700">#FF018786</color>
+    <color name="black">#FF000000</color>
+    <color name="white">#FFFFFFFF</color>
+</resources>

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

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

+ 16 - 0
app/src/main/res/values/themes.xml

@@ -0,0 +1,16 @@
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <!-- Base application theme. -->
+    <style name="Theme.Lib" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
+        <!-- Primary brand color. -->
+        <item name="colorPrimary">@color/purple_500</item>
+        <item name="colorPrimaryVariant">@color/purple_700</item>
+        <item name="colorOnPrimary">@color/white</item>
+        <!-- Secondary brand color. -->
+        <item name="colorSecondary">@color/teal_200</item>
+        <item name="colorSecondaryVariant">@color/teal_700</item>
+        <item name="colorOnSecondary">@color/black</item>
+        <!-- Status bar color. -->
+        <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
+        <!-- Customize your theme here. -->
+    </style>
+</resources>

+ 13 - 0
app/src/main/res/xml/backup_rules.xml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+   Sample backup rules file; uncomment and customize as necessary.
+   See https://developer.android.com/guide/topics/data/autobackup
+   for details.
+   Note: This file is ignored for devices older that API 31
+   See https://developer.android.com/about/versions/12/backup-restore
+-->
+<full-backup-content>
+    <!--
+   <include domain="sharedpref" path="."/>
+   <exclude domain="sharedpref" path="device.xml"/>
+-->
+</full-backup-content>

+ 19 - 0
app/src/main/res/xml/data_extraction_rules.xml

@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+   Sample data extraction rules file; uncomment and customize as necessary.
+   See https://developer.android.com/about/versions/12/backup-restore#xml-changes
+   for details.
+-->
+<data-extraction-rules>
+    <cloud-backup>
+        <!-- TODO: Use <include> and <exclude> to control what is backed up.
+        <include .../>
+        <exclude .../>
+        -->
+    </cloud-backup>
+    <!--
+    <device-transfer>
+        <include .../>
+        <exclude .../>
+    </device-transfer>
+    -->
+</data-extraction-rules>

+ 11 - 0
build.gradle

@@ -0,0 +1,11 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+plugins {
+    id 'com.android.application' version '7.2.0' apply false
+    id 'com.android.library' version '7.2.0' apply false
+    id 'org.jetbrains.kotlin.android' version '1.7.10' apply false
+}
+
+
+task clean(type: Delete) {
+    delete rootProject.buildDir
+}

+ 23 - 0
gradle.properties

@@ -0,0 +1,23 @@
+# 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 -Dfile.encoding=UTF-8
+# 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
+# Kotlin code style for this project: "official" or "obsolete":
+kotlin.code.style=official
+# Enables namespacing of each library's R class so that its R class includes only the
+# resources declared in the library itself and none from the library's dependencies,
+# thereby reducing the size of the R class for that library
+android.nonTransitiveRClass=true

BIN
gradle/wrapper/gradle-wrapper.jar


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

@@ -0,0 +1,6 @@
+#Wed Jul 27 15:06:45 CST 2022
+distributionBase=GRADLE_USER_HOME
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
+distributionPath=wrapper/dists
+zipStorePath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME

+ 185 - 0
gradlew

@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+##  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='"-Xmx64m" "-Xms64m"'
+
+# 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 or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; 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=`expr $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"
+
+exec "$JAVACMD" "$@"

+ 89 - 0
gradlew.bat

@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem      https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@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 Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@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="-Xmx64m" "-Xms64m"
+
+@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 execute
+
+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 execute
+
+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
+
+: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 %*
+
+: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

+ 3 - 0
lib_common/.gitignore

@@ -0,0 +1,3 @@
+/build
+/src/androidTest
+/src/test

+ 3 - 0
lib_common/README.md

@@ -0,0 +1,3 @@
+# lib_common
+
+项目基础组件

+ 116 - 0
lib_common/build.gradle

@@ -0,0 +1,116 @@
+plugins {
+    id 'com.android.library'
+    id 'org.jetbrains.kotlin.android'
+}
+
+android {
+    compileSdk 31
+
+    defaultConfig {
+        minSdk 21
+        targetSdk 30
+
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+        consumerProguardFiles "consumer-rules.pro"
+    }
+
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+    }
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+    kotlinOptions {
+        jvmTarget = '1.8'
+    }
+
+    buildFeatures {
+        viewBinding = true
+    }
+
+}
+
+dependencies {
+
+    api 'androidx.core:core-ktx:1.7.0'
+    api 'androidx.appcompat:appcompat:1.4.2'
+    api 'com.google.android.material:material:1.6.1'
+    api 'androidx.recyclerview:recyclerview:1.2.1'
+
+    testImplementation 'junit:junit:4.13.2'
+    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
+    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
+
+    def kotlin_version = '1.7.10'
+    def lifecycle_version = '2.5.0'
+    api "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
+
+    // ViewModel
+    api("androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version")
+    // LiveData
+    api("androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version")
+    // Lifecycles only (without ViewModel or LiveData)
+    api("androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version")
+    // Saved state module for ViewModel
+    api("androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version")
+    // alternately - if using Java8, use the following instead of lifecycle-compiler
+    api("androidx.lifecycle:lifecycle-common-java8:$lifecycle_version")
+    // optional - helpers for implementing LifecycleOwner in a Service
+    api("androidx.lifecycle:lifecycle-service:$lifecycle_version")
+    // optional - ProcessLifecycleOwner provides a lifecycle for the whole application process
+    api("androidx.lifecycle:lifecycle-process:$lifecycle_version")
+    // optional - ReactiveStreams support for LiveData
+    api("androidx.lifecycle:lifecycle-reactivestreams-ktx:$lifecycle_version")
+
+    //jetpack navigation
+    api 'androidx.navigation:navigation-fragment-ktx:2.4.2'
+    api 'androidx.navigation:navigation-ui-ktx:2.4.2'
+
+    /*Kotlin*/
+    api "androidx.core:core-ktx:1.8.0"
+    api "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+    api 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.3'
+    api 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.3'
+
+    /*google GA分析*/
+    api 'com.google.android.gms:play-services-analytics:18.0.1'
+
+    /*Android6.0 限兼容*/
+    api 'pub.devrel:easypermissions:3.0.0'
+    /*沉浸式状态栏*/
+    api 'com.gyf.immersionbar:immersionbar:3.0.0'
+    /*Logger日志打印*/
+    api 'com.orhanobut:logger:2.2.0'
+    /*EventBus*/
+    api 'org.greenrobot:eventbus:3.2.0'
+
+    /*Coil图片加载库*/
+    api 'io.coil-kt:coil:1.3.2'
+    api("io.coil-kt:coil-gif:1.3.2")
+    api("io.coil-kt:coil-svg:1.3.2")
+    api("io.coil-kt:coil-video:1.3.2")
+
+    /*Json解析包*/
+    api 'com.google.code.gson:gson:2.8.9'
+
+    /*google firebase bom*/
+    api platform('com.google.firebase:firebase-bom:30.2.0')
+    /*google firebase 分析*/
+    api 'com.google.firebase:firebase-analytics-ktx'
+
+//    api 'com.google.firebase:firebase-crashlytics'
+    /*google firebase config*/
+    api 'com.google.firebase:firebase-config-ktx'
+
+    //引入firebase crashlytics aar 需要的子依赖
+//    api(name: 'firebase-crashlytics', ext: 'aar')
+    api 'com.google.firebase:firebase-encoders:17.0.0'
+    api 'com.google.firebase:firebase-encoders-json:18.0.0'
+    api 'com.google.android.datatransport:transport-api:3.0.0'
+    api 'com.google.android.datatransport:transport-backend-cct:3.1.5'
+    api 'com.google.android.datatransport:transport-runtime:3.1.5'
+}

+ 0 - 0
lib_common/consumer-rules.pro


BIN
lib_common/libs/firebase-crashlytics.aar


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

+ 11 - 0
lib_common/src/main/AndroidManifest.xml

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.kdanmobile.android.common">
+
+    <application>
+        <activity
+            android:name=".BaseActivity"
+            android:exported="false" />
+    </application>
+
+</manifest>

+ 38 - 0
lib_common/src/main/java/com/kdanmobile/android/common/activitycontracts/BaseActivityResultLauncher.kt

@@ -0,0 +1,38 @@
+package com.kdanmobile.android.common.activitycontracts
+
+import androidx.activity.result.ActivityResultCallback
+import androidx.activity.result.ActivityResultCaller
+import androidx.activity.result.ActivityResultLauncher
+import androidx.activity.result.contract.ActivityResultContract
+
+/**
+ * @classname BaseActivityResultLauncher
+ * @author LiuXiaoLong
+ * @date 2022-07-27 AM
+ * @description 基础ActivityResult API, I:输入的参数, O:响应的数据
+ */
+open class BaseActivityResultLauncher<I, O>(
+    caller: ActivityResultCaller,
+    contract: ActivityResultContract<I, O>
+) {
+
+    private val launcher: ActivityResultLauncher<I>
+    private var callback: ActivityResultCallback<O>? = null
+
+    init {
+        launcher = caller.registerForActivityResult(contract) { result: O ->
+            callback?.onActivityResult(result)
+            callback = null
+        }
+    }
+
+    fun launch(input: I, callback: ActivityResultCallback<O>) {
+        this.callback = callback
+        launcher.launch(input)
+    }
+
+    fun launch(input: I) {
+        launcher.launch(input)
+    }
+
+}

+ 116 - 0
lib_common/src/main/java/com/kdanmobile/android/common/base/ActivitysUtils.kt

@@ -0,0 +1,116 @@
+package com.kdanmobile.android.common.base
+
+import android.app.Activity
+import android.os.Build
+import com.kdanmobile.android.common.base.view.BaseActivity
+import com.orhanobut.logger.Logger
+import kotlin.system.exitProcess
+
+
+/**
+ *    @author : hubowen
+ *    @e-mail : hubowen@kdanmobile.com
+ *    @date   : 2019-12-23  15:23
+ *    @description:
+ */
+object ActivitysUtils {
+    private val mLock = Object()
+    private val activitys = mutableListOf<Activity>()
+
+    fun size() = activitys.size
+
+    fun current() = synchronized(mLock) {
+        clearNotAliveActivitys().elementAtOrNull(0)
+    }
+
+    fun add(activity: Activity?) {
+        synchronized(mLock) {
+            if (isActivityAlive(activity)) clearNotAliveActivitys().add(0, activity!!)
+        }
+    }
+
+    fun remove(activity: Activity?) {
+        synchronized(mLock) {
+            clearNotAliveActivitys()
+                .filter { !isEqual(it.javaClass, activity?.javaClass) }
+                .run {
+                    activitys.clear()
+                    activitys.addAll(this)
+                }
+        }
+    }
+
+    fun findActivity(cls: Class<*>) = synchronized(mLock) {
+        clearNotAliveActivitys()
+            .filter { isEqual(it.javaClass, cls) }
+            .elementAtOrNull(0)
+    }
+
+    fun finish(vararg cls: Class<*>) {
+        synchronized(mLock) {
+            val others = mutableListOf<Activity>()
+            clearNotAliveActivitys()
+                .apply {
+                    forEach {
+                        when {
+                            cls.any { c -> isEqual(it.javaClass, c) } -> it.finish()
+                            else -> others.add(it)
+                        }
+                    }
+                    clear()
+                    addAll(others)
+                }
+        }
+    }
+
+    fun finishOtherActivities(cls: Class<*>) {
+        synchronized(mLock) {
+            val current = mutableListOf<Activity>()
+            clearNotAliveActivitys()
+                .apply {
+                    forEach {
+                        when {
+                            isEqual(it.javaClass, cls) && (0 == current.size) -> current.add(it)
+                            else -> it.finish()
+                        }
+                    }
+                    clear()
+                    addAll(current)
+                }
+        }
+    }
+
+    fun finishAllActivitys() {
+        synchronized(mLock) {
+            clearNotAliveActivitys().apply {
+                forEach { it.finish() }
+                clear()
+            }
+            exitProcess(0)
+        }
+    }
+
+    private fun isEqual(old: Class<*>?, new: Class<*>?) = when {
+        (null == old) || (null == new) -> false
+        else -> old.simpleName == new.simpleName
+    }
+
+    private fun clearNotAliveActivitys() = activitys.filter {
+        isActivityAlive(it)
+    }.run {
+        activitys.clear()
+        activitys.addAll(this)
+        activitys
+    }
+
+    fun isActivityAlive(activity: Activity?) = when (activity) {
+        null -> false
+        else -> !activity.isFinishing && (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1 || !activity.isDestroyed)
+    }
+
+    fun BaseActivity.refreshApp() {
+        //杀掉以前进程
+        Logger.t("ActivityUtils").e("refreshApp")
+        finishAllActivitys()
+    }
+}

+ 65 - 0
lib_common/src/main/java/com/kdanmobile/android/common/base/view/BaseActivity.kt

@@ -0,0 +1,65 @@
+package com.kdanmobile.android.common.base.view
+
+import android.annotation.SuppressLint
+import android.os.Bundle
+import androidx.appcompat.app.AppCompatActivity
+import com.gyf.immersionbar.ImmersionBar
+import com.kdanmobile.android.common.base.ActivitysUtils.finishAllActivitys
+import com.kdanmobile.android.common.base.extension.removeAllFragments
+import com.kdanmobile.android.common.manager.AppStatusConstant.STATUS_FORCE_KILLED
+import com.kdanmobile.android.common.manager.AppStatusConstant.STATUS_NORMAL
+import com.kdanmobile.android.common.manager.AppStatusManager
+import com.orhanobut.logger.Logger
+
+
+/**
+ * @classname BaseActivity
+ * @author LiuXiaoLong
+ * @date 2022-07-27
+ * @description
+ */
+open class BaseActivity : AppCompatActivity() {
+
+    //沉浸式状体栏
+    @SuppressLint("StaticFieldLeak")
+    var mImmersionBar: ImmersionBar? = null
+
+    //标志符号:判断当前activity是否显示
+    var isCurrentFront = false
+
+    val handler : BaseActivityHandler by lazy { BaseActivityHandler(this) }
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        lifecycle.addObserver(BaseActivityLifecycleEvents(this))
+        validateAppStatus()
+    }
+
+    override fun onStop() {
+        super.onStop()
+        if (isFinishing) removeAllFragments()
+    }
+
+    private fun validateAppStatus() {
+        when (AppStatusManager.appStatus) {
+            //异常退出
+            STATUS_FORCE_KILLED -> {
+                Logger.t("PdfBaseActivity").e("STATUS_FORCE_KILLED")
+                refreshApp()
+            }
+            STATUS_NORMAL -> {
+                Logger.t("PdfBaseActivity").e("STATUS_NORMAL")
+                //不需要处理或者初始方法调用
+            }
+        }
+    }
+
+    fun refreshApp() {
+        //杀掉以前进程
+        Logger.t("ActivityUtils").e("refreshApp")
+        finishAllActivitys()
+    }
+
+
+
+}

+ 31 - 0
lib_common/src/main/java/com/kdanmobile/android/common/base/view/BaseActivityHandler.kt

@@ -0,0 +1,31 @@
+package com.kdanmobile.android.common.base.view
+
+import android.annotation.SuppressLint
+import android.os.Handler
+import android.os.Looper
+import android.os.Message
+import com.kdanmobile.android.common.base.ActivitysUtils
+import com.kdanmobile.android.common.base.view.BaseActivity
+
+/**
+ * @classname:BaseActivity
+ * @description:初始化Handler对象,采用静态内部类的实现方式
+ */
+typealias HandlerCallback = (msg : Message)-> Unit
+
+@SuppressLint("HandlerLeak")
+class BaseActivityHandler(var baseActivity: BaseActivity) : Handler(Looper.getMainLooper()) {
+
+    var callback : HandlerCallback? = null
+
+    override fun handleMessage(msg: Message) {
+        super.handleMessage(msg)
+        when {
+            ActivitysUtils.isActivityAlive(baseActivity) -> callback?.invoke(msg)
+        }
+    }
+
+    fun setHandlerCallBack(handlerCallBack: HandlerCallback) {
+        callback = handlerCallBack
+    }
+}

+ 49 - 0
lib_common/src/main/java/com/kdanmobile/android/common/base/view/BaseActivityLifecycleEvents.kt

@@ -0,0 +1,49 @@
+package com.kdanmobile.android.common.base.view
+
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleEventObserver
+import androidx.lifecycle.LifecycleOwner
+import com.google.android.gms.analytics.GoogleAnalytics
+import com.kdanmobile.android.common.base.ActivitysUtils
+import com.kdanmobile.android.common.manager.AppStatusConstant
+import com.kdanmobile.android.common.manager.AppStatusManager
+import com.orhanobut.logger.Logger
+
+/**
+ * @classname BaseActivityLifecycleEvents
+ * @author LiuXiaoLong
+ * @date 2022-07-27 PM
+ * @description BaseActivity 生命周期监听,在对应生命周期执行特定方法
+ */
+class BaseActivityLifecycleEvents(var activity: BaseActivity) : LifecycleEventObserver{
+
+
+    override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
+        when(event){
+            Lifecycle.Event.ON_CREATE->{
+            }
+            Lifecycle.Event.ON_RESUME->{
+                activity.isCurrentFront = true
+            }
+            Lifecycle.Event.ON_PAUSE->{
+                activity.isCurrentFront = false
+            }
+            Lifecycle.Event.ON_START->{
+                GoogleAnalytics.getInstance(activity).reportActivityStart(activity)
+            }
+            Lifecycle.Event.ON_STOP->{
+                GoogleAnalytics.getInstance(activity).reportActivityStop(activity)
+            }
+            Lifecycle.Event.ON_DESTROY->{
+                activity.mImmersionBar = null
+                activity.handler.removeCallbacksAndMessages(null)
+            }
+            else->{
+
+            }
+        }
+    }
+
+
+
+}

+ 55 - 0
lib_common/src/main/java/com/kdanmobile/android/common/base/view/BaseDialogFragment.kt

@@ -0,0 +1,55 @@
+package com.kdanmobile.android.common.base.view
+
+import android.os.Bundle
+import android.view.KeyEvent
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.DialogFragment
+import androidx.fragment.app.FragmentManager
+import com.kdanmobile.android.common.extension.showAllowingStateLoss
+
+/**
+ * @classname:BaseDialogFragment
+ * @auther: LiuXiaoLong
+ * @date: 2022/7/27
+ * description:
+ */
+open class BaseDialogFragment : DialogFragment() {
+
+    /**
+     * 点击回退是否可以关闭弹窗
+     */
+    open var mCanKeyBack = true
+
+
+    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
+        return super.onCreateView(inflater, container, savedInstanceState)
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+        dialog?.setOnKeyListener { dialog, keyCode, event ->
+            if ((keyCode == KeyEvent.KEYCODE_BACK) or (keyCode == KeyEvent.KEYCODE_ESCAPE) and mCanKeyBack){
+                dismiss()
+                return@setOnKeyListener true
+            }
+            return@setOnKeyListener false
+        }
+    }
+
+    fun setDimAmount(dimAmount : Float){
+        dialog?.window?.attributes?.dimAmount = dimAmount
+    }
+
+    /**
+     * @param :[manager, tag]
+     * @return : void
+     * @methodName :onShow created by luozhipeng on 12/12/17 11:07.
+     * @description :自定义重写了父类的方法show
+     */
+    fun onShow(manager : FragmentManager, tag : String){
+        showAllowingStateLoss(manager, tag)
+    }
+
+}

+ 105 - 0
lib_common/src/main/java/com/kdanmobile/android/common/base/view/BaseFragment.kt

@@ -0,0 +1,105 @@
+package com.kdanmobile.android.common.base.view
+
+import android.content.Context
+import android.os.Bundle
+import android.os.Handler
+import android.os.Looper
+import android.view.View
+import androidx.fragment.app.Fragment
+import com.orhanobut.logger.Logger
+
+/**
+ *    @author : HuBoWen
+ *    @e-mail : hubowen@kdanmobile.com
+ *    @date   : 2020/9/14  3:55 PM
+ *    @description:基础Fragment
+ */
+open class BaseFragment : Fragment() {
+
+    companion object {
+        const val STATE_SAVE_IS_HIDDEN = "STATE_SAVE_IS_HIDDEN"
+    }
+
+    /**
+     * 上下文
+     *
+     */
+    private var context: Context? = null
+
+    /**
+     *  是否与View建立起映射关系
+     *
+     */
+    private var isInitView = false
+
+    /****** 是否是第一次加载数据  */
+    private var isFirstLoad = true
+
+    var handler: Handler? = null
+
+    override fun onAttach(context: Context) {
+        super.onAttach(context)
+        this.context = context
+    }
+
+    override fun getContext() = context
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        /****** 解决Fragment重叠问题 备注:
+         * 这里一定要在save为null时才加载Fragment,Fragment中onCreateView等生命周里加载根子Fragment同理
+         * 因为在页面重启时,Fragment会被保存恢复,而此时再加载Fragment会重复加载,导致重叠,所有activity中increase()
+         * if(findFragmentByTag(rootFragmentTag) == null){
+         * 正常情况下去 加载根Fragment
+         * } */
+        runCatching {
+            savedInstanceState?.let {
+                when {
+                    (savedInstanceState.getBoolean(STATE_SAVE_IS_HIDDEN)) -> childFragmentManager.beginTransaction().hide(this)
+                    else -> childFragmentManager.beginTransaction().show(this)
+                }
+                childFragmentManager.beginTransaction().commit()
+            }
+            handler = Handler(Looper.getMainLooper())
+        }
+    }
+
+    override fun onSaveInstanceState(outState: Bundle) {
+        super.onSaveInstanceState(outState)
+        /****** 自己保存Fragment的Hidden状态*/
+        outState.putBoolean(STATE_SAVE_IS_HIDDEN, isHidden)
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+        getView()?.let {
+            isInitView = true
+            onInitView()
+            onInitData()
+        }
+    }
+
+    protected open fun onInitView() {}
+
+    protected open fun onInitData() {
+        isFirstLoad = false
+    }
+
+    override fun onDestroyView() {
+        super.onDestroyView()
+        isInitView = false
+        isFirstLoad = true
+        if (isRemoving) Logger.t("baseFragment").d(javaClass.simpleName + " : onDestroyView! ")
+    }
+
+    override fun onDestroy() {
+        super.onDestroy()
+        handler?.removeCallbacksAndMessages(null)
+        handler = null
+        if (isRemoving) Logger.t("baseFragment").d(javaClass.simpleName + " : onDestroy! ")
+    }
+
+    open fun onBackPressed() {}
+
+
+}

+ 23 - 0
lib_common/src/main/java/com/kdanmobile/android/common/base/viewbinding/BaseBindingActivity.kt

@@ -0,0 +1,23 @@
+package com.kdanmobile.android.common.base.viewbinding
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import androidx.viewbinding.ViewBinding
+import com.kdanmobile.android.common.base.view.BaseActivity
+
+/**
+ * @classname:BaseBindingActivity
+ * @auther: LiuXiaoLong
+ * @date: 2022-07-27 PM
+ * description:
+ */
+abstract class BaseBindingActivity<VB : ViewBinding>(private val inflate: (LayoutInflater) -> VB) : BaseActivity() {
+
+    lateinit var binding: VB
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        binding = inflate(layoutInflater)
+        setContentView(binding.root)
+        super.onCreate(savedInstanceState)
+    }
+}

+ 23 - 0
lib_common/src/main/java/com/kdanmobile/android/common/base/viewbinding/BaseBindingBottomSheetDialog.kt

@@ -0,0 +1,23 @@
+package com.kdanmobile.android.common.base.viewbinding
+
+import android.content.Context
+import android.view.LayoutInflater
+import androidx.annotation.StyleRes
+import androidx.viewbinding.ViewBinding
+import com.google.android.material.bottomsheet.BottomSheetDialog
+
+/**
+ * @classname: BaseBindingBottomSheetDialog
+ * @auther: LiuXiaoLong
+ * @date: 2022-07-27 PM
+ * description:
+ */
+abstract class BaseBindingBottomSheetDialog<VB : ViewBinding>(inflate: (LayoutInflater) -> VB, context: Context, @StyleRes theme: Int) : BottomSheetDialog(context, theme) {
+
+    var binding: VB = inflate(layoutInflater)
+
+    init {
+        setContentView(binding.root)
+    }
+
+}

+ 30 - 0
lib_common/src/main/java/com/kdanmobile/android/common/base/viewbinding/BaseBindingDialogFragment.kt

@@ -0,0 +1,30 @@
+package com.kdanmobile.android.common.base.viewbinding
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.viewbinding.ViewBinding
+import com.kdanmobile.android.common.base.view.BaseDialogFragment
+
+/**
+ * @classname: BaseBindingDialogFragment
+ * @auther: LiuXiaoLong
+ * @date: 2022-07-27 PM
+ * description:
+ */
+abstract class BaseBindingDialogFragment<VB : ViewBinding>(private val inflate: (LayoutInflater, ViewGroup?, Boolean) -> VB) : BaseDialogFragment() {
+
+    private var _binding: VB? = null
+    val binding: VB? get() = _binding
+
+    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
+        _binding = inflate(inflater, container, false)
+        return binding?.root
+    }
+
+    override fun onDestroyView() {
+        super.onDestroyView()
+        _binding = null
+    }
+}

+ 31 - 0
lib_common/src/main/java/com/kdanmobile/android/common/base/viewbinding/BaseBindingFragment.kt

@@ -0,0 +1,31 @@
+package com.kdanmobile.android.common.base.viewbinding
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.viewbinding.ViewBinding
+import com.kdanmobile.android.common.base.view.BaseFragment
+
+/**
+ * @classname: BaseBindingFragment
+ * @auther: LiuXiaoLong
+ * @date: 2022-07-27 PM
+ * description:
+ */
+abstract class BaseBindingFragment<VB : ViewBinding>(private val inflate: (LayoutInflater, ViewGroup?, Boolean) -> VB) : BaseFragment() {
+
+    private var _binding: VB? = null
+    val binding: VB? get() = _binding
+
+    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
+        _binding = inflate(inflater, container, false)
+        return binding?.root
+    }
+
+    override fun onDestroyView() {
+        super.onDestroyView()
+        _binding = null
+    }
+
+}

+ 41 - 0
lib_common/src/main/java/com/kdanmobile/android/common/base/viewbinding/BindingViewHolder.kt

@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020. Dylan Cai
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress("unused")
+
+package com.kdanmobile.android.common.base.viewbinding
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+import androidx.viewbinding.ViewBinding
+
+/**
+ * @classname: BindingViewHolder
+ * @auther: LiuXiaoLong
+ * @date: 2022-07-27 PM
+ * description:
+ */
+class BindingViewHolder<VB : ViewBinding>(val binding: VB) : RecyclerView.ViewHolder(binding.root) {
+    constructor(parent: ViewGroup, inflate: (LayoutInflater, ViewGroup, Boolean) -> VB) :
+            this(inflate(LayoutInflater.from(parent.context), parent, false))
+}
+
+inline fun <VB : ViewBinding> BindingViewHolder<VB>.onBinding(crossinline action: VB.(Int) -> Unit) =
+    apply { binding.action(absoluteAdapterPosition) }
+
+inline fun <VB : ViewBinding> BindingViewHolder<VB>.onItemClick(crossinline action: VB.(Int) -> Unit) =
+    apply { itemView.setOnClickListener { binding.action(absoluteAdapterPosition) } }

+ 34 - 0
lib_common/src/main/java/com/kdanmobile/android/common/config/MyPdfBaseModule.kt

@@ -0,0 +1,34 @@
+package com.kdanmobile.android.common.config
+
+import android.app.Application
+import android.content.Context
+
+/**
+ *    @author : hubowen
+ *    @e-mail : hubowen@kdanmobile.com
+ *    @date   : 2020/9/17  8:01 PM
+ *    @description:
+ */
+object MyPdfBaseModule {
+
+    var myApplication: Application? = null
+
+    var isDebug: Boolean = true
+
+    var versionName: String = ""
+
+    /**
+     * 子模块和主模块需要共享全局上下文,故需要在app module初始化时传入
+     */
+    fun init(myApplication_: Application, isDebug_: Boolean, versionName_: String) {
+        if (myApplication == null) {
+            myApplication = myApplication_
+            isDebug = isDebug_
+            versionName = versionName_
+        }
+    }
+
+    fun getAppContext(): Context? {
+        return myApplication?.applicationContext
+    }
+}

+ 45 - 0
lib_common/src/main/java/com/kdanmobile/android/common/extension/ActivityExtensions.kt

@@ -0,0 +1,45 @@
+package com.kdanmobile.android.common.base.extension
+
+import android.content.Context
+import android.content.Intent
+import androidx.appcompat.app.AppCompatActivity
+import com.kdanmobile.android.common.base.view.BaseActivity
+
+
+/**
+ * 移除所有fragment
+ */
+fun BaseActivity.removeAllFragments() {
+    runCatching {
+        supportFragmentManager.beginTransaction().apply {
+            for (fragment in supportFragmentManager.fragments) remove(fragment)
+            commitNow()
+        }
+    }
+}
+
+/**
+ * 跳转界面并关闭当前页面
+ * @param clazz 跳转的activity
+ * @param extraData 携带的数据
+ */
+fun Context.readyGo(clazz: Class<*>, extraData : (Intent) -> Unit = {}) {
+    Intent(this, clazz).apply {
+        extraData.invoke(this)
+        startActivity(this)
+    }
+}
+
+
+/**
+ * 跳转界面并关闭当前页面
+ * @param clazz 跳转的activity
+ * @param extraData 携带的数据
+ */
+fun AppCompatActivity.readyGoThenKill(clazz: Class<*>, extraData: (Intent) -> Unit = {}) {
+    Intent(this, clazz).apply {
+        extraData.invoke(this)
+        startActivity(this)
+    }
+    finish()
+}

+ 314 - 0
lib_common/src/main/java/com/kdanmobile/android/common/extension/AnimExtension.kt

@@ -0,0 +1,314 @@
+package com.kdanmobile.android.common.extension
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.view.View
+import android.view.animation.AccelerateInterpolator
+import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlin.coroutines.resume
+
+/**
+ * @classname:AnimExtension
+ * @author:luozhipeng
+ * @date:2020/10/10 8:28 PM
+ * @description:动画扩展类,采用属性动画
+ */
+
+object AnimExt {
+    const val config_shortAnimTime: Long = 200L
+    const val config_mediumAnimTime: Long = 400L
+    const val config_longAnimTime: Long = 500L
+}
+
+@JvmOverloads
+fun View.showFromTop(duration: Long = AnimExt.config_mediumAnimTime) {
+    if (visibility == View.VISIBLE) return
+
+    if (!isHardwareAccelerated) setLayerType(View.LAYER_TYPE_HARDWARE, null)
+    alpha = 0f
+    visibility = View.VISIBLE
+
+    animate()
+        .alpha(1f)
+        .translationY(0f)
+        .setInterpolator(AccelerateInterpolator())
+        .setDuration(duration)
+        .setListener(object : AnimatorListenerAdapter() {
+            override fun onAnimationEnd(animation: Animator?) {
+                super.onAnimationEnd(animation)
+                animation?.removeListener(this)
+                setLayerType(View.LAYER_TYPE_NONE, null)
+                clearAnimation()
+            }
+        })
+}
+
+@JvmOverloads
+fun View.hideFromTop(duration: Long = AnimExt.config_mediumAnimTime) {
+    if (visibility != View.VISIBLE) return
+
+    if (!isHardwareAccelerated) setLayerType(View.LAYER_TYPE_HARDWARE, null)
+
+    animate()
+        .alpha(0.0f)
+        .translationY(-1.0f * height)
+        .setInterpolator(AccelerateInterpolator())
+        .setDuration(duration)
+        .setListener(object : AnimatorListenerAdapter() {
+            override fun onAnimationEnd(animation: Animator?) {
+                super.onAnimationEnd(animation)
+                animation?.removeListener(this)
+                setLayerType(View.LAYER_TYPE_NONE, null)
+                clearAnimation()
+                visibility = View.GONE
+            }
+        })
+}
+
+@JvmOverloads
+fun View.showFromBottom(duration: Long = AnimExt.config_mediumAnimTime) {
+    if (visibility == View.VISIBLE) return
+
+    if (!isHardwareAccelerated) setLayerType(View.LAYER_TYPE_HARDWARE, null)
+    alpha = 0f
+    visibility = View.VISIBLE
+
+    animate()
+        .alpha(1f)
+        .translationY(0f)
+        .setInterpolator(AccelerateInterpolator())
+        .setDuration(duration)
+        .setListener(object : AnimatorListenerAdapter() {
+            override fun onAnimationEnd(animation: Animator?) {
+                super.onAnimationEnd(animation)
+                animation?.removeListener(this)
+                setLayerType(View.LAYER_TYPE_NONE, null)
+                clearAnimation()
+            }
+        })
+}
+
+@JvmOverloads
+fun View.hideFromBottom(duration: Long = AnimExt.config_mediumAnimTime) {
+    if (visibility != View.VISIBLE) return
+    if (!isHardwareAccelerated) setLayerType(View.LAYER_TYPE_HARDWARE, null)
+
+    animate()
+        .alpha(0.0f)
+        .translationY(height.toFloat())
+        .setInterpolator(AccelerateInterpolator())
+        .setDuration(duration)
+        .setListener(object : AnimatorListenerAdapter() {
+            override fun onAnimationEnd(animation: Animator?) {
+                super.onAnimationEnd(animation)
+                animation?.removeListener(this)
+                setLayerType(View.LAYER_TYPE_NONE, null)
+                clearAnimation()
+                visibility = View.GONE
+            }
+        })
+}
+
+@JvmOverloads
+fun View.showFromLeft(duration: Long = AnimExt.config_mediumAnimTime) {
+    if (visibility == View.VISIBLE) return
+
+    if (!isHardwareAccelerated) setLayerType(View.LAYER_TYPE_HARDWARE, null)
+    alpha = 0f
+    visibility = View.VISIBLE
+
+    animate()
+        .alpha(1f)
+        .translationX(0f)
+        .setInterpolator(AccelerateInterpolator())
+        .setDuration(duration)
+        .setListener(object : AnimatorListenerAdapter() {
+            override fun onAnimationEnd(animation: Animator?) {
+                super.onAnimationEnd(animation)
+                animation?.removeListener(this)
+                setLayerType(View.LAYER_TYPE_NONE, null)
+                clearAnimation()
+            }
+        })
+}
+
+@JvmOverloads
+fun View.hideFromLeft(duration: Long = AnimExt.config_mediumAnimTime) {
+    if (visibility != View.VISIBLE) return
+    if (!isHardwareAccelerated) setLayerType(View.LAYER_TYPE_HARDWARE, null)
+    animate()
+        .alpha(0.0f)
+        .translationXBy(-1.0f * width)
+        .setInterpolator(AccelerateInterpolator())
+        .setDuration(duration)
+        .setListener(object : AnimatorListenerAdapter() {
+            override fun onAnimationEnd(animation: Animator?) {
+                super.onAnimationEnd(animation)
+                animation?.removeListener(this)
+                setLayerType(View.LAYER_TYPE_NONE, null)
+                clearAnimation()
+                visibility = View.GONE
+            }
+        })
+}
+
+@JvmOverloads
+fun View.showFromRight(duration: Long = AnimExt.config_mediumAnimTime) {
+    if (visibility == View.VISIBLE) return
+    if (!isHardwareAccelerated) setLayerType(View.LAYER_TYPE_HARDWARE, null)
+    alpha = 0f
+    visibility = View.VISIBLE
+
+    animate()
+        .alpha(1f)
+        .translationX(0.0f)
+        .setInterpolator(AccelerateInterpolator())
+        .setDuration(duration)
+        .setListener(object : AnimatorListenerAdapter() {
+            override fun onAnimationEnd(animation: Animator?) {
+                super.onAnimationEnd(animation)
+                animation?.removeListener(this)
+                setLayerType(View.LAYER_TYPE_NONE, null)
+                clearAnimation()
+            }
+        })
+}
+
+@JvmOverloads
+fun View.hideFromRight(duration: Long = AnimExt.config_mediumAnimTime, isInVisible: Boolean = false) {
+    if (visibility != View.VISIBLE) return
+    if (!isHardwareAccelerated) setLayerType(View.LAYER_TYPE_HARDWARE, null)
+    animate()
+        .alpha(0.0f)
+        .translationX(width.toFloat())
+        .setInterpolator(AccelerateInterpolator())
+        .setDuration(duration)
+        .setListener(object : AnimatorListenerAdapter() {
+            override fun onAnimationEnd(animation: Animator?) {
+                super.onAnimationEnd(animation)
+                animation?.removeListener(this)
+                setLayerType(View.LAYER_TYPE_NONE, null)
+                clearAnimation()
+                visibility = if (isInVisible) View.INVISIBLE else View.GONE
+            }
+        })
+}
+
+@JvmOverloads
+fun View.alphaGone(duration: Long = AnimExt.config_shortAnimTime, isScaleX: Boolean = false, isScaleY: Boolean = false, endFunc: (() -> Unit)? = null) {
+    if (visibility != View.VISIBLE) return
+    if (!isHardwareAccelerated) setLayerType(View.LAYER_TYPE_HARDWARE, null)
+    animate()
+        .alpha(0f)
+        .setInterpolator(AccelerateInterpolator())
+        .apply {
+            if (isScaleX) scaleX(0f)
+            if (isScaleY) scaleY(0f)
+        }
+        .setDuration(duration)
+        .setListener(object : AnimatorListenerAdapter() {
+            override fun onAnimationEnd(animation: Animator?) {
+                super.onAnimationEnd(animation)
+                animation?.removeListener(this)
+                setLayerType(View.LAYER_TYPE_NONE, null)
+                clearAnimation()
+                visibility = View.GONE
+                endFunc?.invoke()
+            }
+        })
+}
+
+@JvmOverloads
+fun View.alphaVisible(duration: Long = AnimExt.config_shortAnimTime, isScaleX: Boolean = false, isScaleY: Boolean = false, endFunc: (() -> Unit)? = null) {
+    if (visibility == View.VISIBLE) return
+    if (!isHardwareAccelerated) setLayerType(View.LAYER_TYPE_HARDWARE, null)
+    alpha = 0f
+    visibility = View.VISIBLE
+    animate()
+        .alpha(1f)
+        .setInterpolator(AccelerateInterpolator())
+        .apply {
+            if (isScaleX) scaleX(1f)
+            if (isScaleY) scaleY(1f)
+        }
+        .setDuration(duration)
+        .setListener(object : AnimatorListenerAdapter() {
+            override fun onAnimationEnd(animation: Animator?) {
+                super.onAnimationEnd(animation)
+                animation?.removeListener(this)
+                setLayerType(View.LAYER_TYPE_NONE, null)
+                clearAnimation()
+                endFunc?.invoke()
+            }
+        })
+}
+
+@JvmOverloads
+fun View.switchVisible(
+    isVisible: Boolean = true,
+    duration: Long = if (isVisible) AnimExt.config_shortAnimTime else AnimExt.config_shortAnimTime,
+    isScaleX: Boolean = false,
+    isScaleY: Boolean = false,
+    endFunc: (() -> Unit)? = null
+) {
+    when {
+        isVisible -> alphaVisible(duration, isScaleX, isScaleY, endFunc)
+        else -> alphaGone(duration, isScaleX, isScaleY, endFunc)
+    }
+}
+
+@JvmOverloads
+fun View.rotate45(isRotation: Boolean = true, duration: Long = AnimExt.config_mediumAnimTime, endFunc: (() -> Unit)? = null) {
+    if (!isHardwareAccelerated) setLayerType(View.LAYER_TYPE_HARDWARE, null)
+    animate()
+        .rotation(if (isRotation) 45f else 0f)
+        .setInterpolator(AccelerateInterpolator())
+        .setDuration(duration)
+        .setListener(object : AnimatorListenerAdapter() {
+            override fun onAnimationEnd(animation: Animator?) {
+                super.onAnimationEnd(animation)
+                animation?.removeListener(this)
+                setLayerType(View.LAYER_TYPE_NONE, null)
+                clearAnimation()
+                endFunc?.invoke()
+            }
+        })
+}
+
+/****** Animator 执行完成 ******/
+suspend fun Animator.awaitAnimEnd() = suspendCancellableCoroutine<Unit> { continuation ->
+    val listener = object : AnimatorListenerAdapter() {
+        private var endedSuccessfully = true
+
+        override fun onAnimationCancel(animation: Animator?) {
+            super.onAnimationCancel(animation)
+            //动画已经被取消,修改是否成功结束的标志
+            endedSuccessfully = false
+        }
+
+        override fun onAnimationEnd(animation: Animator?) {
+            super.onAnimationEnd(animation)
+            //为了在协程恢复后的不发生泄漏,需要确保移除监听
+            animation?.removeListener(this)
+            if (!continuation.isActive) return
+            when {
+                endedSuccessfully -> {
+                    //动画正常结束,恢复协程
+                    continuation.resume(Unit)
+                }
+                else -> {
+                    //动画已经取消,取消协程
+                    continuation.cancel()
+                }
+            }
+        }
+    }
+
+    continuation.invokeOnCancellation {
+        //取消动画,会在onAnimationEnd回调中移除监听
+        cancel()
+    }
+
+    //添加监听
+    addListener(listener)
+}

+ 51 - 0
lib_common/src/main/java/com/kdanmobile/android/common/extension/ContextExtensions.kt

@@ -0,0 +1,51 @@
+package com.kdanmobile.android.common.extension
+
+import android.content.Context
+import android.content.res.Configuration
+import android.os.Build
+import android.view.View
+import android.view.ViewTreeObserver
+import androidx.annotation.ColorRes
+import androidx.annotation.RequiresApi
+import androidx.core.content.ContextCompat
+
+/**
+ * @classname:ContextExtensions
+ * @auther: LiuXiaoLong
+ * @date: 2022/7/27
+ * description:
+ */
+
+
+/**
+ * 获取资源中的颜色
+ */
+fun Context.getColor(@ColorRes colorResId : Int) : Int{
+    return ContextCompat.getColor(this, colorResId)
+}
+
+
+/****** 获取View onGlobalLayout Start ******/
+/****** Register a callback to be invoked when the global layout state or the visibility of views within the view tree changes ******/
+inline fun View.onGlobalLayout(crossinline callback: () -> Unit) = with(viewTreeObserver) {
+    addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
+        @RequiresApi(Build.VERSION_CODES.JELLY_BEAN)
+        override fun onGlobalLayout() {
+            removeOnGlobalLayoutListener(this)
+            callback()
+        }
+    })
+}
+
+/****** Register a callback to be invoked after the view is measured ******/
+inline fun View.afterMeasured(crossinline callback: View.() -> Unit) {
+    viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
+        @RequiresApi(Build.VERSION_CODES.JELLY_BEAN)
+        override fun onGlobalLayout() {
+            if (measuredWidth > 0 && measuredHeight > 0) {
+                viewTreeObserver.removeOnGlobalLayoutListener(this)
+                callback()
+            }
+        }
+    })
+}

+ 26 - 0
lib_common/src/main/java/com/kdanmobile/android/common/extension/DialogFragmentExtenstions.kt

@@ -0,0 +1,26 @@
+package com.kdanmobile.android.common.extension
+
+import androidx.fragment.app.DialogFragment
+import androidx.fragment.app.FragmentManager
+import com.kdanmobile.android.common.utils.ReflectionUtils
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/7/27
+ * description:
+ */
+/****** 显示文件信息对话框 End ******/
+fun DialogFragment.showAllowingStateLoss(fm: FragmentManager, tag: String) {
+    try {
+        /****** 通过反射修改父类的属性值:mDismissed = false  */
+        ReflectionUtils.setFieldValue(this, "mDismissed", false)
+        /****** 通过反射修改父类的属性值:mShownByMe = true  */
+        ReflectionUtils.setFieldValue(this, "mShownByMe", true)
+        fm.beginTransaction().add(this, tag).commitAllowingStateLoss()
+        /****** 解决findFragmentByTag为NULL问题  */
+        fm.executePendingTransactions()
+    } catch (e: Exception) {
+        e.printStackTrace()
+    }
+}

+ 207 - 0
lib_common/src/main/java/com/kdanmobile/android/common/extension/FileExtensions.kt

@@ -0,0 +1,207 @@
+package com.kdanmobile.android.common.extension
+
+import android.content.Context
+import android.net.Uri
+import android.os.Build
+import android.os.Environment
+import android.os.StatFs
+import androidx.core.content.FileProvider
+import java.io.File
+import java.io.FileFilter
+import java.io.FileOutputStream
+import java.security.DigestInputStream
+import java.security.MessageDigest
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/7/27
+ * description:
+ */
+
+fun File.getUri(context: Context) : Uri?{
+    return try {
+        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M){
+            Uri.fromFile(this)
+        }else{
+            FileProvider.getUriForFile(context, context.packageName +".fileprovider", this)
+        }
+    }catch (e : Exception){
+        null
+    }
+}
+
+/**
+ * 路径转文件
+ */
+fun String?.toFile() : File? = this?.let { File(it) }
+
+/**
+ * 文件转输出流
+ */
+fun File.outputStream(append : Boolean = false) : FileOutputStream{
+    return FileOutputStream(this, append)
+}
+
+/**
+ * 重命名
+ */
+fun File.rename(file: File, destName : String) : Boolean{
+    if (file.exists().not()){
+        return false
+    }
+
+    if (destName.isEmpty()){
+        return false
+    }
+
+    if (destName.equals(file.name)){
+        return true
+    }
+
+    val destFile = File(file.parent + File.separator + destName)
+
+    return !destFile.exists() && file.renameTo(destFile)
+}
+
+/**
+ * 如果文件存在则 文件名(+1).后缀
+ */
+fun File.renameSuffix() : File{
+    if (exists()){
+        var destFile : File
+        var index = 1
+        do {
+            destFile = File(parentFile, "${this.nameWithoutExtension}(${index++}).${this.extension}")
+        }while (destFile.exists())
+        return destFile
+    }
+    return this
+}
+
+
+
+/**
+ * 移动文件
+ */
+fun File.move(targetFile: File) : Boolean{
+    return copyTo(targetFile) != null && deleteRecursively()
+}
+
+
+/**
+ * 删除文件夹中满足条件的文件或文件夹
+ */
+fun File.deleteFileWithFilter(filter: FileFilter) : Boolean{
+    val files = listFiles(filter)
+    files?.forEach {
+        it.deleteRecursively()
+    }
+    return true
+}
+
+/**
+ * 列出文件夹下满足条件的所有文件
+ * @param recursively 递归所有文件夹
+ * @param accept 满足的条件
+ */
+fun File.listFilesInDirWithFilter(recursively: Boolean = false, accept: (File) -> Boolean) : List<File>{
+    return if (recursively){
+        this.walkTopDown().asSequence().filter {
+            accept.invoke(it)
+        }.toList()
+    }else{
+        listFiles { file ->
+            accept.invoke(file)
+        }?.toList()?: emptyList()
+    }
+}
+
+/**
+ * 列出文件夹下指定格式的所有文件。
+ * @param recursively 是否递归所有文件夹进行查找
+ */
+fun File.listFilesInDirWithExtension(recursively: Boolean, vararg fileExtensions : String) : List<File>{
+    return listFilesInDirWithFilter(recursively = recursively, accept = {
+        fileExtensions.contains(it.extension)
+    })
+}
+
+/**
+ * 文件的行数
+ */
+fun File.lines() : Int{
+    return readLines().size
+}
+
+
+/**
+ * 获取文件夹大小
+ */
+fun File.getDirLength() : Long{
+    var length = 0L
+    walkTopDown().asSequence()
+        .forEach {
+            if (it.isDirectory.not()) {
+                length += it.length()
+            }
+        }
+    return length
+}
+
+fun File.lengthToFitMemorySize() : String{
+    val byteNum = getDirLength()
+    return if (byteNum < 0) {
+        "0"
+    } else if (byteNum < 1024) {
+        String.format("%.3fB", byteNum.toDouble())
+    } else if (byteNum < 1048576) {
+        String.format("%.3fKB", byteNum.toDouble() / 1024)
+    } else if (byteNum < 1073741824) {
+        String.format("%.3fMB", byteNum.toDouble() / 1048576)
+    } else {
+        String.format("%.3fGB", byteNum.toDouble() / 1073741824)
+    }
+}
+
+
+/**
+ * 获取MD5 字节数组
+ */
+fun File.getMD5Bytes() : ByteArray {
+    var md =  MessageDigest.getInstance("MD5")
+
+    val dis = DigestInputStream(inputStream(), md)
+    return try {
+        md = dis.messageDigest
+        md.digest()
+    }catch (e : Exception){
+        byteArrayOf()
+    }finally {
+        dis.close()
+    }
+}
+
+/**
+ * 获取md5 字符串
+ */
+fun File.getMD5() : String{
+    return getMD5Bytes().toHex()
+}
+
+/**
+ * 获取SD卡剩余存储空间
+ */
+fun File.getAvailableExternalMemorySize() : Long{
+    if (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED){
+        val stat = StatFs(Environment.getExternalStorageDirectory().path)
+        return stat.availableBlocksLong * stat.blockSizeLong
+    }else{
+        return -1
+    }
+}
+
+
+
+
+

+ 17 - 0
lib_common/src/main/java/com/kdanmobile/android/common/extension/IntentExtensions.kt

@@ -0,0 +1,17 @@
+package com.kdanmobile.android.common.extension
+
+import android.os.Bundle
+import android.os.Parcelable
+import java.io.Serializable
+import java.util.ArrayList
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/7/29
+ * description:
+ */
+
+
+
+

+ 11 - 0
lib_common/src/main/java/com/kdanmobile/android/common/extension/NetExtensions.kt

@@ -0,0 +1,11 @@
+package com.kdanmobile.android.common.extension
+
+import android.content.Context
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/7/27
+ * description:
+ */
+

+ 72 - 0
lib_common/src/main/java/com/kdanmobile/android/common/extension/RecycleViewExtensions.kt

@@ -0,0 +1,72 @@
+package com.kdanmobile.android.common.extension
+
+import android.content.Context
+import androidx.recyclerview.widget.GridLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.kdanmobile.android.common.utils.decoration.PaddingItemDecoration
+
+/**
+ * @classname: RecyclerViewExt
+ * @auther: LiuXiaoLong
+ * @date: 2022-07-27 PM
+ * description: RecyclerView相关扩展
+ */
+
+/****** Add item padding ******/
+fun RecyclerView.itemPadding(padding: Int) {
+    itemPadding(padding, padding, padding, padding)
+}
+
+/****** Add item padding for top, bottom, left, right ******/
+fun RecyclerView.itemPadding(top: Int, bottom: Int, left: Int = 0, right: Int = 0) {
+    mutableListOf<PaddingItemDecoration>()
+        .also { list ->
+            for (i in 0 until itemDecorationCount) {
+                when (val item = getItemDecorationAt(i)) {
+                    is PaddingItemDecoration -> list.add(item)
+                }
+            }
+        }
+        .forEach { removeItemDecoration(it) }
+
+    addItemDecoration(PaddingItemDecoration(top, bottom, left, right))
+}
+
+
+
+fun RecyclerView.onItemAdaptiveOfGrid(itemWidthDimen: Int, paddingDimen: Int = 5): Int {
+    val standardPadding = context.px2dip(resources.getDimension(paddingDimen))
+    val itemViewW = resources.getDimension(itemWidthDimen)
+    val screenW = context.getScreenWidth()
+
+    var column = (screenW / itemViewW).toInt().run {
+        when {
+            this >= 3 -> this
+            else -> 3
+        }
+    }
+
+    val padding = when (column) {
+        1 -> context.px2dip( (screenW / column - itemViewW) / 2)
+        else -> context.px2dip((screenW / column - itemViewW) / 2).run {
+            when {
+                (this >= standardPadding) -> this
+                else -> {
+                    column -= 1
+                    context.px2dip( (screenW / column - itemViewW) / 2)
+                }
+            }
+        }
+    }
+
+    layoutManager = WrapContentGridLayoutManager(context,column)
+    itemPadding(padding / 2, padding / 2, padding, padding)
+    return padding
+}
+
+
+class WrapContentGridLayoutManager constructor(context: Context?, column: Int) : GridLayoutManager(context, column) {
+    override fun supportsPredictiveItemAnimations(): Boolean {
+        return false
+    }
+}

+ 387 - 0
lib_common/src/main/java/com/kdanmobile/android/common/extension/ScreenExtensions.kt

@@ -0,0 +1,387 @@
+package com.kdanmobile.android.common.extension
+
+import android.app.Activity
+import android.app.KeyguardManager
+import android.content.Context
+import android.content.pm.ActivityInfo
+import android.content.res.Configuration
+import android.graphics.Bitmap
+import android.graphics.Color
+import android.os.Build
+import android.util.DisplayMetrics
+import android.view.Surface
+import android.view.View
+import android.view.WindowInsets
+import android.view.WindowInsetsAnimation
+import androidx.annotation.ColorInt
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowCompat
+import androidx.core.view.WindowInsetsCompat
+import androidx.core.view.WindowInsetsControllerCompat
+import kotlin.math.max
+import kotlin.math.min
+import kotlin.math.sqrt
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/7/29
+ * description: 屏幕相关的扩展类
+ */
+
+/**
+ * 屏幕宽度
+ */
+fun Context?.getScreenWidth(): Int = this?.resources?.displayMetrics?.widthPixels ?: 0
+
+/**
+ * 屏幕高度
+ */
+fun Context?.getScreenHeight(): Int = this?.resources?.displayMetrics?.heightPixels ?: 0
+
+/**
+ * 获取屏幕密度
+ */
+fun Context?.getDensity(): Float = this?.resources?.displayMetrics?.density ?: 0F
+
+/**
+ * 获取屏幕密度Dpi
+ */
+fun Context?.getDensityDpi(): Int = this?.resources?.displayMetrics?.densityDpi ?: 0
+
+/**
+ * 屏幕X轴方向上的长度(dp)
+ */
+fun Context.getXDp(): Double {
+    return (getScreenWidth().div(getDensity())).toDouble()
+}
+
+/**
+ * 屏幕Y轴方向的长度(dp)
+ */
+fun Context.getYDp(): Double {
+    return (getScreenHeight().div(getDensity())).toDouble()
+}
+
+/**
+ * @param :[context, dpValue]
+ * @return : int
+ * @methodName :dip2px created by luozhipeng on 5/12/17 13:57.
+ * @description :根据手机的分辨率从 dp 的单位 转成为 px(像素)
+ */
+fun Context.dp2px(dpValue: Float): Int {
+    val scale = getDensity()
+    return (dpValue * scale + 0.5f).toInt()
+}
+
+/**
+ * @param :[context, pxValue]
+ * @return : int
+ * @methodName :px2dip created by luozhipeng on 5/12/17 14:00.
+ * @description :根据手机的分辨率从 px(像素) 的单位 转成为 dp
+ */
+fun Context.px2dip(pxValue: Float): Int {
+    val scale = getDensity()
+    return (pxValue / scale + 0.5f).toInt()
+}
+
+/**
+ * 获取屏幕最小边
+ */
+fun Context.screenMinSide(): Int = min(getScreenWidth(), getScreenHeight())
+
+
+/**
+ * 获取屏幕最大边
+ */
+fun Context.screenMaxSide(): Int = max(getScreenWidth(), getScreenHeight())
+
+/**
+ * 是否是平板设备
+ */
+fun Context.isPadDevice(): Boolean {
+    return resources.configuration.screenLayout and Configuration.SCREENLAYOUT_SIZE_MASK >= Configuration.SCREENLAYOUT_SIZE_LARGE
+}
+
+/**
+ * @param :[context]
+ * @return : double
+ * @methodName :getScreenDimension created by luozhipeng on 5/12/17 14:05.
+ * @description :获取屏幕的尺寸(英寸)
+ */
+fun Context.getScreenDimension(): Double {
+    // 得到屏幕的宽(像素)
+    val screenX = getScreenWidth()
+    // 得到屏幕的高(像素)
+    val screenY = getScreenHeight()
+    // 每英寸的像素点
+    val dpi = getDensityDpi()
+    // 得到屏幕的宽(英寸)
+    val a = screenX.toFloat() / dpi
+    // 得到屏幕的高(英寸)
+    val b = screenY.toFloat() / dpi
+    // 勾股定理
+    return sqrt((a * a + b * b).toDouble())
+}
+
+/**
+ * 锁定屏幕为竖屏
+ */
+fun Activity.lockScreenPortrait() {
+    requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
+}
+
+/**
+ * 锁定为横屏
+ */
+fun Activity.lockScreenLandSpace(activity: Activity) {
+    activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
+}
+
+/**
+ * 是否是横屏
+ */
+fun Activity.isLandscape(): Boolean {
+    val display = windowManager?.defaultDisplay
+    when (display?.rotation) {
+        Surface.ROTATION_0, Surface.ROTATION_180 -> return false
+        Surface.ROTATION_90, Surface.ROTATION_270 -> return true
+    }
+    return false
+}
+
+/**
+ * @param :[context]
+ * @return : boolean
+ * @methodName :isScreenLock created by luozhipeng on 15/12/17 21:46.
+ * @description :判断是否锁屏
+ */
+fun Context.isScreenLock(): Boolean {
+    val km = applicationContext.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
+    return km.inKeyguardRestrictedInputMode()
+}
+
+
+fun Activity.getWindowInsetsController(): WindowInsetsControllerCompat {
+    return WindowCompat.getInsetsController(window, findViewById(android.R.id.content))
+}
+
+/**
+ * 设置沉浸式状态栏
+ * @param isTransparent 是否是透明状态栏 true:透明, false : 不透明
+ */
+fun Activity.setTransparentStatusBar(
+    activity: Activity,
+    isTransparent: Boolean = true,
+    blackStatusBar: Boolean = false,
+    statusBarColor: Int = Color.TRANSPARENT,
+) {
+    WindowCompat.setDecorFitsSystemWindows(activity.window, isTransparent.not())
+    activity.window.statusBarColor = statusBarColor
+
+    getWindowInsetsController().let {
+        it.isAppearanceLightStatusBars = blackStatusBar
+        it.isAppearanceLightNavigationBars = blackStatusBar
+    }
+}
+
+/**
+ * 显示系统UI(状态栏、导航栏)
+ */
+fun Activity.showSystemUI() {
+    WindowCompat.setDecorFitsSystemWindows(window, true)
+    getWindowInsetsController().show(WindowInsetsCompat.Type.systemBars())
+}
+
+/**
+ * 隐藏系统UI(状态栏、导航栏)
+ */
+fun Activity.hideSystemUI() {
+    WindowCompat.setDecorFitsSystemWindows(window, false)
+    getWindowInsetsController().let {
+        it.hide(WindowInsetsCompat.Type.systemBars())
+        it.systemBarsBehavior =
+            WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
+    }
+}
+
+/**
+ * 设置状态栏
+ * @param statusBarColor 状态栏颜色
+ * @param blackStatusBar true:状态栏文字黑色, false:状态栏文字白色
+ */
+fun Activity.setStatusBar(@ColorInt statusBarColor: Int, blackStatusBar: Boolean = false) {
+    window.statusBarColor = statusBarColor
+    getWindowInsetsController().let {
+        it.show(WindowInsetsCompat.Type.statusBars())
+        it.isAppearanceLightStatusBars = blackStatusBar
+    }
+}
+
+/**
+ * 隐藏状态栏
+ */
+fun Activity.hideStatusBar() {
+    getWindowInsetsController().hide(WindowInsetsCompat.Type.statusBars())
+}
+
+
+/**
+ * 显示导航栏
+ * @param iconIsBlack icon颜色:true:黑色, false:白色
+ */
+fun Activity.showNavigationBar(iconIsBlack: Boolean = true) {
+    getWindowInsetsController().let {
+        it.show(WindowInsetsCompat.Type.navigationBars())
+        it.isAppearanceLightNavigationBars = iconIsBlack
+    }
+}
+
+/**
+ * 移除导航栏
+ */
+fun Activity.hideNavigationBar(activity: Activity) {
+    getWindowInsetsController().let {
+        it.hide(WindowInsetsCompat.Type.navigationBars())
+        it.systemBarsBehavior =
+            WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
+    }
+}
+
+private fun Activity.getWindowInsetsCompat(): WindowInsetsCompat? {
+    return ViewCompat.getRootWindowInsets(findViewById(android.R.id.content))
+}
+
+/**
+ * 判断导航栏是否显示
+ */
+fun Activity.hasNavigationBars(): Boolean {
+    val windowInsetsCompat: WindowInsetsCompat = getWindowInsetsCompat() ?: return false
+
+    return windowInsetsCompat.isVisible(WindowInsetsCompat.Type.navigationBars())
+            && windowInsetsCompat.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom > 0
+}
+
+/**
+ * 获取导航栏高度
+ */
+fun Activity.getNavigationBarsHeight(): Int {
+    val windowInsetsCompat = getWindowInsetsCompat() ?: return 0
+    return windowInsetsCompat.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom
+}
+
+/**
+ * 获取状态栏高度
+ */
+fun Activity.getStatusBarHeight(): Int {
+    val windowInsetsCompat = getWindowInsetsCompat() ?: return 0
+    return windowInsetsCompat.getInsets(WindowInsetsCompat.Type.statusBars()).top
+}
+
+/**
+ * 隐藏键盘
+ */
+fun Activity.hideSoftKeyboard() {
+    getWindowInsetsController().hide(WindowInsetsCompat.Type.ime())
+}
+
+/**
+ * 显示键盘
+ */
+fun Activity.showSoftKeyboard() {
+    getWindowInsetsController().show(WindowInsetsCompat.Type.ime())
+}
+
+/**
+ * 添加键盘高度监听
+ */
+fun addKeyboardHeightChangeCallBack(view: View, onAction: (height: Int) -> Unit) {
+    var posBottom: Int
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+        val cb = object : WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
+            override fun onProgress(insets: WindowInsets, runningAnimations: MutableList<WindowInsetsAnimation>): WindowInsets {
+                posBottom = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom +
+                        insets.getInsets(WindowInsetsCompat.Type.systemBars()).bottom
+                onAction.invoke(posBottom)
+                return insets
+            }
+        }
+        view.setWindowInsetsAnimationCallback(cb)
+    } else {
+
+        ViewCompat.setOnApplyWindowInsetsListener(view) { _, insets ->
+            posBottom = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom +
+                    insets.getInsets(WindowInsetsCompat.Type.systemBars()).bottom
+            onAction.invoke(posBottom)
+            insets
+        }
+    }
+}
+
+/**
+ * 截屏
+ */
+fun Activity.screenShot(isDeleteStatusBar: Boolean = false): Bitmap {
+    val decorView = window.decorView
+    decorView.isDrawingCacheEnabled = true
+    decorView.buildDrawingCache()
+    val bmp = decorView.drawingCache
+    val dm = DisplayMetrics()
+    windowManager.defaultDisplay.getMetrics(dm)
+    val ret: Bitmap = if (isDeleteStatusBar) {
+        val resourceId = resources.getIdentifier("status_bar_height", "dimen", "android")
+        val statusBarHeight = resources.getDimensionPixelSize(resourceId)
+        Bitmap.createBitmap(bmp, 0, statusBarHeight, dm.widthPixels, dm.heightPixels - statusBarHeight)
+    } else {
+        Bitmap.createBitmap(bmp, 0, 0, dm.widthPixels, dm.heightPixels)
+    }
+    decorView.destroyDrawingCache()
+    return ret
+}
+
+/**
+ * 屏幕锁定
+ * @param isLock 是否锁定方向
+ * @param isSystemOrientation 是否跟随系统方向设定
+ */
+fun Activity.lockScreenOrientation(isLock: Boolean, isSystemOrientation: Boolean) {
+    try {
+
+        if (isLock) {
+            /****** 锁定 ******/
+            val display = windowManager.defaultDisplay
+            val rotation = display.rotation
+            when (rotation) {
+                Surface.ROTATION_0 -> requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
+                Surface.ROTATION_90 -> requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
+                Surface.ROTATION_180 -> requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT
+                Surface.ROTATION_270 -> requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
+                else -> requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
+            }
+
+        } else {
+            if (isSystemOrientation) {
+                /****** 跟随系统的方向设定 ******/
+                requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
+            } else {
+                /****** 根据手机陀螺仪来设定方向 ******/
+                requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR
+            }
+        }
+
+    } catch (e: Exception) {
+        e.printStackTrace()
+    }
+}
+
+/**
+ * 是否为RTL
+ */
+fun View.isLayoutRTL() : Boolean{
+    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1){
+        View.LAYOUT_DIRECTION_RTL == layoutDirection
+    }else{
+        false
+    }
+}
+

+ 34 - 0
lib_common/src/main/java/com/kdanmobile/android/common/extension/StringExtensions.kt

@@ -0,0 +1,34 @@
+package com.kdanmobile.android.common.extension
+
+import android.os.Build
+import android.text.Html
+import android.text.Spanned
+import java.util.regex.Pattern
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/7/29
+ * description:
+ */
+
+fun ByteArray.toHex() : String{
+    return joinToString(separator = ""){eachByte->
+        "%02x".format(eachByte)
+    }.uppercase()
+}
+
+
+fun CharSequence?.isEmail(): Boolean {
+    fun isMatch(regex: String?, input: CharSequence?): Boolean {
+        return input != null && input.isNotEmpty() && Pattern.matches(regex, input)
+    }
+    return isMatch("^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$", this)
+}
+
+fun String.fromHtml(): Spanned? {
+    return when {
+        Build.VERSION.SDK_INT >= Build.VERSION_CODES.N -> Html.fromHtml(this, Html.FROM_HTML_MODE_LEGACY)
+        else -> Html.fromHtml(this)
+    }
+}

+ 71 - 0
lib_common/src/main/java/com/kdanmobile/android/common/extension/SystemExtensions.kt

@@ -0,0 +1,71 @@
+package com.kdanmobile.android.common.extension
+
+import android.content.Context
+import android.content.res.Configuration
+import android.media.AudioManager
+import android.os.Build
+import android.view.View
+import android.view.inputmethod.InputMethodManager
+
+/**
+ * @classname:SystemExtensions
+ * @auther: LiuXiaoLong
+ * @date: 2022/7/27
+ * description: 系统相关的一些扩展
+ */
+
+
+/**
+ * 判断是否为暗黑模式
+ */
+fun Context.isNightMode(): Boolean = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
+
+
+/**
+ * 展示KeyBoard
+ */
+fun View.showKeyboard() {
+    postDelayed({
+        isFocusable = true
+        isFocusableInTouchMode = true
+        requestFocus()
+        val imm = context.applicationContext.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
+        imm.showSoftInput(this, InputMethodManager.SHOW_FORCED)
+    }, 300)
+}
+
+/**
+ * 隐藏KeyBoard
+ */
+fun View.hideKeyboard(): Boolean {
+    context.runCatching {
+        val inputMethodManager = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
+        return inputMethodManager.hideSoftInputFromWindow(windowToken, 0)
+    }
+    return false
+}
+
+
+
+/****** 设定是否静音 ******/
+fun Context.isSysVolumeMute(isMute: Boolean = false) {
+    (getSystemService(Context.AUDIO_SERVICE) as AudioManager?)?.let {
+        when {
+            isMute -> {//静音
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+                    it.adjustStreamVolume(AudioManager.STREAM_MUSIC, AudioManager.ADJUST_MUTE, AudioManager.FLAG_VIBRATE)
+                } else {
+                    it.setStreamMute(AudioManager.STREAM_MUSIC, true)
+                }
+            }
+            else -> {//关闭静音
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+                    it.adjustStreamVolume(AudioManager.STREAM_MUSIC, AudioManager.ADJUST_UNMUTE, AudioManager.FLAG_VIBRATE)
+                } else {
+                    it.setStreamMute(AudioManager.STREAM_MUSIC, false)
+                }
+            }
+        }
+    }
+}
+

+ 16 - 0
lib_common/src/main/java/com/kdanmobile/android/common/extension/ViewBindingExtension.kt

@@ -0,0 +1,16 @@
+package com.kdanmobile.android.common.extension
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.viewbinding.ViewBinding
+import com.kdanmobile.android.common.base.view.BaseActivity
+
+fun <VB : ViewBinding> BaseActivity.activityBinding(inflate: (LayoutInflater) -> VB) = lazy {
+    inflate(layoutInflater).also {
+        setContentView(it.root)
+    }
+}
+
+fun <VB : ViewBinding> ViewGroup.viewGroupBinding(inflate: (LayoutInflater, ViewGroup?, Boolean) -> VB, attachToParent: Boolean = true) = lazy {
+    inflate(LayoutInflater.from(context), if (attachToParent) this else null, attachToParent)
+}

+ 127 - 0
lib_common/src/main/java/com/kdanmobile/android/common/extension/ViewExtensions.kt

@@ -0,0 +1,127 @@
+package com.kdanmobile.android.common.extension
+
+import android.content.Context
+import android.view.View
+import androidx.annotation.DrawableRes
+import androidx.core.content.ContextCompat
+import androidx.lifecycle.findViewTreeLifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+
+/**
+ * @classname:ViewExtensions
+ * @auther: LiuXiaoLong
+ * @date: 2022/7/27
+ * description: view相关扩展
+ */
+
+
+/****** 获取Drawable Start ******/
+fun Context.drawableByResId(@DrawableRes id: Int) = ContextCompat.getDrawable(this, id)
+/****** 获取Drawable  End ******/
+
+fun Context.setViewsAlpha(alpha : Float, vararg views: View){
+    for (view in views) {
+        view.alpha = alpha
+    }
+}
+
+/****** View 点击事件添加扩展 Start ******/
+fun Context.setViewsClick(listener: View.OnClickListener, vararg views: View) {
+    views.forEach {
+        it.setOnClickListener(listener)
+    }
+}
+
+fun <T : View> Context.setViewsClickWithTrigger(block: (T) -> Unit, vararg views: View) {
+    views.forEach {
+        it.clickWithTrigger { v ->
+            block(v as T)
+        }
+    }
+}
+
+fun <T : View> Context.setViewsClickAlphaWithTrigger(block: (T) -> Unit, vararg views: View) {
+    views.forEach {
+        it.clickAlphaWithTrigger { v ->
+            block(v as T)
+        }
+    }
+}
+
+fun <T : View> T.clickWithTrigger(block: (T) -> Unit) {
+    setOnClickListener {
+        if (clickEnable()) {
+            block(it as T)
+        }
+    }
+}
+
+@JvmOverloads
+fun <T : View> T.clickAlphaWithTrigger(time: Long = 500, block: (T) -> Unit) {
+    triggerDelay = time
+    setOnClickListener {
+        try {
+            it.findViewTreeLifecycleOwner()?.lifecycleScope?.launch {
+                if (clickEnable()) {
+                    it.alpha = 0.6f
+                    delay(150)
+                    it.alpha = 1.0f
+                    block(it as T)
+                }
+            }?:block(it as T)
+        } catch (e: Exception) {
+
+        }
+    }
+}
+
+fun <T : View> T.longClickAlphaWithTrigger(time: Long = 600, block: (T) -> Unit) {
+    triggerDelay = time
+    setOnLongClickListener {
+        try {
+            it.findViewTreeLifecycleOwner()?.lifecycleScope?.launch {
+                if (clickEnable()) {
+                    it.alpha = 0.6f
+                    delay(150)
+                    it.alpha = 1.0f
+                    block(it as T)
+                }
+            }?:block(it as T)
+        } catch (e: Exception) {
+
+        }
+        true
+    }
+}
+/****** View 点击事件添加扩展 End ******/
+
+/****** 防重点击相关扩展属性 Start ******/
+private var <T : View> T.triggerLastTime: Long
+    get() = if (getTag(1123460103) != null) (getTag(1123460103) as Long) else 0
+    set(value) {
+        setTag(1123460103, value)
+    }
+
+private var <T : View> T.triggerDelay: Long
+    get() = if (getTag(1123461123) != null) (getTag(1123461123) as Long) else -1
+    set(value) {
+        setTag(1123461123, value)
+    }
+
+fun <T : View> T.withTrigger(delay: Long = 600): T {
+    triggerDelay = delay
+    return this
+}
+
+private fun <T : View> T.clickEnable(): Boolean {
+    var flag = false
+    val currentClickTime = System.currentTimeMillis()
+    if (currentClickTime - triggerLastTime > triggerDelay) {
+        flag = true
+    }
+    triggerLastTime = currentClickTime
+    return flag
+}
+/****** 防重点击相关扩展属性 End ******/

+ 50 - 0
lib_common/src/main/java/com/kdanmobile/android/common/manager/AppStatusManager.kt

@@ -0,0 +1,50 @@
+package com.kdanmobile.android.common.manager
+
+import android.app.Activity
+import android.app.Application.ActivityLifecycleCallbacks
+import android.os.Bundle
+import com.kdanmobile.android.common.config.MyPdfBaseModule
+
+
+/**
+ * @author: hubowen
+ * @date: 2021/9/23
+ * @description:
+ */
+object AppStatusConstant {
+    const val STATUS_FORCE_KILLED = -1 //应用放在后台被强杀了
+    const val STATUS_NORMAL = 1 //APP正常态
+}
+
+object AppStatusManager : ActivityLifecycleCallbacks {
+    //默认被初始化状态,被系统回收(强杀)状态
+    var appStatus = AppStatusConstant.STATUS_FORCE_KILLED
+
+    //是否前台
+    var isForGround = false
+
+    //Activity运行个数
+    private var activeCount = 0
+
+    init {
+        MyPdfBaseModule.myApplication?.registerActivityLifecycleCallbacks(this)
+    }
+
+    override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {}
+    override fun onActivityStarted(activity: Activity) {
+        activeCount++
+    }
+
+    override fun onActivityResumed(activity: Activity) {
+        isForGround = true
+    }
+
+    override fun onActivityPaused(activity: Activity) {}
+    override fun onActivityStopped(activity: Activity) {
+        activeCount--
+        if (activeCount == 0) isForGround = false
+    }
+
+    override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
+    override fun onActivityDestroyed(activity: Activity) {}
+}

+ 107 - 0
lib_common/src/main/java/com/kdanmobile/android/common/manager/GoogleAnalyticsManager.kt

@@ -0,0 +1,107 @@
+//package com.kdan.pdfbaselibrary.base.manager
+//
+//import android.content.Context
+//import com.google.android.gms.analytics.GoogleAnalytics
+//import com.google.android.gms.analytics.HitBuilders.ScreenViewBuilder
+//import com.google.android.gms.analytics.Tracker
+//import com.kdan.pdfbaselibrary.R
+//import com.kdan.pdfbaselibrary.config.MyPdfBaseModule
+//
+///**
+// *    @author : HoboWen
+// *    @date   : 2021/5/20  3:43 下午
+// *    @description:
+// */
+//object GoogleAnalyticsManager {
+//    private var mTracker: Tracker? = null
+//    private var mGoogleTracker: Tracker? = null
+//
+//    @Synchronized
+//    fun onInitTracks(context: Context?) {
+//        context?.apply {
+//            val analytics = GoogleAnalytics.getInstance(this)
+//            /****** DEBUG模式下不发送GA数据  */
+//            analytics.setDryRun(MyPdfBaseModule.isDebug)
+//
+//            /*使用多个跟踪器,向不同的媒体资源发送数据*/mTracker = analytics.newTracker(R.xml.global_tracker)
+//            mGoogleTracker = analytics.newTracker(R.xml.google_tracker)
+//        }
+//    }
+//
+//    /**
+//     * Google Analytics 屏幕-要发送屏幕浏览数据,主要用于:“屏幕”报告,互动流
+//     */
+//    fun setupAppView(screenName: String?) {
+//        try {
+//            screenName?.apply {
+//                // Set screen name.
+//                mTracker?.setScreenName(this)
+//                // Send a screen view.
+//                mTracker?.send(ScreenViewBuilder().build())
+//            }
+//        } catch (e: Exception) {
+//            e.printStackTrace()
+//        }
+//    }
+//
+////    /**
+////     * Google Analytics事件跟踪-发送事件数据。
+////     *
+////     * @param_category事件类别
+////     * @param_action事件操作
+////     * @param_label事件标签
+////     */
+////    fun setupEvent(cls: Class<*>, category: String, action: String, label: String?) {
+////        try {
+////            //类名_label_版本号
+////            val labelString: String = StringBuffer(cls.simpleName).append("_").append(label).append("_").append(MyPdfBaseModule.versionName).toString()
+////            // Build and send an Event.
+////            mTracker?.send(
+////                EventBuilder()
+////                    .setCategory(category)
+////                    .setAction(action)
+////                    .setLabel(labelString)
+////                    .build()
+////            )
+////        } catch (e: Exception) {
+////            e.printStackTrace()
+////        }
+////    }
+////
+////    /**
+////     * Google Analytics事件跟踪-发送事件数据。
+////     *
+////     * @param_category事件类别
+////     * @param_action事件操作
+////     * @param_label事件标签
+////     * @param_value事件值
+////     */
+////    fun setupEvent(cls: Class<*>, category: String, action: String, label: String?, value: Long?) {
+////        try {
+////            //类名_label_版本号
+////            val labelString: String = StringBuffer(cls.simpleName).append("_").append(label).append("_").append(MyPdfBaseModule.versionName).toString()
+////
+////            // Build and send an Event.
+////            mTracker?.send(
+////                EventBuilder()
+////                    .setCategory(category)
+////                    .setAction(action)
+////                    .setLabel(labelString)
+////                    .setValue(value ?: "0".toLong())
+////                    .build()
+////            )
+////        } catch (e: Exception) {
+////            e.printStackTrace()
+////        }
+////    }
+//
+//    /**
+//     * @param:
+//     * @create at: 9/10/16 16:55
+//     */
+//    fun setupDispatchLocalHits() {
+//        MyPdfBaseModule.getAppContext()?.apply {
+//            GoogleAnalytics.getInstance(this).dispatchLocalHits()
+//        }
+//    }
+//}

+ 124 - 0
lib_common/src/main/java/com/kdanmobile/android/common/utils/ReflectionUtils.kt

@@ -0,0 +1,124 @@
+package com.kdanmobile.android.common.utils
+
+import java.lang.reflect.Field
+import java.lang.reflect.InvocationTargetException
+import java.lang.reflect.Method
+
+/**
+ *    @author : hubowen
+ *    @e-mail : hubowen@kdanmobile.com
+ *    @date   : 2020/9/17  7:19 PM
+ *    @description:
+ */
+object ReflectionUtils {
+    /**
+     * 循环向上转型, 获取对象的 DeclaredMethod
+     * @param object : 子类对象
+     * @param methodName : 父类中的方法名
+     * @param parameterTypes : 父类中的方法参数类型
+     * @return 父类中的方法对象
+     */
+    fun getDeclaredMethod(`object`: Any, methodName: String?, vararg parameterTypes: Class<*>?): Method? {
+        var method: Method
+        var clazz: Class<*>? = `object`.javaClass
+        while (clazz != Any::class.java) {
+            try {
+                method = clazz!!.getDeclaredMethod(methodName!!, *parameterTypes)
+                return method
+            } catch (ignored: Exception) {
+                /*这里甚么都不要做!并且这里的异常必须这样写,不能抛出去。
+                如果这里的异常打印或者往外抛,则就不会执行clazz = clazz.getSuperclass(),最后就不会进入到父类中了*/
+            }
+            clazz = clazz!!.superclass
+        }
+        return null
+    }
+
+    /**
+     * 直接调用对象方法, 而忽略修饰符(private, protected, default)
+     * @param object : 子类对象
+     * @param methodName : 父类中的方法名
+     * @param parameterTypes : 父类中的方法参数类型
+     * @param parameters : 父类中的方法参数
+     * @return 父类中方法的执行结果
+     */
+    fun invokeMethod(`object`: Any, methodName: String?, parameterTypes: Array<Class<*>?>, parameters: Array<Any?>): Any? {
+        //根据 对象、方法名和对应的方法参数 通过反射 调用上面的方法获取 Method 对象
+        val method = getDeclaredMethod(`object`, methodName, *parameterTypes)
+        //抑制Java对方法进行检查,主要是针对私有方法而言
+        method!!.isAccessible = true
+        try {
+            return method.invoke(`object`, *parameters)
+        } catch (e: IllegalArgumentException) {
+            e.printStackTrace()
+        } catch (e: IllegalAccessException) {
+            e.printStackTrace()
+        } catch (e: InvocationTargetException) {
+            e.printStackTrace()
+        }
+        return null
+    }
+
+    /**
+     * 循环向上转型, 获取对象的 DeclaredField
+     * @param object : 子类对象
+     * @param fieldName : 父类中的属性名
+     * @return 父类中的属性对象
+     */
+    fun getDeclaredField(`object`: Any, fieldName: String?): Field? {
+        var field: Field
+        var clazz: Class<*>? = `object`.javaClass
+        while (clazz != Any::class.java) {
+            try {
+                field = clazz!!.getDeclaredField(fieldName!!)
+                return field
+            } catch (ignored: Exception) {
+                //这里甚么都不要做!并且这里的异常必须这样写,不能抛出去。
+                //如果这里的异常打印或者往外抛,则就不会执行clazz = clazz.getSuperclass(),最后就不会进入到父类中了
+            }
+            clazz = clazz!!.superclass
+        }
+        return null
+    }
+
+    /**
+     * 直接设置对象属性值, 忽略 private/protected 修饰符, 也不经过 setter
+     * @param object : 子类对象
+     * @param fieldName : 父类中的属性名
+     * @param value : 将要设置的值
+     */
+    fun setFieldValue(`object`: Any, fieldName: String?, value: Any?) {
+        //根据 对象和属性名通过反射 调用上面的方法获取 Field对象
+        val field = getDeclaredField(`object`, fieldName)
+        //抑制Java对其的检查
+        field!!.isAccessible = true
+        try {
+            //将 object 中 field 所代表的值 设置为 value
+            field[`object`] = value
+        } catch (e: IllegalArgumentException) {
+            e.printStackTrace()
+        } catch (e: IllegalAccessException) {
+            e.printStackTrace()
+        }
+    }
+
+    /**
+     * 直接读取对象的属性值, 忽略 private/protected 修饰符, 也不经过 getter
+     * @param object : 子类对象
+     * @param fieldName : 父类中的属性名
+     * @return : 父类中的属性值
+     */
+    fun getFieldValue(`object`: Any, fieldName: String?): Any? {
+        //根据 对象和属性名通过反射 调用上面的方法获取 Field对象
+        val field = getDeclaredField(`object`, fieldName)
+        //抑制Java对其的检查
+        field!!.isAccessible = true
+        try {
+            //获取 object 中 field 所代表的属性值
+            return field[`object`]
+        } catch (e: Exception) {
+            e.printStackTrace()
+        }
+        return null
+    }
+}

+ 27 - 0
lib_common/src/main/java/com/kdanmobile/android/common/utils/decoration/PaddingItemDecoration.kt

@@ -0,0 +1,27 @@
+package com.kdanmobile.android.common.utils.decoration
+
+import android.graphics.Rect
+import android.view.View
+import androidx.recyclerview.widget.RecyclerView
+import com.kdanmobile.android.common.extension.dp2px
+
+/**
+ * @classname:PaddingItemDecoration
+ * @auther: LiuXiaoLong
+ * @date: 2022/7/27
+ * description: RecyclerView 条目添加Padding 装饰
+ */
+class PaddingItemDecoration(top: Int, bottom: Int, left: Int, right: Int) : RecyclerView.ItemDecoration() {
+    private val mBottom = bottom
+    private val mLeft = left
+    private val mRight = right
+    private val mTop = top
+
+    override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
+        super.getItemOffsets(outRect, view, parent, state)
+        outRect.bottom = view.context.dp2px(mBottom.toFloat())
+        outRect.top = view.context.dp2px(mTop.toFloat())
+        outRect.left = view.context.dp2px(mLeft.toFloat())
+        outRect.right = view.context.dp2px(mRight.toFloat())
+    }
+}

+ 50 - 0
lib_common/src/main/java/com/kdanmobile/android/common/utils/decoration/SpacesItemDecoration.kt

@@ -0,0 +1,50 @@
+package com.kdanmobile.android.common.utils.decoration
+
+import android.graphics.Rect
+import android.view.View
+import androidx.recyclerview.widget.RecyclerView
+
+/**
+ * @classname: SpacesItemDecoration
+ * @auther: LiuXiaoLong
+ * @date: 2022-07-27 PM
+ * description:
+ */
+class SpacesItemDecoration : RecyclerView.ItemDecoration {
+    private var left = 0
+    private var right = 0
+    private var bottom = 0
+    private var fistItemTop = 0
+
+    private constructor() : super() {}
+
+    constructor(space: Int) {
+        left = space
+        right = space
+        bottom = space
+        fistItemTop = space
+    }
+
+    /**
+     * @构造函数
+     * @说明:对recycleView的item的decoration进行配置
+     * @param left
+     * @param right
+     * @param bottom
+     * @param fistItemTop
+     */
+    constructor(left: Int, right: Int, bottom: Int, fistItemTop: Int) {
+        this.left = left
+        this.right = right
+        this.fistItemTop = fistItemTop
+        this.bottom = bottom
+    }
+
+    override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
+        outRect.left = left
+        outRect.right = right
+        outRect.top = fistItemTop
+        outRect.bottom = bottom
+    }
+
+}

+ 27 - 0
lib_common/src/main/java/com/kdanmobile/android/common/utils/dimens/DimenGenerator.kt

@@ -0,0 +1,27 @@
+package com.kdanmobile.android.common.utils.dimens
+
+/**
+ * @classname:DimenGenerator
+ * @author:luozhipeng
+ * @date:23/7/18 10:10
+ * @description:
+ */
+object DimenGenerator {
+    /**
+     * 设计稿尺寸(根据自己设计师的设计稿的宽度填入)
+     */
+    private const val DESIGN_WIDTH = 360
+
+    /**
+     * 设计稿高度  没用到
+     */
+    private const val DESIGN_HEIGHT = 640
+
+    @JvmStatic
+    fun main(args: Array<String>) {
+        val values = DimenTypes.values()
+        for (value in values) {
+            MakeUtils.makeAll(DESIGN_WIDTH, value, "./pdf/src/main/res")
+        }
+    }
+}

+ 16 - 0
lib_common/src/main/java/com/kdanmobile/android/common/utils/dimens/DimenTypes.kt

@@ -0,0 +1,16 @@
+package com.kdanmobile.android.common.utils.dimens
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/7/27
+ * description:
+ */
+enum class DimenTypes(val swWidthDP : Int) {
+
+    DP_SW_240(240),
+    DP_SW_320(320),
+    DP_SW_360(360),
+    DP_SW_480(480);
+
+}

+ 95 - 0
lib_common/src/main/java/com/kdanmobile/android/common/utils/dimens/MakeUtils.kt

@@ -0,0 +1,95 @@
+package com.kdanmobile.android.common.utils.dimens
+
+import java.io.File
+import java.io.FileOutputStream
+import java.io.IOException
+import java.math.BigDecimal
+
+
+/**
+ * @classname:MakeUtils
+ * @author:luozhipeng
+ * @date:23/7/18 10:09
+ * @description:
+ */
+object MakeUtils {
+
+    private const val XML_HEADER = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n"
+    private const val XML_RESOURCE_START = "<resources>\r\n"
+    private const val XML_RESOURCE_END = "</resources>\r\n"
+    private const val XML_DIMEN_TEMPLETE = "<dimen name=\"qb_%1\$spx_%2\$d\">%3$.2fdp</dimen>\r\n"
+    private const val XML_BASE_DPI = "<dimen name=\"base_dpi\">%ddp</dimen>\r\n"
+    private const val MAX_SIZE = 1600
+
+    /**
+     * 生成的文件名
+     */
+    private const val XML_NAME = "sw_dimens.xml"
+
+
+    private fun px2dip(pxValue: Float, sw: Int, designWidth: Int): Float {
+        val dpValue = pxValue / designWidth.toFloat() * sw
+        val bigDecimal = BigDecimal.valueOf(dpValue.toDouble())
+        return bigDecimal.setScale(2, BigDecimal.ROUND_HALF_UP).toFloat()
+    }
+
+    /**
+     * 生成所有的尺寸数据
+     *
+     * @param type
+     * @return
+     */
+    private fun makeAllDimens(type: DimenTypes, designWidth: Int): String {
+        return try {
+            buildString {
+                append(XML_HEADER)
+                append(XML_RESOURCE_START)
+                append(String.format(XML_BASE_DPI, type.swWidthDP))
+                for (i in 0..MAX_SIZE){
+                    val dpValue = px2dip(i.toFloat(), type.swWidthDP, designWidth)
+                    append(String.format(XML_DIMEN_TEMPLETE, "", i, dpValue))
+                }
+                append(XML_RESOURCE_END)
+            }
+        } catch (e: Exception) {
+            e.printStackTrace()
+            ""
+        }
+    }
+
+    /**
+     * 生成的目标文件夹
+     * 只需传宽进来就行
+     *
+     * @param type     枚举类型
+     * @param buildDir 生成的目标文件夹
+     */
+    fun makeAll(designWidth: Int, type: DimenTypes, buildDir: String) {
+        var fos: FileOutputStream? = null
+        try {
+            //生成规则
+            if (type.swWidthDP <=0) return
+            val folderName = "values-sw" + java.lang.String.valueOf(type.swWidthDP) + "dp"
+
+            //生成目标目录
+            val file = File(buildDir + File.separator + folderName).apply {
+                if (exists().not()){
+                    mkdirs()
+                }
+            }
+
+            //生成values文件
+            fos = FileOutputStream(file.absolutePath + File.separator + XML_NAME)
+            fos.write(makeAllDimens(type, designWidth).toByteArray())
+            fos.flush()
+        } catch (e: Exception) {
+            e.printStackTrace()
+        } finally {
+            try {
+                fos?.close()
+            } catch (e: IOException) {
+                e.printStackTrace()
+            }
+        }
+    }
+}

+ 63 - 0
lib_common/src/main/java/com/kdanmobile/android/common/utils/eventbus/EventBusUtils.kt

@@ -0,0 +1,63 @@
+package com.kdanmobile.android.common.utils.eventbus
+
+import org.greenrobot.eventbus.EventBus
+
+/**
+ *    @author : hubowen
+ *    @e-mail : hubowen@kdanmobile.com
+ *    @date   : 2020/9/14  4:59 PM
+ *    @description:
+ */
+object EventBusUtils {
+    
+    
+    fun register(obj : Any) {
+        if (!EventBus.getDefault().isRegistered(obj)) EventBus.getDefault().register(obj)
+    }
+
+    fun unRegister(obj: Any) {
+        if (EventBus.getDefault().isRegistered(obj)) EventBus.getDefault().unregister(obj)
+    }
+
+    fun post(Publisher: Any) {
+        EventBus.getDefault().post(Publisher)
+    }
+
+    fun <T> post(tag: String, t: T) {
+        EventBus.getDefault().post(MessageEvent(tag, t))
+    }
+
+    fun postSticky(Publisher: Any) {
+        EventBus.getDefault().postSticky(Publisher)
+    }
+
+    /**
+     * @methodName:cancelEventDelivery created by luozhipeng on 14/8/18 15:06.
+     * @description: 取消事件传送 事件取消仅限于ThreadMode.PostThread下才可以使用,不取消事件就会一直存在
+     */
+    fun cancelEventDelivery(event: Any) {
+        EventBus.getDefault().cancelEventDelivery(event)
+    }
+
+    /**
+     * @param :[eventType]
+     * @return : void
+     * @methodName :removeStickyEvent created by luozhipeng on 14/8/18 15:08.
+     * @description :移除指定的粘性订阅事件
+     */
+    fun <T> removeStickyEvent(eventType: Class<T>?) {
+        EventBus.getDefault().getStickyEvent(eventType)?.apply {
+            EventBus.getDefault().removeStickyEvent(this)
+        }
+    }
+
+    /**
+     * @methodName:removeAllStickyEvents created by luozhipeng on 14/8/18 15:06.
+     * @description: 移除所有的粘性订阅事件
+     */
+    fun removeAllStickyEvents() {
+        EventBus.getDefault().removeAllStickyEvents()
+    }
+
+
+}

+ 16 - 0
lib_common/src/main/java/com/kdanmobile/android/common/utils/eventbus/MessageEvent.kt

@@ -0,0 +1,16 @@
+package com.kdanmobile.android.common.utils.eventbus
+
+import java.io.Serializable
+
+/**
+ *    @author : hubowen
+ *    @e-mail : hubowen@kdanmobile.com
+ *    @date   : 2020/9/14  5:03 PM
+ *    @description:
+ */
+class MessageEvent<T>(private val tag: String, private val event: T) : Serializable {
+
+    fun getTag() = tag
+
+    fun getEvent() = event
+}

+ 33 - 0
lib_common/src/main/java/com/kdanmobile/android/common/utils/file/CloseUtils.kt

@@ -0,0 +1,33 @@
+package com.kdanmobile.android.common.utils.file
+
+import java.io.Closeable
+import java.io.IOException
+
+/**
+ * @classname CloseUtils
+ * @author LiuXiaoLong
+ * @date 2022-07-19
+ * @description 关闭流工具类
+ */
+object CloseUtils {
+
+    /**
+     * 关闭 IO
+     * @param closeables closeables
+     */
+    @JvmStatic
+    fun closeIO(vararg closeables: Closeable?) {
+        if (closeables.isNullOrEmpty()) {
+            return
+        }
+        try {
+            for (closeable in closeables) {
+                closeable?.close()
+            }
+        } catch (e: IOException) {
+            e.printStackTrace()
+        }
+    }
+
+
+}

+ 428 - 0
lib_common/src/main/java/com/kdanmobile/android/common/utils/file/FileIOUtils.kt

@@ -0,0 +1,428 @@
+package com.kdanmobile.android.common.utils.file
+
+import com.kdanmobile.android.common.extension.outputStream
+import com.kdanmobile.android.common.extension.toFile
+import java.io.*
+import java.lang.StringBuilder
+import java.nio.ByteBuffer
+import java.nio.channels.FileChannel
+import java.nio.charset.Charset
+import java.util.ArrayList
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/7/27
+ * description: 文件相关的io操作工具类
+ */
+object FileIOUtils {
+
+    private val DEFAULT_CHARSET_NAME = "UTF-8"
+
+    /**
+     * 从输入流写入文件
+     * @param filePath 文件路径
+     * @param inputStream 输入流
+     * @return `true`: success<br></br>`false`: fail
+     */
+    fun writeFileFromIS(filePath: String, inputStream: InputStream?, append : Boolean = false): Boolean {
+        return writeFileFromIS(filePath.toFile(), inputStream, append)
+    }
+
+
+    /**
+     * 从输入流写入文件
+     * @param file 文件
+     * @param is 要写入的数据输入流
+     * @return `true`: success<br></br>`false`: fail
+     */
+    fun writeFileFromIS(file: File?, inputStream : InputStream?, append : Boolean = false): Boolean {
+        return writeFileFromBytesByStream(file = file, bytes = inputStream?.readBytes(), append = append)
+    }
+
+    /**
+     * 写入字节流
+     * @param filePath 写入的文件地址
+     * @param bytes 要写入的数组
+     * @return `true`: success<br></br>`false`: fail
+     */
+    fun writeFileFromBytesByStream(filePath: String, bytes: ByteArray?, append: Boolean = false): Boolean {
+        return writeFileFromBytesByStream(filePath.toFile(), bytes, append)
+    }
+
+
+    /**
+     * Write file from bytes by stream.
+     * @param file The file.
+     * @param bytes The bytes.
+     * @param append 是否附加数据 true,在后面添加
+     * @return `true`: success<br></br>`false`: fail
+     */
+    fun writeFileFromBytesByStream(
+        file: File?,
+        bytes: ByteArray?,
+        append: Boolean
+    ): Boolean {
+        if (bytes == null || !createOrExistsFile(file)) {
+            return false
+        }
+        return try {
+            if (append){
+                file?.appendBytes(bytes)
+            }else{
+                file?.writeBytes(bytes)
+            }
+            true
+        } catch (e: IOException) {
+            e.printStackTrace()
+            false
+        }
+    }
+
+
+
+    /**
+     * Write file from bytes by channel.
+     * @param filePath The path of file.
+     * @param bytes The bytes.
+     * @param append True to append, false otherwise.
+     * @param isForce True to force write file, false otherwise.
+     * @return `true`: success<br></br>`false`: fail
+     */
+    fun writeFileFromBytesByChannel(
+        filePath: String,
+        bytes: ByteArray?,
+        append: Boolean = false,
+        isForce: Boolean
+    ): Boolean {
+        return writeFileFromBytesByChannel(filePath.toFile(), bytes, append, isForce)
+    }
+
+
+    /**
+     * Write file from bytes by channel.
+     * @param file The file.
+     * @param bytes The bytes.
+     * @param append True to append, false otherwise.
+     * @param isForce True to force write file, false otherwise.
+     * @return `true`: success<br></br>`false`: fail
+     */
+    fun writeFileFromBytesByChannel(
+        file: File?,
+        bytes: ByteArray?,
+        append: Boolean,
+        isForce: Boolean
+    ): Boolean {
+        if (bytes == null) {
+            return false
+        }
+        return try {
+            file?.outputStream(append)?.channel?.use {
+                it.position(it.size())
+                it.write(ByteBuffer.wrap(bytes))
+                if (isForce){
+                    it.force(true)
+                }
+            }
+            true
+        } catch (e: IOException) {
+            e.printStackTrace()
+            false
+        }
+    }
+
+
+    /**
+     * Write file from bytes by map.
+     * @param filePath The path of file.
+     * @param bytes The bytes.
+     * @param append True to append, false otherwise.
+     * @param isForce True to force write file, false otherwise.
+     * @return `true`: success<br></br>`false`: fail
+     */
+    fun writeFileFromBytesByMap(
+        filePath: String,
+        bytes: ByteArray?,
+        append: Boolean = false,
+        isForce: Boolean
+    ): Boolean {
+        return writeFileFromBytesByMap(filePath.toFile(), bytes, append, isForce)
+    }
+
+
+    /**
+     * Write file from bytes by map.
+     * @param file The file.
+     * @param bytes The bytes.
+     * @param append True to append, false otherwise.
+     * @param isForce True to force write file, false otherwise.
+     * @return `true`: success<br></br>`false`: fail
+     */
+    fun writeFileFromBytesByMap(
+        file: File?,
+        bytes: ByteArray?,
+        append: Boolean,
+        isForce: Boolean
+    ): Boolean {
+        if (bytes == null || !createOrExistsFile(file)) {
+            return false
+        }
+        return try {
+            file?.outputStream(append)?.channel?.use {
+              val mbb = it.map(FileChannel.MapMode.READ_WRITE, it.size(), bytes.size.toLong())
+               mbb.put(bytes)
+               if (isForce){
+                   mbb.force()
+               }
+           }
+            true
+        } catch (e: IOException) {
+            e.printStackTrace()
+            false
+        }
+    }
+
+
+    /**
+     * Write file from string.
+     * @param filePath The path of file.
+     * @param content The string of content.
+     * @param append True to append, false otherwise.
+     * @return `true`: success<br></br>`false`: fail
+     */
+    fun writeFileFromString(
+        filePath: String,
+        content: String?,
+        append: Boolean = false
+    ): Boolean {
+        return writeFileFromString(filePath.toFile(), content, append)
+    }
+
+
+    /**
+     * Write file from string.
+     * @param file The file.
+     * @param content The string of content.
+     * @param append True to append, false otherwise.
+     * @return `true`: success<br></br>`false`: fail
+     */
+    fun writeFileFromString(
+        file: File?,
+        content: String?,
+        append: Boolean
+    ): Boolean {
+        if (content == null) {
+            return false
+        }
+        if (!createOrExistsFile(file)) {
+            return false
+        }
+        return try {
+            if (append){
+                file?.appendText(content)
+            }else{
+                file?.writeText(content)
+            }
+            true
+        } catch (e: IOException) {
+            e.printStackTrace()
+            false
+        }
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // the divide line of write and read
+    ///////////////////////////////////////////////////////////////////////////
+
+    ///////////////////////////////////////////////////////////////////////////
+    // the divide line of write and read
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Return the lines in file.
+     * @param filePath The path of file.
+     * @param charsetName The name of charset.
+     * @return the lines in file
+     */
+    fun readFile2List(filePath: String, charsetName: String = DEFAULT_CHARSET_NAME): List<String?>? {
+        return readFile2List(filePath.toFile(), charsetName)
+    }
+
+    /**
+     * Return the lines in file.
+     * @param file The file.
+     * @param charsetName The name of charset.
+     * @return the lines in file
+     */
+    fun readFile2List(file: File?, charsetName: String = DEFAULT_CHARSET_NAME): List<String?>? {
+        return readFile2List(file, 0, 0x7FFFFFFF, charsetName)
+    }
+
+    /**
+     * Return the lines in file.
+     * @param filePath The path of file.
+     * @param st The line's index of start.
+     * @param end The line's index of end.
+     * @param charsetName The name of charset.
+     * @return the lines in file
+     */
+    fun readFile2List(
+        filePath: String,
+        st: Int,
+        end: Int,
+        charsetName: String = DEFAULT_CHARSET_NAME
+    ): List<String?>? {
+        return readFile2List(filePath.toFile(), st, end, charsetName)
+    }
+
+
+    /**
+     * Return the lines in file.
+     * @param file The file.
+     * @param st The line's index of start.
+     * @param end The line's index of end.
+     * @param charsetName The name of charset.
+     * @return the lines in file
+     */
+    fun readFile2List(
+        file: File?,
+        st: Int,
+        end: Int,
+        charsetName: String = "UTF-8"
+    ): List<String?>? {
+        if (!isFileExists(file)) {
+            return null
+        }
+        if (st > end) {
+            return null
+        }
+        return try {
+            var line: String?
+            var curLine = 1
+
+            val list: MutableList<String?> = ArrayList()
+
+            val reader = file!!.bufferedReader(Charset.forName(charsetName))
+
+            while (reader.readLine().also { line = it } != null) {
+                if (curLine > end) {
+                    break
+                }
+                if (curLine in st..end) {
+                    list.add(line)
+                }
+                ++curLine
+            }
+
+            list
+        } catch (e: IOException) {
+            e.printStackTrace()
+            null
+        } finally {
+        }
+    }
+
+
+
+
+    /**
+     * Return the bytes in file by channel.
+     * @param filePath The path of file.
+     * @return the bytes in file
+     */
+    fun readFile2BytesByChannel(filePath: String): ByteArray? {
+        return readFile2BytesByChannel(File(filePath))
+    }
+
+    /**
+     * Return the bytes in file by channel.
+     * @param file The file.
+     * @return the bytes in file
+     */
+    fun readFile2BytesByChannel(file: File?): ByteArray? {
+        if (!isFileExists(file)) {
+            return null
+        }
+        var fc: FileChannel? = null
+        return try {
+
+            fc = RandomAccessFile(file, "r").channel
+            val byteBuffer = ByteBuffer.allocate(fc.size().toInt())
+            while (true) {
+                if (fc.read(byteBuffer) <= 0) {
+                    break
+                }
+            }
+            byteBuffer.array()
+        } catch (e: IOException) {
+            e.printStackTrace()
+            null
+        } finally {
+            CloseUtils.closeIO(fc)
+        }
+    }
+
+    /**
+     * Return the bytes in file by map.
+     * @param filePath The path of file.
+     * @return the bytes in file
+     */
+    fun readFile2BytesByMap(filePath: String): ByteArray? {
+        return readFile2BytesByMap(File(filePath))
+    }
+
+    /**
+     * Return the bytes in file by map.
+     * @param file The file.
+     * @return the bytes in file
+     */
+    fun readFile2BytesByMap(file: File?): ByteArray? {
+        if (!isFileExists(file)) {
+            return null
+        }
+        var fc: FileChannel? = null
+        return try {
+            fc = RandomAccessFile(file, "r").channel
+            val size = fc.size().toInt()
+            val mbb = fc.map(FileChannel.MapMode.READ_ONLY, 0, size.toLong()).load()
+            val result = ByteArray(size)
+            mbb[result, 0, size]
+            result
+        } catch (e: IOException) {
+            e.printStackTrace()
+            null
+        } finally {
+            CloseUtils.closeIO(fc)
+        }
+    }
+
+    fun createOrExistsFile(filePath: String): Boolean {
+        return createOrExistsFile(File(filePath))
+    }
+
+     fun createOrExistsFile(file: File?): Boolean {
+        if (file == null) {
+            return false
+        }
+        if (file.exists()) {
+            return file.isFile
+        }
+        return if (!createOrExistsDir(file.parentFile)) {
+            false
+        } else try {
+            file.createNewFile()
+        } catch (e: IOException) {
+            e.printStackTrace()
+            false
+        }
+    }
+
+    fun createOrExistsDir(file: File?): Boolean {
+        return file != null && if (file.exists()) file.isDirectory else file.mkdirs()
+    }
+
+    private fun isFileExists(file: File?): Boolean {
+        return file?.exists() == true
+    }
+
+}

+ 36 - 0
lib_common/src/main/java/com/kdanmobile/android/common/utils/firebase/FirebaseEventUtils.kt

@@ -0,0 +1,36 @@
+package com.kdanmobile.android.common.utils.firebase
+
+import android.os.Bundle
+import android.os.Parcelable
+import com.google.firebase.analytics.FirebaseAnalytics
+import com.kdanmobile.android.common.config.MyPdfBaseModule
+import java.io.Serializable
+import java.util.ArrayList
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/7/29
+ * description: firebase埋点
+ */
+object FirebaseEventUtils {
+
+    private val mFirebaseAnalytics : FirebaseAnalytics by lazy(LazyThreadSafetyMode.SYNCHRONIZED){
+        FirebaseAnalytics.getInstance(MyPdfBaseModule.getAppContext()!!).apply {
+            setAnalyticsCollectionEnabled(true)
+        }
+    }
+
+
+    fun logEvent(eventName : String, bundle : (Bundle)-> Unit){
+        mFirebaseAnalytics.logEvent(eventName, Bundle().apply {
+            bundle.invoke(this)
+        })
+    }
+
+    fun logEvent(eventName: String, bundle: Bundle){
+        mFirebaseAnalytics.logEvent(eventName, bundle)
+    }
+
+
+}

+ 39 - 0
lib_common/src/main/java/com/kdanmobile/android/common/utils/firebase/FirebaseRemoteConfig.kt

@@ -0,0 +1,39 @@
+package com.kdanmobile.android.common.utils.firebase
+
+import android.content.Context
+import com.google.firebase.ktx.Firebase
+import com.google.firebase.remoteconfig.ktx.remoteConfig
+import com.google.firebase.remoteconfig.ktx.remoteConfigSettings
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/7/29
+ * description:Firebase远程配置
+ */
+object FirebaseRemoteConfig {
+
+    fun initFirebaseConfig(context: Context, callback : (()-> Unit)? = null){
+        val configSettings = remoteConfigSettings {
+            minimumFetchIntervalInSeconds = 3600
+            fetchTimeoutInSeconds = 30
+        }
+
+        Firebase.remoteConfig.apply {
+            setConfigSettingsAsync(configSettings)
+
+        }.fetchAndActivate().addOnCompleteListener {
+            callback?.invoke()
+        }
+    }
+
+    fun getBoolean(key : String) : Boolean = Firebase.remoteConfig.getBoolean(key)
+
+    fun getString(key: String) : String = Firebase.remoteConfig.getString(key)
+
+    fun getLong(key: String) : Long = Firebase.remoteConfig.getLong(key)
+
+    fun getDouble(key: String) : Double = Firebase.remoteConfig.getDouble(key)
+
+
+}

+ 873 - 0
lib_common/src/main/java/com/kdanmobile/android/common/utils/image/BitmapUtils.kt

@@ -0,0 +1,873 @@
+//package com.kdanmobile.android.common.utils.image
+//
+//import android.R
+//import android.annotation.SuppressLint
+//import android.content.Context
+//import android.content.res.ColorStateList
+//import android.content.res.Resources
+//import android.graphics.*
+//import android.graphics.drawable.BitmapDrawable
+//import android.graphics.drawable.Drawable
+//import android.graphics.drawable.StateListDrawable
+//import android.media.ExifInterface
+//import android.net.Uri
+//import android.os.Build
+//import android.text.TextUtils
+//import android.view.View
+//import android.widget.ImageView
+//import androidx.annotation.DrawableRes
+//import androidx.annotation.RequiresApi
+//import androidx.appcompat.widget.AppCompatImageButton
+//import androidx.appcompat.widget.AppCompatImageView
+//import androidx.core.content.ContextCompat
+//import androidx.core.graphics.drawable.DrawableCompat
+//import com.kdan.pdfbaselibrary.utils.systemui.ScreenUtil.Companion.getDensity
+//import com.kdan.pdfbaselibrary.utils.file.FileUtils
+//import kotlinx.coroutines.Dispatchers
+//import kotlinx.coroutines.withContext
+//import java.io.*
+//import java.text.SimpleDateFormat
+//import java.util.*
+//
+//
+///**
+// * @classname:BitmapUtils
+// * @author:wangzhe
+// * @date:2018/9/13 下午2:19
+// * @description:
+// */
+//object BitmapUtils {
+//
+//
+//    /**
+//     * 从资源中解码经过采样处理后的bitmap
+//     */
+//    fun decodeSampledBitmapFromResource(res: Resources?, resId: Int, reqWidth: Int, reqHeight: Int): Bitmap? {
+//        // 第一次解析将inJustDecodeBounds设置为true,来获取图片大小
+//        val options = BitmapFactory.Options()
+//        options.inJustDecodeBounds = true
+//        BitmapFactory.decodeResource(res, resId, options)
+//        // 调用上面定义的方法计算inSampleSize值
+//        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight)
+//        // 使用获取到的inSampleSize值再次解析图片
+//        options.inJustDecodeBounds = false
+//        return BitmapFactory.decodeResource(res, resId, options)
+//    }
+//
+//    /**
+//     * 从文件
+//     */
+//    fun decodeSampledBitmapFromPath(path: String?, reqWidth: Int, reqHeight: Int): Bitmap? {
+//        // 第一次解析将inJustDecodeBounds设置为true,来获取图片大小
+//        val options = BitmapFactory.Options()
+//        options.inJustDecodeBounds = true
+//        BitmapFactory.decodeFile(path, options)
+//        // 调用上面定义的方法计算inSampleSize值
+//        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight)
+//        // 使用获取到的inSampleSize值再次解析图片
+//        options.inJustDecodeBounds = false
+//        return BitmapFactory.decodeFile(path, options)
+//    }
+//
+//
+//    fun getBitmapByResource(context: Context?,@DrawableRes resId: Int): Bitmap? {
+//        val drawable = ContextCompat.getDrawable(context!!, resId)
+//        val bd = drawable as BitmapDrawable?
+//        return bd!!.bitmap
+//    }
+//
+//
+//    /**
+//     * 计算图片的采样大小
+//     */
+//    fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int {
+//        // 源图片的高度和宽度
+//        val height = options.outHeight
+//        val width = options.outWidth
+//        var inSampleSize = 1
+//        if (height > reqHeight || width > reqWidth) {
+//            // 计算出实际宽高和目标宽高的比率
+//            val heightRatio = Math.round(height.toFloat() / reqHeight.toFloat())
+//            val widthRatio = Math.round(width.toFloat() / reqWidth.toFloat())
+//            // 选择宽和高中最小的比率作为inSampleSize的值,这样可以保证最终图片的宽和高
+//            // 一定都会大于等于目标的宽和高。
+//            inSampleSize = if (heightRatio > widthRatio) heightRatio else widthRatio
+//        }
+//        return inSampleSize
+//    }
+//
+//
+//
+//
+//    /**
+//     * @param image
+//     * @param percent 压缩百分比
+//     * @return
+//     * @方法说明:质量压缩方法
+//     * @方法名称:compressImage
+//     * @返回值:Bitmap
+//     */
+//    fun compressImage(image: Bitmap, percent: Int): Bitmap? {
+//        val baos = ByteArrayOutputStream()
+//        image.compress(Bitmap.CompressFormat.JPEG, percent, baos) // 质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中
+//        var options = 60
+//        while (baos.toByteArray().size / 1024 > 80) { // 循环判断如果压缩后图片是否大于80kb,大于继续压缩
+//            baos.reset() // 重置baos即清空baos
+//            image.compress(Bitmap.CompressFormat.JPEG, options, baos) // 这里压缩options%,把压缩后的数据存放到baos中
+//            options -= 10 // 每次都减少10
+//        }
+//        val isBm = ByteArrayInputStream(baos.toByteArray()) // 把压缩后的数据baos存放到ByteArrayInputStream中
+//        return BitmapFactory.decodeStream(isBm, null, null)
+//    }
+//
+//    @JvmStatic
+//    fun getBitmapFromFile(file: File?): Bitmap? = if (file?.exists() == true) getBitmapFromFilePath(file.absolutePath) else null
+//
+//    @JvmStatic
+//    fun getBitmapFromFilePath(filePath: String?): Bitmap? {
+//        return try {
+//            when {
+//                filePath.isNullOrEmpty() -> null
+//                !File(filePath).exists() -> null
+//                else -> {
+//                    val opts = BitmapFactory.Options()
+//                    opts.inJustDecodeBounds = false
+//                    opts.inSampleSize = 1
+//                    opts.inPreferredConfig = Bitmap.Config.RGB_565
+//                    BitmapFactory.decodeFile(filePath, opts)
+//                }
+//            }
+//        } catch (e: Exception) {
+//            null
+//        }
+//    }
+//
+//    @JvmStatic
+//    fun saveBitmapToFile(bitmap: Bitmap?, file: File?, quality: Int = 80, type: String = "png"): Boolean {
+//        bitmap ?: return false
+//        file ?: return false
+//        if (bitmap.isRecycled) return false
+//
+//        if (!file.exists()) FileUtils.createFile(file, true)
+//        if (!file.exists()) return false
+//
+//        return try {
+//            FileOutputStream(file)
+//                .run {
+//                    try {
+//                        bitmap.compress(if ("png".equals(type, ignoreCase = true)) Bitmap.CompressFormat.PNG else Bitmap.CompressFormat.JPEG, quality, this)
+//                        flush()
+//                        true
+//                    } catch (e: Exception) {
+//                        false
+//                    } finally {
+//                        runCatching { close() }
+//                    }
+//                }
+//        } catch (e: Exception) {
+//            //基本出现这里就异常的是File 文件区域没有读写权限导致的。
+//            return false
+//        }
+//    }
+//
+//    @SuppressLint("SimpleDateFormat")
+//    @JvmStatic
+//    fun saveBitmapToFile(bitmap: Bitmap?, dir: String, type: String): String? {
+//        bitmap ?: return null
+//        if (bitmap.isRecycled) return null
+//
+//        val tempType = when (type) {
+//            "png" -> ".png"
+//            else -> ".jpeg"
+//        }
+//
+//        val file = File(dir, "${SimpleDateFormat("yyyyMMddHHmmss").format(Date(System.currentTimeMillis()))}$tempType")
+//        if (!file.exists()) FileUtils.createFile(file, true)
+//        if (!file.exists()) return null
+//        return saveBitmapToFile(bitmap = bitmap, file = file, type = tempType).run {
+//            when {
+//                this -> file.canonicalPath
+//                else -> null
+//            }
+//        }
+//    }
+//
+//    /**
+//     * @methodName:rotateImage created by liujiyuan on 2019-12-03 18:30.
+//     * @description:把图片顺时针旋转
+//     */
+//    @JvmStatic
+//    fun rotateImage(bitmap: Bitmap?, degrees: Float): Bitmap? {
+//        return try {
+//            if (degrees == 0f || null == bitmap) {
+//                return bitmap
+//            }
+//            val matrix = Matrix()
+//            matrix.setRotate(degrees)
+//            val bmp = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
+//            bitmap.recycle()
+//            bmp
+//        } catch (e: Exception) {
+//            null
+//        }
+//    }
+//
+//    /**
+//     * 读取图片的旋转的角度
+//     *
+//     * @param path 图片绝对路径
+//     * @return 图片的逆时针旋转角度
+//     */
+//    @JvmStatic
+//    fun getBitmapDegree(path: String?): Int {
+//        var degree = 0
+//        if (TextUtils.isEmpty(path)) {
+//            return degree
+//        }
+//        return try { // 从指定路径下读取图片,并获取其EXIF信息
+//            val exifInterface = ExifInterface(path!!)
+//            // 获取图片的旋转信息
+//            val orientation = exifInterface.getAttributeInt(
+//                ExifInterface.TAG_ORIENTATION,
+//                ExifInterface.ORIENTATION_NORMAL
+//            )
+//            when (orientation) {
+//                ExifInterface.ORIENTATION_ROTATE_90 -> degree = 90
+//                ExifInterface.ORIENTATION_ROTATE_180 -> degree = 180
+//                ExifInterface.ORIENTATION_ROTATE_270 -> degree = 270
+//            }
+//            degree
+//        } catch (e: Exception) {
+//            degree
+//        }
+//    }
+//
+//    @RequiresApi(Build.VERSION_CODES.N)
+//    @JvmStatic
+//    fun getBitmapDegree(input: InputStream?): Int {
+//        var degree = 0
+//        if (input == null) {
+//            return 0
+//        }
+//        return try { // 从指定路径下读取图片,并获取其EXIF信息
+//            val exifInterface = ExifInterface(input)
+//            // 获取图片的旋转信息
+//            val orientation = exifInterface.getAttributeInt(
+//                ExifInterface.TAG_ORIENTATION,
+//                ExifInterface.ORIENTATION_NORMAL
+//            )
+//            when (orientation) {
+//                ExifInterface.ORIENTATION_ROTATE_90 -> degree = 90
+//                ExifInterface.ORIENTATION_ROTATE_180 -> degree = 180
+//                ExifInterface.ORIENTATION_ROTATE_270 -> degree = 270
+//            }
+//            degree
+//        } catch (e: Exception) {
+//            degree
+//        }
+//    }
+//
+//    /**
+//     * @methodName:rotatePicture created by liujiyuan on 2019-12-03 18:06.
+//     * @description:把图片的旋转角度变为0,使图片不是颠倒的
+//     */
+//    @JvmStatic
+//    suspend fun rotatePicture(path: String?): Boolean {
+//        if (path.isNullOrEmpty()) return false
+//
+//        return withContext(Dispatchers.IO) {
+//            var bitmap: Bitmap? = getBitmapFromFile(File(path))
+//            bitmap?.run {
+//                try {
+//                    bitmap = rotateImage(bitmap, getBitmapDegree(path).toFloat())
+//                    saveBitmapToFile(bitmap, File(path))
+//                } catch (e: Exception) {
+//                    false
+//                } finally {
+//                    bitmap?.apply { if (!isRecycled) recycle() }
+//                }
+//            } ?: false
+//        }
+//    }
+//
+//    /**
+//     * @methodName: created by liujiyuan on 2020/7/23 下午2:01.
+//     * @description:通过uri把bitmap保存到指定路径
+//     */
+//    @JvmStatic
+//    fun saveBitmapFromUri(context: Context, uri: Uri, path: String?) {
+//        try {
+//            context.contentResolver.let { resolver ->
+//                var degree = 0
+//                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+//                    degree = getBitmapDegree(resolver.openInputStream(uri))
+//                }
+//                resolver.openFileDescriptor(uri, "r")?.run {
+//                    var bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor)
+//                    close()
+//                    bitmap = rotateImage(bitmap, degree.toFloat())
+//                    saveBitmapToFile(bitmap, File(path ?: "ø"))
+//                }
+//            }
+//        } catch (e: java.lang.Exception) {
+//            e.printStackTrace()
+//        }
+//    }
+//
+//    /**
+//     * @methodName: created by liujiyuan on 2020/11/3 上午9:13.
+//     * @description:设置透明背景色
+//     */
+//    fun createTransparentBitmapFromBitmap(bitmap: Bitmap?, replaceThisColor: Int): Bitmap? {
+//        bitmap?.apply {
+//            if (isRecycled) {
+//                return null
+//            }
+//            val picw = width
+//            val pich = height
+//            val pix = IntArray(picw * pich)
+//            getPixels(pix, 0, picw, 0, 0, picw, pich)
+//            for (y in 0 until pich) {
+//                for (x in 0 until picw) {
+//                    val index = y * picw + x
+//                    if (pix[index] == replaceThisColor) {
+//                        pix[index] = Color.TRANSPARENT
+//                    }
+//                }
+//            }
+//            recycle()
+//            return Bitmap.createBitmap(pix, picw, pich, Bitmap.Config.ARGB_8888)
+//        }
+//        return null
+//    }
+//
+//    /**
+//     * @methodName: created by liujiyuan on 2020/11/3 上午9:13.
+//     * @description:压缩图片
+//     */
+//    fun compressImageForSize(image: Bitmap?, size: Int): Bitmap? {
+//        if (image == null || image.isRecycled) {
+//            return null
+//        }
+//        synchronized(image) {
+//            val baos = ByteArrayOutputStream()
+//            // 质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中
+//            image.compress(Bitmap.CompressFormat.PNG, 100, baos)
+//            var options = 100
+//            val len: Int = baos.toByteArray().size / 1024
+//            if (len > size) {
+//                options = options * size / len
+//                if (options == 0) {
+//                    options = 1
+//                }
+//            }
+//            baos.reset() // 重置baos即清空baos
+//            // 这里压缩options%,把压缩后的数据存放到baos中
+//            image.compress(Bitmap.CompressFormat.PNG, options, baos)
+//
+//            // 把压缩后的数据baos存放到ByteArrayInputStream中
+//            val isBm = ByteArrayInputStream(baos.toByteArray())
+//            // 把ByteArrayInputStream数据生成图片
+//            val bitmap = BitmapFactory.decodeStream(isBm, null, null)
+//            try {
+//                baos.close()
+//            } catch (e: IOException) {
+//                e.printStackTrace()
+//            }
+//            try {
+//                isBm.close()
+//            } catch (e: IOException) {
+//                e.printStackTrace()
+//            }
+//            if (!image.isRecycled) {
+//                image.recycle()
+//            }
+//            return bitmap
+//        }
+//    }
+//
+//    /**
+//     * @methodName: created by liujiyuan on 2020/11/3 上午9:16.
+//     * @description:裁剪bitmap
+//     */
+//    @JvmStatic
+//    fun createBitmapFitRect(temp: Bitmap?, width: Float, height: Float): Bitmap? {
+//        if (temp == null || temp.isRecycled || width <= 0 || height <= 0) {
+//            return null
+//        }
+//        val imageHeight = temp.height
+//        val imageWidth = temp.width
+//        //String imageType = options.outMimeType;
+//        val radio: Float = when {
+//            imageHeight / height > imageWidth / width -> height / imageHeight
+//            else -> width / imageWidth
+//        }
+//        val matrix = Matrix()
+//        matrix.preScale(radio, radio)
+//        return Bitmap.createBitmap(temp, 0, 0, imageWidth, imageHeight, matrix, true)
+//    }
+//
+//    fun fitBitmap(target: Bitmap?, newWidth: Int, isRecycle: Boolean): Bitmap? {
+//        var bmp: Bitmap? = null
+//        target?.run {
+//            val width = width
+//            val height = height
+//            val matrix = Matrix()
+//            val scaleWidth = newWidth.toFloat() / width
+//            matrix.postScale(scaleWidth, scaleWidth)
+//            bmp = Bitmap.createBitmap(
+//                this, 0, 0, width, height, matrix,
+//                true
+//            )
+//            if (isRecycle) if (this != bmp && !isRecycled) recycle()
+//        }
+//        return bmp
+//    }
+//
+//
+//    /**
+//     * 缩放图片
+//     */
+//    fun scaleImage(bitmap: Bitmap?, width : Int) : Bitmap?{
+//        if (bitmap == null){
+//            return null
+//        }
+//
+//        val scale = (width * 1F) / bitmap.width
+//        if (scale > 1){
+//            return bitmap
+//        }
+//        return scaleImage(bitmap, scale, scale)
+//
+//    }
+//
+//    /**
+//     * 图片缩放
+//     *
+//     * @param bitmap
+//     * @param sx
+//     * @param sy
+//     * @return
+//     */
+//    fun scaleImage(bitmap: Bitmap?, sx: Float, sy: Float): Bitmap? {
+//        if (bitmap == null) {
+//            return null
+//        }
+//        val matrix = Matrix()
+//        matrix.setScale(sx, sy)
+//        return Bitmap.createBitmap(
+//            bitmap, 0, 0, bitmap.width,
+//            bitmap.height, matrix, false
+//        )
+//    }
+//
+//
+//    fun rotateImageCanvas(bm: Bitmap, orientationDegree: Int): Bitmap? {
+//        val m = Matrix()
+//        m.setRotate(orientationDegree.toFloat(), bm.width.toFloat() / 2, bm.height.toFloat() / 2)
+//        val targetX: Float
+//        val targetY: Float
+//        if (orientationDegree == 90) {
+//            targetX = bm.height.toFloat()
+//            targetY = 0f
+//        } else {
+//            targetX = bm.height.toFloat()
+//            targetY = bm.width.toFloat()
+//        }
+//        val values = FloatArray(9)
+//        m.getValues(values)
+//        val x1 = values[Matrix.MTRANS_X]
+//        val y1 = values[Matrix.MTRANS_Y]
+//        m.postTranslate(targetX - x1, targetY - y1)
+//        val bm1 = Bitmap.createBitmap(bm.height, bm.width, Bitmap.Config.ARGB_8888)
+//        val paint = Paint()
+//        val canvas = Canvas(bm1)
+//        canvas.drawBitmap(bm, m, paint)
+//        return bm1
+//    }
+//
+//    /**
+//     * 获取圆形图片
+//     *
+//     * @param bitmap
+//     * @param length 返回图片的宽高
+//     * @param l      图片粘贴的左上角位置
+//     * @param t
+//     * @return
+//     */
+//    fun getRoundedCornerBitmap(context: Context?, bitmap: Bitmap?, length: Int, l: Int, t: Int): Bitmap? {
+//        if (bitmap == null) {
+//            return null
+//        }
+//        val output = Bitmap.createBitmap(length, length, Bitmap.Config.ARGB_8888)
+//        val canvas = Canvas(output)
+//        val color = -0x1000000
+//        val paint = Paint()
+//        val rect = Rect(0, 0, length, length)
+//        val rectF = RectF(rect)
+//        val roundPx = length * 0.5f
+//        paint.isAntiAlias = true
+//        canvas.drawARGB(0, 0, 0, 0)
+//        paint.color = color
+//        canvas.drawRoundRect(rectF, roundPx, roundPx, paint)
+//        paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
+//        canvas.drawBitmap(bitmap, l.toFloat(), t.toFloat(), paint)
+//        paint.color = Color.GREEN
+//        val len = 10
+//        canvas.drawLine((length / 2 - len).toFloat(), (length / 2).toFloat(), (length / 2 + len).toFloat(), (length / 2).toFloat(), paint)
+//        canvas.drawLine((length / 2).toFloat(), (length / 2 - len).toFloat(), (length / 2).toFloat(), (length / 2 + len).toFloat(), paint)
+//        paint.strokeWidth = 2 * getDensity(context)
+//        paint.color = Color.WHITE
+//        paint.style = Paint.Style.STROKE
+//        canvas.drawCircle((length / 2).toFloat(), (length / 2).toFloat(), length / 2 - 1 * getDensity(context), paint)
+//        return output
+//    }
+//
+//
+//    /**
+//     * 图片灰度处理
+//     *
+//     * @param bitmap
+//     * @return
+//     */
+//    fun greyscale(bitmap: Bitmap?): Bitmap? {
+//        if (bitmap == null) {
+//            return null
+//        }
+//        val colorMatrix = ColorMatrix()
+//        colorMatrix.setSaturation(0f)
+//        val width = bitmap.width
+//        val height = bitmap.height
+//        val buffer: Bitmap
+//        try {
+//            buffer = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565)
+//            val canvas = Canvas(buffer)
+//            val paint = Paint()
+//            paint.colorFilter = ColorMatrixColorFilter(colorMatrix)
+//            canvas.drawBitmap(bitmap, 0f, 0f, paint)
+//        } catch (e: java.lang.Exception) {
+//            e.printStackTrace()
+//            return bitmap
+//        }
+//        return buffer
+//    }
+//
+//
+//    /**
+//     * 图片透明度处理
+//     *
+//     * @param bitmap 原始图片
+//     * @param alpha  透明度
+//     * @return
+//     */
+//    fun setAlpha(bitmap: Bitmap?, alpha: Int): Bitmap? {
+//        if (bitmap == null) {
+//            return null
+//        }
+//        val argb = IntArray(bitmap.width * bitmap.height)
+//        bitmap.getPixels(
+//            argb, 0, bitmap.width, 0, 0, bitmap.width,
+//            bitmap.height
+//        ) // 获得图片的ARGB值
+//        var al: Int
+//        for (i in argb.indices) {
+//            al = (Color.alpha(argb[i]) * alpha / 100f).toInt()
+//            argb[i] = al shl 24 or (argb[i] and 0x00FFFFFF) // 修改最高2位的值
+//        }
+//        return Bitmap.createBitmap(
+//            argb, bitmap.width,
+//            bitmap.height, Bitmap.Config.ARGB_8888
+//        )
+//    }
+//
+//
+//    /**
+//     * 图片进行亮度和对比度的处理
+//     *
+//     * @param src
+//     * @param light
+//     * @param contrast
+//     * @return
+//     */
+//    fun filter(src: Bitmap?, light: Int, contrast: Int): Bitmap? {
+//        if (src == null) {
+//            return null
+//        }
+//        return if (light == -1 && contrast == -1) { //默认值
+//            src
+//        } else {
+//            val width = src.width
+//            val height = src.height
+//            val b = light / 50f
+//            val c = (contrast + 20) / 40f
+//            val inPixels: IntArray
+//            val outPixels: IntArray
+//            try {
+//                inPixels = IntArray(width * height)
+//                outPixels = IntArray(width * height)
+//            } catch (e: OutOfMemoryError) {
+//                // TODO Auto-generated catch block
+//                e.printStackTrace()
+//                return src
+//            }
+//            src.getPixels(inPixels, 0, width, 0, 0, width, height)
+//
+//            // calculate RED, GREEN, BLUE means of pixel
+//            var index = 0
+//            val rgbmeans = IntArray(3)
+//            var redSum = 0.0
+//            var greenSum = 0.0
+//            var blueSum = 0.0
+//            val total = (height * width).toDouble()
+//            for (row in 0 until height) {
+//                // int ta = 0;
+//                var tr = 0
+//                var tg = 0
+//                var tb = 0
+//                for (col in 0 until width) {
+//                    index = row * width + col
+//                    // ta = (inPixels[index] >> 24) & 0xff;
+//                    tr = inPixels[index] shr 16 and 0xff
+//                    tg = inPixels[index] shr 8 and 0xff
+//                    tb = inPixels[index] and 0xff
+//                    redSum += tr.toDouble()
+//                    greenSum += tg.toDouble()
+//                    blueSum += tb.toDouble()
+//                }
+//            }
+//            rgbmeans[0] = (redSum / total).toInt()
+//            rgbmeans[1] = (greenSum / total).toInt()
+//            rgbmeans[2] = (blueSum / total).toInt()
+//
+//            // adjust contrast and brightness algorithm, here
+//            for (row in 0 until height) {
+//                var ta = 0
+//                var tr = 0
+//                var tg = 0
+//                var tb = 0
+//                for (col in 0 until width) {
+//                    index = row * width + col
+//                    ta = inPixels[index] shr 24 and 0xff
+//                    tr = inPixels[index] shr 16 and 0xff
+//                    tg = inPixels[index] shr 8 and 0xff
+//                    tb = inPixels[index] and 0xff
+//
+//                    // remove means
+//                    tr -= rgbmeans[0]
+//                    tg -= rgbmeans[1]
+//                    tb -= rgbmeans[2]
+//
+//                    // adjust contrast now !!!
+//                    tr = (tr * c).toInt()
+//                    tg = (tg * c).toInt()
+//                    tb = (tb * c).toInt()
+//
+//                    // adjust brightness
+//                    tr += (rgbmeans[0] * b).toInt()
+//                    tg += (rgbmeans[1] * b).toInt()
+//                    tb += (rgbmeans[2] * b).toInt()
+//                    outPixels[index] = (ta shl 24 or (clamp(tr) shl 16)
+//                            or (clamp(tg) shl 8) or clamp(tb))
+//                }
+//            }
+//            val dest: Bitmap
+//            dest = try {
+//                Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
+//            } catch (e: OutOfMemoryError) {
+//                // TODO Auto-generated catch block
+//                e.printStackTrace()
+//                return null
+//            }
+//            dest.setPixels(outPixels, 0, width, 0, 0, width, height)
+//            dest
+//        }
+//    }
+//
+//    fun clamp(value: Int): Int {
+//        return if (value > 255) 255 else if (value < 0) 0 else value
+//    }
+//
+//
+//    /**
+//     * 二值化处理
+//     *
+//     * @param img
+//     * @return
+//     */
+//    fun binarization(img: Bitmap?): Bitmap? {
+//        if (img == null) {
+//            return null
+//        }
+//        val width = img.width
+//        val height = img.height
+//        val area = width * height
+//        val gray = Array(width) { IntArray(height) }
+//        var average = 0 // 灰度平均值
+//        var graysum = 0
+//        var graymean = 0
+//        var grayfrontmean = 0
+//        var graybackmean = 0
+//        var pixelGray: Int
+//        var front = 0
+//        var back = 0
+//        val pix = IntArray(width * height)
+//        img.getPixels(pix, 0, width, 0, 0, width, height)
+//        for (i in 1 until width) { // 不算边界行和列,为避免越界
+//            for (j in 1 until height) {
+//                val x = j * width + i
+//                val r = pix[x] shr 16 and 0xff
+//                val g = pix[x] shr 8 and 0xff
+//                val b = pix[x] and 0xff
+//                pixelGray = (0.3 * r + 0.59 * g + 0.11 * b).toInt() // 计算每个坐标点的灰度
+//                gray[i][j] = (pixelGray shl 16) + (pixelGray shl 8) + pixelGray
+//                graysum += pixelGray
+//            }
+//        }
+//        graymean = graysum / area // 整个图的灰度平均值
+//        average = graymean
+//        for (i in 0 until width) { // 计算整个图的二值化阈值
+//            for (j in 0 until height) {
+//                if (gray[i][j] and 0x0000ff < graymean) {
+//                    graybackmean += gray[i][j] and 0x0000ff
+//                    back++
+//                } else {
+//                    grayfrontmean += gray[i][j] and 0x0000ff
+//                    front++
+//                }
+//            }
+//        }
+//        var frontvalue = 0
+//        var backvalue = 0
+//        if (front <= 0 || back <= 0) {
+//            return img
+//        }
+//        frontvalue = grayfrontmean / front // 前景中心
+//        backvalue = graybackmean / back // 背景中心
+//        val G = FloatArray(frontvalue - backvalue + 1) // 方差数组
+//        var s = 0
+//        for (i1 in backvalue until frontvalue + 1)  // 以前景中心和背景中心为区间采用大津法算法(OTSU算法)
+//        {
+//            back = 0
+//            front = 0
+//            grayfrontmean = 0
+//            graybackmean = 0
+//            for (i in 0 until width) {
+//                for (j in 0 until height) {
+//                    if (gray[i][j] and 0x0000ff < i1 + 1) {
+//                        graybackmean += gray[i][j] and 0x0000ff
+//                        back++
+//                    } else {
+//                        grayfrontmean += gray[i][j] and 0x0000ff
+//                        front++
+//                    }
+//                }
+//            }
+//            if (front <= 0 || back <= 0) {
+//                return img
+//            }
+//            grayfrontmean = grayfrontmean / front
+//            graybackmean = graybackmean / back
+//            G[s] = (back.toFloat() / area * (graybackmean - average)
+//                    * (graybackmean - average)) + (front.toFloat() / area
+//                    * (grayfrontmean - average) * (grayfrontmean - average))
+//            s++
+//        }
+//        var max = G[0]
+//        var index = 0
+//        for (i in 1 until frontvalue - backvalue + 1) {
+//            if (max < G[i]) {
+//                max = G[i]
+//                index = i
+//            }
+//        }
+//        for (i in 0 until width) {
+//            for (j in 0 until height) {
+//                val `in` = j * width + i
+//                if (gray[i][j] and 0x0000ff < index + backvalue) {
+//                    pix[`in`] = Color.rgb(0, 0, 0)
+//                } else {
+//                    pix[`in`] = Color.rgb(255, 255, 255)
+//                }
+//            }
+//        }
+//        val temp = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565)
+//        temp.setPixels(pix, 0, width, 0, 0, width, height)
+//        return temp
+//    }
+//
+//    fun getTintDrawableList(context: Context?, srcResourceId: Int, colorResId: Int, colorPressedResId: Int): Drawable? {
+//        var drawable = ContextCompat.getDrawable(context!!, srcResourceId)
+//        val colors = intArrayOf(
+//            ContextCompat.getColor(context, colorPressedResId),
+//            ContextCompat.getColor(context, colorPressedResId),
+//            ContextCompat.getColor(context, colorResId)
+//        )
+//        val states = arrayOfNulls<IntArray>(3)
+//        states[0] = intArrayOf(R.attr.state_pressed)
+//        states[1] = intArrayOf(R.attr.state_selected)
+//        states[2] = intArrayOf()
+//        val colorList = ColorStateList(states, colors)
+//        val stateListDrawable = StateListDrawable()
+//        stateListDrawable.addState(states[0], drawable) //注意顺序
+//        stateListDrawable.addState(states[1], drawable)
+//        stateListDrawable.addState(states[2], drawable)
+//        val state = stateListDrawable.constantState
+//        drawable = DrawableCompat.wrap(state?.newDrawable() ?: stateListDrawable).mutate()
+//        DrawableCompat.setTintList(drawable, colorList)
+//        drawable.setBounds(0, 0, drawable.minimumWidth, drawable.minimumHeight)
+//        return drawable
+//    }
+//
+//    /**
+//     * @param:
+//     * @author: luozhipeng
+//     * @Decreption:通过tint实现图片selector
+//     * @create at: 8/8/16 16:17
+//     */
+//    fun setTintDrawableList(context: Context?, view: View?, srcResourceId: Int, colorResId: Int, colorPressedResId: Int) {
+//        var drawable = ContextCompat.getDrawable(context!!, srcResourceId)
+//        val colors = intArrayOf(
+//            ContextCompat.getColor(context, colorPressedResId),
+//            ContextCompat.getColor(context, colorPressedResId),
+//            ContextCompat.getColor(context, colorResId)
+//        )
+//        val states = arrayOfNulls<IntArray>(3)
+//        states[0] = intArrayOf(R.attr.state_pressed)
+//        states[1] = intArrayOf(R.attr.state_selected)
+//        states[2] = intArrayOf()
+//        val colorList = ColorStateList(states, colors)
+//        val stateListDrawable = StateListDrawable()
+//        stateListDrawable.addState(states[0], drawable) //注意顺序
+//        stateListDrawable.addState(states[1], drawable)
+//        stateListDrawable.addState(states[2], drawable)
+//        val state = stateListDrawable.constantState
+//        drawable = DrawableCompat.wrap(state?.newDrawable() ?: stateListDrawable).mutate()
+//        DrawableCompat.setTintList(drawable, colorList)
+//        drawable.setBounds(0, 0, drawable.minimumWidth, drawable.minimumHeight)
+//        if (view is AppCompatImageView) {
+//            view.setImageDrawable(drawable)
+//        }
+//        if (view is ImageView) {
+//            view.setImageDrawable(drawable)
+//        }
+//        if (view is AppCompatImageButton) {
+//            view.setImageDrawable(drawable)
+//        }
+//    }
+//
+//
+//    /**
+//     * bitmap文件是否可用
+//     */
+//    fun bitmapFileIsAvailable(bitmapFilePath : String) : Boolean{
+//        return try {
+//            val options = BitmapFactory.Options()
+//            options.inJustDecodeBounds = false
+//            val bitmap = BitmapFactory.decodeFile(bitmapFilePath, options)
+//            bitmap != null
+//        }catch (e : Exception){
+//            false
+//        }
+//    }
+//
+//}

+ 1 - 0
lib_common/src/main/res/values/strings.xml

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

+ 33 - 0
settings.gradle

@@ -0,0 +1,33 @@
+pluginManagement {
+    repositories {
+        gradlePluginPortal()
+        google()
+        mavenCentral()
+        jcenter()
+        maven { url 'https://jitpack.io' }
+        maven { url 'https://maven.aliyun.com/nexus/content/repositories/releases/' }
+        //GroMore SDK依赖
+        maven { url "https://artifact.bytedance.com/repository/pangle" }
+        maven { url "https://dl-maven-android.mintegral.com/repository/mbridge_android_sdk_support/" }
+
+    }
+}
+dependencyResolutionManagement {
+    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+    repositories {
+        google()
+        mavenCentral()
+        google()
+        mavenCentral()
+        jcenter()
+        maven { url 'https://jitpack.io' }
+        maven { url 'https://maven.aliyun.com/nexus/content/repositories/releases/' }
+        //GroMore SDK依赖
+        maven { url "https://artifact.bytedance.com/repository/pangle" }
+        maven { url "https://dl-maven-android.mintegral.com/repository/mbridge_android_sdk_support/" }
+
+    }
+}
+rootProject.name = "Lib"
+include ':app'
+include ':lib_common'