Linjiajia
2023-03-09 99f403b54a2633a07174144ce2f6011e39aa19f7
mvvm基础框架
73个文件已添加
4015 ■■■■■ 已修改文件
.gitignore 88 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.idea/.gitignore 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.idea/compiler.xml 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.idea/dbnavigator.xml 420 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.idea/inspectionProfiles/Project_Default.xml 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.idea/misc.xml 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/.gitignore 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/build.gradle 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/proguard-rules.pro 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/androidTest/java/com/application/zhangshi_app_android/ExampleInstrumentedTest.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/AndroidManifest.xml 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/application/zhangshi_app_android/MyApplication.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/application/zhangshi_app_android/config/BaseConfig.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/application/zhangshi_app_android/model/DataRepository.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/application/zhangshi_app_android/model/source/HttpDataSource.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/application/zhangshi_app_android/model/source/LocalDataSource.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/application/zhangshi_app_android/model/source/http/ApiService.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/application/zhangshi_app_android/model/source/http/HttpDataSourceImpl.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/application/zhangshi_app_android/model/source/local/LocalDataSourceImpl.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/application/zhangshi_app_android/view/MainActivity.java 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/application/zhangshi_app_android/viewmodel/MainViewModel.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/drawable-v24/ic_launcher_foreground.xml 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/drawable/ic_launcher_background.xml 170 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/layout/activity_main.xml 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/mipmap-anydpi-v33/ic_launcher.xml 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/mipmap-hdpi/ic_launcher.webp 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/mipmap-hdpi/ic_launcher_round.webp 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/mipmap-mdpi/ic_launcher.webp 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/mipmap-mdpi/ic_launcher_round.webp 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/mipmap-xhdpi/ic_launcher.webp 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/mipmap-xxhdpi/ic_launcher.webp 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/values-night/themes.xml 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/values/colors.xml 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/values/strings.xml 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/values/themes.xml 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/xml/backup_rules.xml 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/xml/data_extraction_rules.xml 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/test/java/com/application/zhangshi_app_android/ExampleUnitTest.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app_base/.gitignore 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app_base/build.gradle 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app_base/consumer-rules.pro 补丁 | 查看 | 原始文档 | blame | 历史
app_base/proguard-rules.pro 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app_base/src/androidTest/java/com/android/app_base/ExampleInstrumentedTest.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app_base/src/main/AndroidManifest.xml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app_base/src/main/java/com/android/app_base/base/BaseApplication.java 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app_base/src/main/java/com/android/app_base/base/StateViewEnum.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app_base/src/main/java/com/android/app_base/base/adapter/BaseRVAdapter.java 481 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app_base/src/main/java/com/android/app_base/base/model/BaseModel.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app_base/src/main/java/com/android/app_base/base/view/BaseActivity.java 177 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app_base/src/main/java/com/android/app_base/base/view/BaseFragment.java 191 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app_base/src/main/java/com/android/app_base/base/viewmodel/BaseViewModel.java 120 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app_base/src/main/java/com/android/app_base/helper/DoubleClickHelper.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app_base/src/main/java/com/android/app_base/http/OkHttpHelper.java 96 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app_base/src/main/java/com/android/app_base/http/ResultData.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app_base/src/main/java/com/android/app_base/http/RetrofitManager.java 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app_base/src/main/java/com/android/app_base/http/interceptor/CacheInterceptor.java 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app_base/src/main/java/com/android/app_base/manager/AppManager.java 212 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app_base/src/main/java/com/android/app_base/utils/SPUtils.java 282 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app_base/src/main/java/com/android/app_base/utils/ToastUtils.java 429 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app_base/src/test/java/com/android/app_base/ExampleUnitTest.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
build.gradle 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
gradle.properties 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
gradle/wrapper/gradle-wrapper.jar 补丁 | 查看 | 原始文档 | blame | 历史
gradle/wrapper/gradle-wrapper.properties 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
gradlew 185 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
gradlew.bat 89 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
settings.gradle 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.gitignore
New file
@@ -0,0 +1,88 @@
# Built application files
*.apk
*.aar
*.ap_
*.aab
# Files for the ART/Dalvik VM
*.dex
# Java class files
*.class
# Generated files
bin/
gen/
out/
#  Uncomment the following line in case you need and you don't have the release build type files in your app
# release/
# Gradle files
.gradle/
build/
# Local configuration file (sdk path, etc)
local.properties
# Proguard folder generated by Eclipse
proguard/
# Log Files
*.log
# Android Studio Navigation editor temp files
.navigation/
# Android Studio captures folder
captures/
# IntelliJ
*.iml
.idea/workspace.xml
.idea/tasks.xml
.idea/gradle.xml
.idea/assetWizardSettings.xml
.idea/dictionaries
.idea/libraries
# Android Studio 3 in .gitignore file.
.idea/caches
.idea/modules.xml
# Comment next line if keeping position of elements in Navigation Editor is relevant for you
.idea/navEditor.xml
# Keystore files
# Uncomment the following lines if you do not want to check your keystore files in.
#*.jks
#*.keystore
# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild
.cxx/
# Google Services (e.g. APIs or Firebase)
# google-services.json
# Freeline
freeline.py
freeline/
freeline_project_description.json
# fastlane
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
fastlane/test_output
fastlane/readme.md
# Version control
vcs.xml
# lint
lint/intermediates/
lint/generated/
lint/outputs/
lint/tmp/
# lint/reports/
# Android Profiling
*.hprof
.idea/.gitignore
New file
@@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml
.idea/compiler.xml
New file
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="CompilerConfiguration">
    <bytecodeTargetLevel target="11" />
  </component>
</project>
.idea/dbnavigator.xml
New file
@@ -0,0 +1,420 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="DBNavigator.Project.DataEditorManager">
    <record-view-column-sorting-type value="BY_INDEX" />
    <value-preview-text-wrapping value="false" />
    <value-preview-pinned value="false" />
  </component>
  <component name="DBNavigator.Project.DatabaseBrowserManager">
    <autoscroll-to-editor value="false" />
    <autoscroll-from-editor value="true" />
    <show-object-properties value="true" />
    <loaded-nodes />
  </component>
  <component name="DBNavigator.Project.DatabaseEditorStateManager">
    <last-used-providers />
  </component>
  <component name="DBNavigator.Project.DatabaseFileManager">
    <open-files />
  </component>
  <component name="DBNavigator.Project.Settings">
    <connections />
    <browser-settings>
      <general>
        <display-mode value="TABBED" />
        <navigation-history-size value="100" />
        <show-object-details value="false" />
      </general>
      <filters>
        <object-type-filter>
          <object-type name="SCHEMA" enabled="true" />
          <object-type name="USER" enabled="true" />
          <object-type name="ROLE" enabled="true" />
          <object-type name="PRIVILEGE" enabled="true" />
          <object-type name="CHARSET" enabled="true" />
          <object-type name="TABLE" enabled="true" />
          <object-type name="VIEW" enabled="true" />
          <object-type name="MATERIALIZED_VIEW" enabled="true" />
          <object-type name="NESTED_TABLE" enabled="true" />
          <object-type name="COLUMN" enabled="true" />
          <object-type name="INDEX" enabled="true" />
          <object-type name="CONSTRAINT" enabled="true" />
          <object-type name="DATASET_TRIGGER" enabled="true" />
          <object-type name="DATABASE_TRIGGER" enabled="true" />
          <object-type name="SYNONYM" enabled="true" />
          <object-type name="SEQUENCE" enabled="true" />
          <object-type name="PROCEDURE" enabled="true" />
          <object-type name="FUNCTION" enabled="true" />
          <object-type name="PACKAGE" enabled="true" />
          <object-type name="TYPE" enabled="true" />
          <object-type name="TYPE_ATTRIBUTE" enabled="true" />
          <object-type name="ARGUMENT" enabled="true" />
          <object-type name="DIMENSION" enabled="true" />
          <object-type name="CLUSTER" enabled="true" />
          <object-type name="DBLINK" enabled="true" />
        </object-type-filter>
      </filters>
      <sorting>
        <object-type name="COLUMN" sorting-type="NAME" />
        <object-type name="FUNCTION" sorting-type="NAME" />
        <object-type name="PROCEDURE" sorting-type="NAME" />
        <object-type name="ARGUMENT" sorting-type="POSITION" />
        <object-type name="TYPE ATTRIBUTE" sorting-type="POSITION" />
      </sorting>
      <default-editors>
        <object-type name="VIEW" editor-type="SELECTION" />
        <object-type name="PACKAGE" editor-type="SELECTION" />
        <object-type name="TYPE" editor-type="SELECTION" />
      </default-editors>
    </browser-settings>
    <navigation-settings>
      <lookup-filters>
        <lookup-objects>
          <object-type name="SCHEMA" enabled="true" />
          <object-type name="USER" enabled="false" />
          <object-type name="ROLE" enabled="false" />
          <object-type name="PRIVILEGE" enabled="false" />
          <object-type name="CHARSET" enabled="false" />
          <object-type name="TABLE" enabled="true" />
          <object-type name="VIEW" enabled="true" />
          <object-type name="MATERIALIZED VIEW" enabled="true" />
          <object-type name="INDEX" enabled="true" />
          <object-type name="CONSTRAINT" enabled="true" />
          <object-type name="DATASET TRIGGER" enabled="true" />
          <object-type name="DATABASE TRIGGER" enabled="true" />
          <object-type name="SYNONYM" enabled="false" />
          <object-type name="SEQUENCE" enabled="true" />
          <object-type name="PROCEDURE" enabled="true" />
          <object-type name="FUNCTION" enabled="true" />
          <object-type name="PACKAGE" enabled="true" />
          <object-type name="TYPE" enabled="true" />
          <object-type name="DIMENSION" enabled="false" />
          <object-type name="CLUSTER" enabled="false" />
          <object-type name="DBLINK" enabled="true" />
        </lookup-objects>
        <force-database-load value="false" />
        <prompt-connection-selection value="true" />
        <prompt-schema-selection value="true" />
      </lookup-filters>
    </navigation-settings>
    <dataset-grid-settings>
      <general>
        <enable-zooming value="true" />
        <enable-column-tooltip value="true" />
      </general>
      <sorting>
        <nulls-first value="true" />
        <max-sorting-columns value="4" />
      </sorting>
      <audit-columns>
        <column-names value="" />
        <visible value="true" />
        <editable value="false" />
      </audit-columns>
    </dataset-grid-settings>
    <dataset-editor-settings>
      <text-editor-popup>
        <active value="false" />
        <active-if-empty value="false" />
        <data-length-threshold value="100" />
        <popup-delay value="1000" />
      </text-editor-popup>
      <values-actions-popup>
        <show-popup-button value="true" />
        <element-count-threshold value="1000" />
        <data-length-threshold value="250" />
      </values-actions-popup>
      <general>
        <fetch-block-size value="100" />
        <fetch-timeout value="30" />
        <trim-whitespaces value="true" />
        <convert-empty-strings-to-null value="true" />
        <select-content-on-cell-edit value="true" />
        <large-value-preview-active value="true" />
      </general>
      <filters>
        <prompt-filter-dialog value="true" />
        <default-filter-type value="BASIC" />
      </filters>
      <qualified-text-editor text-length-threshold="300">
        <content-types>
          <content-type name="Text" enabled="true" />
          <content-type name="Properties" enabled="true" />
          <content-type name="XML" enabled="true" />
          <content-type name="DTD" enabled="true" />
          <content-type name="HTML" enabled="true" />
          <content-type name="XHTML" enabled="true" />
          <content-type name="Java" enabled="true" />
          <content-type name="SQL" enabled="true" />
          <content-type name="PL/SQL" enabled="true" />
          <content-type name="JSON" enabled="true" />
          <content-type name="JSON5" enabled="true" />
          <content-type name="Groovy" enabled="true" />
          <content-type name="AIDL" enabled="true" />
          <content-type name="YAML" enabled="true" />
          <content-type name="Manifest" enabled="true" />
        </content-types>
      </qualified-text-editor>
      <record-navigation>
        <navigation-target value="VIEWER" />
      </record-navigation>
    </dataset-editor-settings>
    <code-editor-settings>
      <general>
        <show-object-navigation-gutter value="false" />
        <show-spec-declaration-navigation-gutter value="true" />
        <enable-spellchecking value="true" />
        <enable-reference-spellchecking value="false" />
      </general>
      <confirmations>
        <save-changes value="false" />
        <revert-changes value="true" />
      </confirmations>
    </code-editor-settings>
    <code-completion-settings>
      <filters>
        <basic-filter>
          <filter-element type="RESERVED_WORD" id="keyword" selected="true" />
          <filter-element type="RESERVED_WORD" id="function" selected="true" />
          <filter-element type="RESERVED_WORD" id="parameter" selected="true" />
          <filter-element type="RESERVED_WORD" id="datatype" selected="true" />
          <filter-element type="RESERVED_WORD" id="exception" selected="true" />
          <filter-element type="OBJECT" id="schema" selected="true" />
          <filter-element type="OBJECT" id="role" selected="true" />
          <filter-element type="OBJECT" id="user" selected="true" />
          <filter-element type="OBJECT" id="privilege" selected="true" />
          <user-schema>
            <filter-element type="OBJECT" id="table" selected="true" />
            <filter-element type="OBJECT" id="view" selected="true" />
            <filter-element type="OBJECT" id="materialized view" selected="true" />
            <filter-element type="OBJECT" id="index" selected="true" />
            <filter-element type="OBJECT" id="constraint" selected="true" />
            <filter-element type="OBJECT" id="trigger" selected="true" />
            <filter-element type="OBJECT" id="synonym" selected="false" />
            <filter-element type="OBJECT" id="sequence" selected="true" />
            <filter-element type="OBJECT" id="procedure" selected="true" />
            <filter-element type="OBJECT" id="function" selected="true" />
            <filter-element type="OBJECT" id="package" selected="true" />
            <filter-element type="OBJECT" id="type" selected="true" />
            <filter-element type="OBJECT" id="dimension" selected="true" />
            <filter-element type="OBJECT" id="cluster" selected="true" />
            <filter-element type="OBJECT" id="dblink" selected="true" />
          </user-schema>
          <public-schema>
            <filter-element type="OBJECT" id="table" selected="false" />
            <filter-element type="OBJECT" id="view" selected="false" />
            <filter-element type="OBJECT" id="materialized view" selected="false" />
            <filter-element type="OBJECT" id="index" selected="false" />
            <filter-element type="OBJECT" id="constraint" selected="false" />
            <filter-element type="OBJECT" id="trigger" selected="false" />
            <filter-element type="OBJECT" id="synonym" selected="false" />
            <filter-element type="OBJECT" id="sequence" selected="false" />
            <filter-element type="OBJECT" id="procedure" selected="false" />
            <filter-element type="OBJECT" id="function" selected="false" />
            <filter-element type="OBJECT" id="package" selected="false" />
            <filter-element type="OBJECT" id="type" selected="false" />
            <filter-element type="OBJECT" id="dimension" selected="false" />
            <filter-element type="OBJECT" id="cluster" selected="false" />
            <filter-element type="OBJECT" id="dblink" selected="false" />
          </public-schema>
          <any-schema>
            <filter-element type="OBJECT" id="table" selected="true" />
            <filter-element type="OBJECT" id="view" selected="true" />
            <filter-element type="OBJECT" id="materialized view" selected="true" />
            <filter-element type="OBJECT" id="index" selected="true" />
            <filter-element type="OBJECT" id="constraint" selected="true" />
            <filter-element type="OBJECT" id="trigger" selected="true" />
            <filter-element type="OBJECT" id="synonym" selected="true" />
            <filter-element type="OBJECT" id="sequence" selected="true" />
            <filter-element type="OBJECT" id="procedure" selected="true" />
            <filter-element type="OBJECT" id="function" selected="true" />
            <filter-element type="OBJECT" id="package" selected="true" />
            <filter-element type="OBJECT" id="type" selected="true" />
            <filter-element type="OBJECT" id="dimension" selected="true" />
            <filter-element type="OBJECT" id="cluster" selected="true" />
            <filter-element type="OBJECT" id="dblink" selected="true" />
          </any-schema>
        </basic-filter>
        <extended-filter>
          <filter-element type="RESERVED_WORD" id="keyword" selected="true" />
          <filter-element type="RESERVED_WORD" id="function" selected="true" />
          <filter-element type="RESERVED_WORD" id="parameter" selected="true" />
          <filter-element type="RESERVED_WORD" id="datatype" selected="true" />
          <filter-element type="RESERVED_WORD" id="exception" selected="true" />
          <filter-element type="OBJECT" id="schema" selected="true" />
          <filter-element type="OBJECT" id="user" selected="true" />
          <filter-element type="OBJECT" id="role" selected="true" />
          <filter-element type="OBJECT" id="privilege" selected="true" />
          <user-schema>
            <filter-element type="OBJECT" id="table" selected="true" />
            <filter-element type="OBJECT" id="view" selected="true" />
            <filter-element type="OBJECT" id="materialized view" selected="true" />
            <filter-element type="OBJECT" id="index" selected="true" />
            <filter-element type="OBJECT" id="constraint" selected="true" />
            <filter-element type="OBJECT" id="trigger" selected="true" />
            <filter-element type="OBJECT" id="synonym" selected="true" />
            <filter-element type="OBJECT" id="sequence" selected="true" />
            <filter-element type="OBJECT" id="procedure" selected="true" />
            <filter-element type="OBJECT" id="function" selected="true" />
            <filter-element type="OBJECT" id="package" selected="true" />
            <filter-element type="OBJECT" id="type" selected="true" />
            <filter-element type="OBJECT" id="dimension" selected="true" />
            <filter-element type="OBJECT" id="cluster" selected="true" />
            <filter-element type="OBJECT" id="dblink" selected="true" />
          </user-schema>
          <public-schema>
            <filter-element type="OBJECT" id="table" selected="true" />
            <filter-element type="OBJECT" id="view" selected="true" />
            <filter-element type="OBJECT" id="materialized view" selected="true" />
            <filter-element type="OBJECT" id="index" selected="true" />
            <filter-element type="OBJECT" id="constraint" selected="true" />
            <filter-element type="OBJECT" id="trigger" selected="true" />
            <filter-element type="OBJECT" id="synonym" selected="true" />
            <filter-element type="OBJECT" id="sequence" selected="true" />
            <filter-element type="OBJECT" id="procedure" selected="true" />
            <filter-element type="OBJECT" id="function" selected="true" />
            <filter-element type="OBJECT" id="package" selected="true" />
            <filter-element type="OBJECT" id="type" selected="true" />
            <filter-element type="OBJECT" id="dimension" selected="true" />
            <filter-element type="OBJECT" id="cluster" selected="true" />
            <filter-element type="OBJECT" id="dblink" selected="true" />
          </public-schema>
          <any-schema>
            <filter-element type="OBJECT" id="table" selected="true" />
            <filter-element type="OBJECT" id="view" selected="true" />
            <filter-element type="OBJECT" id="materialized view" selected="true" />
            <filter-element type="OBJECT" id="index" selected="true" />
            <filter-element type="OBJECT" id="constraint" selected="true" />
            <filter-element type="OBJECT" id="trigger" selected="true" />
            <filter-element type="OBJECT" id="synonym" selected="true" />
            <filter-element type="OBJECT" id="sequence" selected="true" />
            <filter-element type="OBJECT" id="procedure" selected="true" />
            <filter-element type="OBJECT" id="function" selected="true" />
            <filter-element type="OBJECT" id="package" selected="true" />
            <filter-element type="OBJECT" id="type" selected="true" />
            <filter-element type="OBJECT" id="dimension" selected="true" />
            <filter-element type="OBJECT" id="cluster" selected="true" />
            <filter-element type="OBJECT" id="dblink" selected="true" />
          </any-schema>
        </extended-filter>
      </filters>
      <sorting enabled="true">
        <sorting-element type="RESERVED_WORD" id="keyword" />
        <sorting-element type="RESERVED_WORD" id="datatype" />
        <sorting-element type="OBJECT" id="column" />
        <sorting-element type="OBJECT" id="table" />
        <sorting-element type="OBJECT" id="view" />
        <sorting-element type="OBJECT" id="materialized view" />
        <sorting-element type="OBJECT" id="index" />
        <sorting-element type="OBJECT" id="constraint" />
        <sorting-element type="OBJECT" id="trigger" />
        <sorting-element type="OBJECT" id="synonym" />
        <sorting-element type="OBJECT" id="sequence" />
        <sorting-element type="OBJECT" id="procedure" />
        <sorting-element type="OBJECT" id="function" />
        <sorting-element type="OBJECT" id="package" />
        <sorting-element type="OBJECT" id="type" />
        <sorting-element type="OBJECT" id="dimension" />
        <sorting-element type="OBJECT" id="cluster" />
        <sorting-element type="OBJECT" id="dblink" />
        <sorting-element type="OBJECT" id="schema" />
        <sorting-element type="OBJECT" id="role" />
        <sorting-element type="OBJECT" id="user" />
        <sorting-element type="RESERVED_WORD" id="function" />
        <sorting-element type="RESERVED_WORD" id="parameter" />
      </sorting>
      <format>
        <enforce-code-style-case value="true" />
      </format>
    </code-completion-settings>
    <execution-engine-settings>
      <statement-execution>
        <fetch-block-size value="100" />
        <execution-timeout value="20" />
        <debug-execution-timeout value="600" />
        <focus-result value="false" />
        <prompt-execution value="false" />
      </statement-execution>
      <script-execution>
        <command-line-interfaces />
        <execution-timeout value="300" />
      </script-execution>
      <method-execution>
        <execution-timeout value="30" />
        <debug-execution-timeout value="600" />
        <parameter-history-size value="10" />
      </method-execution>
    </execution-engine-settings>
    <operation-settings>
      <transactions>
        <uncommitted-changes>
          <on-project-close value="ASK" />
          <on-disconnect value="ASK" />
          <on-autocommit-toggle value="ASK" />
        </uncommitted-changes>
        <multiple-uncommitted-changes>
          <on-commit value="ASK" />
          <on-rollback value="ASK" />
        </multiple-uncommitted-changes>
      </transactions>
      <session-browser>
        <disconnect-session value="ASK" />
        <kill-session value="ASK" />
        <reload-on-filter-change value="false" />
      </session-browser>
      <compiler>
        <compile-type value="KEEP" />
        <compile-dependencies value="ASK" />
        <always-show-controls value="false" />
      </compiler>
      <debugger>
        <debugger-type value="ASK" />
        <use-generic-runners value="true" />
      </debugger>
    </operation-settings>
    <ddl-file-settings>
      <extensions>
        <mapping file-type-id="VIEW" extensions="vw" />
        <mapping file-type-id="TRIGGER" extensions="trg" />
        <mapping file-type-id="PROCEDURE" extensions="prc" />
        <mapping file-type-id="FUNCTION" extensions="fnc" />
        <mapping file-type-id="PACKAGE" extensions="pkg" />
        <mapping file-type-id="PACKAGE_SPEC" extensions="pks" />
        <mapping file-type-id="PACKAGE_BODY" extensions="pkb" />
        <mapping file-type-id="TYPE" extensions="tpe" />
        <mapping file-type-id="TYPE_SPEC" extensions="tps" />
        <mapping file-type-id="TYPE_BODY" extensions="tpb" />
      </extensions>
      <general>
        <lookup-ddl-files value="true" />
        <create-ddl-files value="false" />
        <synchronize-ddl-files value="true" />
        <use-qualified-names value="false" />
        <make-scripts-rerunnable value="true" />
      </general>
    </ddl-file-settings>
    <general-settings>
      <regional-settings>
        <date-format value="MEDIUM" />
        <number-format value="UNGROUPED" />
        <locale value="SYSTEM_DEFAULT" />
        <use-custom-formats value="false" />
      </regional-settings>
      <environment>
        <environment-types>
          <environment-type id="development" name="Development" description="Development environment" color="-2430209/-12296320" readonly-code="false" readonly-data="false" />
          <environment-type id="integration" name="Integration" description="Integration environment" color="-2621494/-12163514" readonly-code="true" readonly-data="false" />
          <environment-type id="production" name="Production" description="Productive environment" color="-11574/-10271420" readonly-code="true" readonly-data="true" />
          <environment-type id="other" name="Other" description="" color="-1576/-10724543" readonly-code="false" readonly-data="false" />
        </environment-types>
        <visibility-settings>
          <connection-tabs value="true" />
          <dialog-headers value="true" />
          <object-editor-tabs value="true" />
          <script-editor-tabs value="false" />
          <execution-result-tabs value="true" />
        </visibility-settings>
      </environment>
    </general-settings>
  </component>
</project>
.idea/inspectionProfiles/Project_Default.xml
New file
@@ -0,0 +1,11 @@
<component name="InspectionProjectProfileManager">
  <profile version="1.0">
    <option name="myName" value="Project Default" />
    <inspection_tool class="AutoCloseableResource" enabled="true" level="WARNING" enabled_by_default="true">
      <option name="METHOD_MATCHER_CONFIG" value="java.util.Formatter,format,java.io.Writer,append,com.google.common.base.Preconditions,checkNotNull,org.hibernate.Session,close,java.io.PrintWriter,printf,java.io.PrintStream,printf,okhttp3.Interceptor.Chain,proceed" />
    </inspection_tool>
    <inspection_tool class="JavadocDeclaration" enabled="true" level="WARNING" enabled_by_default="true">
      <option name="ADDITIONAL_TAGS" value="date,desc" />
    </inspection_tool>
  </profile>
</component>
.idea/misc.xml
New file
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="ExternalStorageConfigurationManager" enabled="true" />
  <component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="1.8" project-jdk-type="JavaSDK">
    <output url="file://$PROJECT_DIR$/build/classes" />
  </component>
  <component name="ProjectType">
    <option name="id" value="Android" />
  </component>
</project>
app/.gitignore
New file
@@ -0,0 +1 @@
/build
app/build.gradle
New file
@@ -0,0 +1,61 @@
apply plugin: 'com.android.application'
android {
    namespace 'com.application.zhangshi_app_android'
    compileSdk 33
    defaultConfig {
        applicationId "com.application.zhangshi_app_android"
        minSdk 24
        targetSdk 33
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
//    signingConfigs {
//        release {
//            storeFile file('xxx.keystore')
//            storePassword '123456'
//            keyAlias 'alias'
//            keyPassword '123456'
//        }
//    }
    buildTypes {
        release {
            debuggable true
            minifyEnabled true //启用Proguard
            shrinkResources true //是否清理无用资源,依赖于minifyEnabled
            zipAlignEnabled true //是否启用zipAlign压缩
//            signingConfig signingConfigs.release
//            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
        debug {
            debuggable true
            minifyEnabled false  //不启用Proguard
            shrinkResources false //是否清理无用资源,依赖于minifyEnabled
            zipAlignEnabled false //是否启用zipAlign压缩
//            signingConfig signingConfigs.release
        }
    }
    buildFeatures{
        dataBinding = true
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}
dependencies {
    implementation 'androidx.appcompat:appcompat:1.6.1'
    implementation 'com.google.android.material:material:1.8.0'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.5'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
    implementation project(path: ':app_base')
}
app/proguard-rules.pro
New file
@@ -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
app/src/androidTest/java/com/application/zhangshi_app_android/ExampleInstrumentedTest.java
New file
@@ -0,0 +1,26 @@
package com.application.zhangshi_app_android;
import android.content.Context;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
 * Instrumented test, which will execute on an Android device.
 *
 * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
 */
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
    @Test
    public void useAppContext() {
        // Context of the app under test.
        Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
        assertEquals("com.application.zhangshi_app_android", appContext.getPackageName());
    }
}
app/src/main/AndroidManifest.xml
New file
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <uses-permission android:name="android.permission.INTERNET" />
    <application
        android:name=".MyApplication"
        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:supportsRtl="true"
        android:theme="@style/Theme.Zhangshi_app_android"
        tools:targetApi="31" >
        <activity
            android:name=".view.MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>
app/src/main/java/com/application/zhangshi_app_android/MyApplication.java
New file
@@ -0,0 +1,11 @@
package com.application.zhangshi_app_android;
import com.android.app_base.base.BaseApplication;
/**
 * @author Ljj
 * @date 2023.03.02. 17:51
 * @desc
 */
public class MyApplication extends BaseApplication {
}
app/src/main/java/com/application/zhangshi_app_android/config/BaseConfig.java
New file
@@ -0,0 +1,10 @@
package com.application.zhangshi_app_android.config;
/**
 * @author Ljj
 * @date 2023.03.02. 15:33
 * @desc
 */
public class BaseConfig {
    public final static String BASE_URL = "";
}
app/src/main/java/com/application/zhangshi_app_android/model/DataRepository.java
New file
@@ -0,0 +1,45 @@
package com.application.zhangshi_app_android.model;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import com.android.app_base.base.model.BaseModel;
import com.application.zhangshi_app_android.model.source.HttpDataSource;
import com.application.zhangshi_app_android.model.source.LocalDataSource;
import com.application.zhangshi_app_android.model.source.http.HttpDataSourceImpl;
import com.application.zhangshi_app_android.model.source.local.LocalDataSourceImpl;
/**
 * @author Ljj
 * @date 2023.03.01. 20:58
 * @desc 数据仓库,包含网络数据和本地数据
 */
public class DataRepository extends BaseModel implements HttpDataSource, LocalDataSource {
    private volatile static DataRepository INSTANCE = null;
    private final HttpDataSource mHttpDataSource;
    private final LocalDataSource mLocalDataSource;
    private DataRepository(@NonNull HttpDataSource httpDataSource,
                           @NonNull LocalDataSource localDataSource) {
        this.mHttpDataSource = httpDataSource;
        this.mLocalDataSource = localDataSource;
    }
    public static DataRepository getInstance() {
        if (INSTANCE == null) {
            synchronized (DataRepository.class) {
                if (INSTANCE == null) {
                    INSTANCE = new DataRepository(HttpDataSourceImpl.getInstance(), LocalDataSourceImpl.getInstance());
                }
            }
        }
        return INSTANCE;
    }
    @Override
    public void onCleared() {
        INSTANCE = null;
    }
}
app/src/main/java/com/application/zhangshi_app_android/model/source/HttpDataSource.java
New file
@@ -0,0 +1,9 @@
package com.application.zhangshi_app_android.model.source;
/**
 * @author Ljj
 * @date 2023.03.01. 21:18
 * @desc 网络请求数据源 接口
 */
public interface HttpDataSource {
}
app/src/main/java/com/application/zhangshi_app_android/model/source/LocalDataSource.java
New file
@@ -0,0 +1,9 @@
package com.application.zhangshi_app_android.model.source;
/**
 * @author Ljj
 * @date 2023.03.01. 21:19
 * @desc 本地数据源 接口
 */
public interface LocalDataSource {
}
app/src/main/java/com/application/zhangshi_app_android/model/source/http/ApiService.java
New file
@@ -0,0 +1,9 @@
package com.application.zhangshi_app_android.model.source.http;
/**
 * @author Ljj
 * @date 2023.03.01. 21:13
 * @desc 网络请求 service
 */
public interface ApiService {
}
app/src/main/java/com/application/zhangshi_app_android/model/source/http/HttpDataSourceImpl.java
New file
@@ -0,0 +1,38 @@
package com.application.zhangshi_app_android.model.source.http;
import com.android.app_base.http.RetrofitManager;
import com.application.zhangshi_app_android.config.BaseConfig;
import com.application.zhangshi_app_android.model.source.HttpDataSource;
/**
 * @author Ljj
 * @date 2023.03.01. 21:23
 * @desc 网络请求数据源 接口实现类
 *       配合 Retrofit 使用
 */
public class HttpDataSourceImpl implements HttpDataSource {
    private ApiService apiService;
    private volatile static HttpDataSourceImpl INSTANCE = null;
    private HttpDataSourceImpl() {
        this.apiService = RetrofitManager.getInstance().getRetrofit(BaseConfig.BASE_URL).create(ApiService.class);
    }
    public static HttpDataSourceImpl getInstance() {
        if (INSTANCE == null) {
            synchronized (HttpDataSourceImpl.class) {
                if (INSTANCE == null) {
                    INSTANCE = new HttpDataSourceImpl();
                }
            }
        }
        return INSTANCE;
    }
    public static void destroyInstance() {
        INSTANCE = null;
    }
}
app/src/main/java/com/application/zhangshi_app_android/model/source/local/LocalDataSourceImpl.java
New file
@@ -0,0 +1,32 @@
package com.application.zhangshi_app_android.model.source.local;
import com.application.zhangshi_app_android.model.source.LocalDataSource;
/**
 * @author Ljj
 * @date 2023.03.01. 21:21
 * @desc 本地数据源 接口实现类
 *       配合 Room框架 或 SP本地存储 使用
 */
public class LocalDataSourceImpl implements LocalDataSource {
    private volatile static LocalDataSourceImpl INSTANCE = null;
    public static LocalDataSourceImpl getInstance() {
        if (INSTANCE == null) {
            synchronized (LocalDataSourceImpl.class) {
                if (INSTANCE == null) {
                    INSTANCE = new LocalDataSourceImpl();
                }
            }
        }
        return INSTANCE;
    }
    public static void destroyInstance() {
        INSTANCE = null;
    }
    private LocalDataSourceImpl() {
    }
}
app/src/main/java/com/application/zhangshi_app_android/view/MainActivity.java
New file
@@ -0,0 +1,55 @@
package com.application.zhangshi_app_android.view;
import com.android.app_base.base.view.BaseActivity;
import com.android.app_base.base.viewmodel.BaseViewModel;
import com.android.app_base.helper.DoubleClickHelper;
import com.android.app_base.manager.AppManager;
import com.android.app_base.utils.ToastUtils;
import com.application.zhangshi_app_android.BR;
import com.application.zhangshi_app_android.R;
import com.application.zhangshi_app_android.databinding.ActivityMainBinding;
import com.application.zhangshi_app_android.viewmodel.MainViewModel;
public class MainActivity extends BaseActivity<ActivityMainBinding, MainViewModel> {
    @Override
    public int getLayoutId() {
        return R.layout.activity_main;
    }
    @Override
    public int getVariableId() {
        return BR.viewModel;
    }
    @Override
    public void initParam() {
    }
    @Override
    public void initView() {
    }
    @Override
    public void initData() {
    }
    @Override
    public void initLiveDataObserve() {
    }
    @Override
    public void onBackPressed() {
        if (DoubleClickHelper.isOnDoubleClick()) {
            moveTaskToBack(false);
            // 进行内存优化,销毁掉所有的界面
            AppManager.getAppManager().finishAllActivity();
        } else {
            ToastUtils.showShort("再按一次退出");
        }
    }
}
app/src/main/java/com/application/zhangshi_app_android/viewmodel/MainViewModel.java
New file
@@ -0,0 +1,25 @@
package com.application.zhangshi_app_android.viewmodel;
import android.app.Application;
import androidx.annotation.NonNull;
import com.android.app_base.base.model.BaseModel;
import com.android.app_base.base.viewmodel.BaseViewModel;
/**
 * @author Ljj
 * @date 2023.03.02. 23:07
 * @desc
 */
public class MainViewModel extends BaseViewModel {
    public MainViewModel(@NonNull Application application) {
        super(application);
    }
    @Override
    protected BaseModel initModel() {
        return null;
    }
}
app/src/main/res/drawable-v24/ic_launcher_foreground.xml
New file
@@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:aapt="http://schemas.android.com/aapt"
    android:width="108dp"
    android:height="108dp"
    android:viewportWidth="108"
    android:viewportHeight="108">
    <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
        <aapt:attr name="android:fillColor">
            <gradient
                android:endX="85.84757"
                android:endY="92.4963"
                android:startX="42.9492"
                android:startY="49.59793"
                android:type="linear">
                <item
                    android:color="#44000000"
                    android:offset="0.0" />
                <item
                    android:color="#00000000"
                    android:offset="1.0" />
            </gradient>
        </aapt:attr>
    </path>
    <path
        android:fillColor="#FFFFFF"
        android:fillType="nonZero"
        android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
        android:strokeWidth="1"
        android:strokeColor="#00000000" />
</vector>
app/src/main/res/drawable/ic_launcher_background.xml
New file
@@ -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>
app/src/main/res/layout/activity_main.xml
New file
@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable
            name="viewModel"
            type="com.application.zhangshi_app_android.viewmodel.MainViewModel" />
    </data>
    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <Button
            android:id="@+id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Button"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
        <TextView
            android:id="@+id/textView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="TextView"
            app:layout_constraintBottom_toTopOf="@+id/button"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
New file
@@ -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>
app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
New file
@@ -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>
app/src/main/res/mipmap-anydpi-v33/ic_launcher.xml
New file
@@ -0,0 +1,6 @@
<?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" />
    <monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
app/src/main/res/mipmap-hdpi/ic_launcher.webp
Binary files differ
app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
Binary files differ
app/src/main/res/mipmap-mdpi/ic_launcher.webp
Binary files differ
app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
Binary files differ
app/src/main/res/mipmap-xhdpi/ic_launcher.webp
Binary files differ
app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
Binary files differ
app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
Binary files differ
app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
Binary files differ
app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
Binary files differ
app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
Binary files differ
app/src/main/res/values-night/themes.xml
New file
@@ -0,0 +1,16 @@
<resources xmlns:tools="http://schemas.android.com/tools">
    <!-- Base application theme. -->
    <style name="Theme.Zhangshi_app_android" 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">?attr/colorPrimaryVariant</item>
        <!-- Customize your theme here. -->
    </style>
</resources>
app/src/main/res/values/colors.xml
New file
@@ -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>
app/src/main/res/values/strings.xml
New file
@@ -0,0 +1,3 @@
<resources>
    <string name="app_name">zhangshi_app_android</string>
</resources>
app/src/main/res/values/themes.xml
New file
@@ -0,0 +1,16 @@
<resources xmlns:tools="http://schemas.android.com/tools">
    <!-- Base application theme. -->
    <style name="Theme.Zhangshi_app_android" 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">?attr/colorPrimaryVariant</item>
        <!-- Customize your theme here. -->
    </style>
</resources>
app/src/main/res/xml/backup_rules.xml
New file
@@ -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>
app/src/main/res/xml/data_extraction_rules.xml
New file
@@ -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>
app/src/test/java/com/application/zhangshi_app_android/ExampleUnitTest.java
New file
@@ -0,0 +1,17 @@
package com.application.zhangshi_app_android;
import org.junit.Test;
import static org.junit.Assert.*;
/**
 * Example local unit test, which will execute on the development machine (host).
 *
 * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
 */
public class ExampleUnitTest {
    @Test
    public void addition_isCorrect() {
        assertEquals(4, 2 + 2);
    }
}
app_base/.gitignore
New file
@@ -0,0 +1 @@
/build
app_base/build.gradle
New file
@@ -0,0 +1,58 @@
plugins {
    id 'com.android.library'
}
android {
    namespace 'com.android.app_base'
    compileSdk 33
    defaultConfig {
        minSdk 24
        targetSdk 33
        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
    }
    buildFeatures{
        dataBinding = true
    }
}
dependencies {
    api fileTree(include: ['*.jar'], dir: 'libs')
    implementation 'androidx.appcompat:appcompat:1.6.1'
    implementation 'com.google.android.material:material:1.8.0'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.5'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
    //blankj工具类
    api 'com.blankj:utilcodex:1.31.1'
    //Retrofit+Okhttp+Rxjava(Retrofit2内置了okhttp)
    api 'com.squareup.retrofit2:retrofit:2.9.0'
    api 'com.squareup.retrofit2:converter-gson:2.9.0'//gson转换和
    api 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'//对Rxjava适配
    api 'com.squareup.retrofit2:retrofit-converters:2.4.0'//数据解析器
    api 'com.squareup.retrofit2:retrofit-adapters:2.4.0'//相关适配
    api 'com.squareup.okhttp3:okhttp:4.10.0'
    api 'com.squareup.okhttp3:logging-interceptor:4.10.0'
    api "io.reactivex.rxjava2:rxjava:2.2.21"
    api 'io.reactivex.rxjava2:rxandroid:2.1.1'
    //Cookie 持久化
    api 'com.github.franmontiel:PersistentCookieJar:v1.0.1'
}
app_base/consumer-rules.pro
app_base/proguard-rules.pro
New file
@@ -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
app_base/src/androidTest/java/com/android/app_base/ExampleInstrumentedTest.java
New file
@@ -0,0 +1,26 @@
package com.android.app_base;
import android.content.Context;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
 * Instrumented test, which will execute on an Android device.
 *
 * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
 */
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
    @Test
    public void useAppContext() {
        // Context of the app under test.
        Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
        assertEquals("com.android.app_base.test", appContext.getPackageName());
    }
}
app_base/src/main/AndroidManifest.xml
New file
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-permission android:name="android.permission.INTERNET" />
</manifest>
app_base/src/main/java/com/android/app_base/base/BaseApplication.java
New file
@@ -0,0 +1,73 @@
package com.android.app_base.base;
import android.app.Activity;
import android.app.Application;
import android.os.Bundle;
import androidx.annotation.NonNull;
import com.android.app_base.manager.AppManager;
/**
 *  Application基类
 */
public class BaseApplication extends Application {
    private static Application sInstance;
    @Override
    public void onCreate() {
        super.onCreate();
        setApplication(this);
    }
    /**
     * 当主工程没有继承BaseApplication时,可以使用setApplication方法初始化BaseApplication
     * @param application
     */
    public static synchronized void setApplication(@NonNull Application application) {
        sInstance = application;
        //注册监听每个activity的生命周期,便于堆栈式管理
        application.registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
            @Override
            public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
                AppManager.getAppManager().addActivity(activity);
            }
            @Override
            public void onActivityStarted(Activity activity) {
            }
            @Override
            public void onActivityResumed(Activity activity) {
            }
            @Override
            public void onActivityPaused(Activity activity) {
            }
            @Override
            public void onActivityStopped(Activity activity) {
            }
            @Override
            public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
            }
            @Override
            public void onActivityDestroyed(Activity activity) {
                AppManager.getAppManager().removeActivity(activity);
            }
        });
    }
    /**
     * 获得当前app运行的Application
     */
    public static Application getInstance() {
        if (sInstance == null) {
            throw new NullPointerException("please inherit BaseApplication or call setApplication.");
        }
        return sInstance;
    }
}
app_base/src/main/java/com/android/app_base/base/StateViewEnum.java
New file
@@ -0,0 +1,14 @@
package com.android.app_base.base;
/**
 * 进行数据请求时,进行Dialog加载或缺省页等状态
 */
public enum StateViewEnum {
    DIALOG_LOADING,// dialog加载中
    DIALOG_DISMISS,//dialog隐藏
    DATA_LOADING,
    DATA_ERROR,// 数据错误
    DATA_NULL,// 没有数据
    NET_ERROR,//网络错误
    HIDE,// 隐藏
}
app_base/src/main/java/com/android/app_base/base/adapter/BaseRVAdapter.java
New file
@@ -0,0 +1,481 @@
package com.android.app_base.base.adapter;
import android.content.Context;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.IdRes;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.databinding.DataBindingUtil;
import androidx.databinding.ViewDataBinding;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;
/**
 * @author Ljj
 * @date 2023.03.02. 17:09
 * @desc
 */
public abstract class BaseRVAdapter<T,VDB extends ViewDataBinding> extends RecyclerView.Adapter<BaseRVAdapter.BaseViewHolder<VDB>> {
    /** 上下文对象 */
    private final Context mContext;
    /** RecyclerView 对象 */
    private RecyclerView mRecyclerView;
    /** 条目点击监听器 */
    private OnItemClickListener mItemClickListener;
    /** 条目长按监听器 */
    private OnItemLongClickListener mItemLongClickListener;
    /**
     * 该 adapter 上绑定的滑动监听器
     */
    private MyOnScrollListener myOnScrollListener;
    /** 条目子 View 点击监听器 */
    private SparseArray<OnChildClickListener> mChildClickListeners;
    /** 条目子 View 长按监听器 */
    private SparseArray<OnChildLongClickListener> mChildLongClickListeners;
    /**
     * 列表数据
     */
    private List<T> mDataList;
    public BaseRVAdapter(Context context) {
        mContext = context;
        if (mContext == null) {
            throw new IllegalArgumentException("are you ok?");
        }
    }
    public BaseRVAdapter(Context context, List<T> list) {
        mContext = context;
        mDataList = (list == null) ? new ArrayList<T>() : list;
        setData(list);
    }
    @NonNull
    @Override
    public BaseViewHolder<VDB> onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        VDB itemBind = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()),getLayoutId(),parent,false);
        return new BaseViewHolder<>(itemBind);
    }
    protected abstract int getLayoutId();
    protected abstract void onBind(BaseViewHolder<VDB> holder, int position);
    @Override
    public void onBindViewHolder(@NonNull BaseViewHolder<VDB> holder, int position) {
        View itemView = holder.itemView;
        // 设置条目的点击和长按事件
        if (mItemClickListener != null) {
            itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    mItemClickListener.onItemClick(mRecyclerView,view, holder.getLayoutPosition());
                }
            });
        }
        if (mItemLongClickListener != null) {
            itemView.setOnLongClickListener(new View.OnLongClickListener() {
                @Override
                public boolean onLongClick(View view) {
                    return mItemLongClickListener.onItemLongClick(mRecyclerView, view, holder.getLayoutPosition());
                }
            });
        }
        // 设置条目子 View 点击事件
        if (mChildClickListeners != null) {
            for (int i = 0; i < mChildClickListeners.size(); i++) {
                View childView = itemView.findViewById(mChildClickListeners.keyAt(i));
                if (childView != null) {
                    childView.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View view) {
                            OnChildClickListener listener = mChildClickListeners.get(view.getId());
                            if (listener != null) {
                                listener.onChildClick(mRecyclerView, view, holder.getLayoutPosition());
                            }
                        }
                    });
                }
            }
        }
        // 设置条目子 View 长按事件
        if (mChildLongClickListeners != null) {
            for (int i = 0; i < mChildLongClickListeners.size(); i++) {
                View childView = itemView.findViewById(mChildLongClickListeners.keyAt(i));
                if (childView != null) {
                    childView.setOnLongClickListener(new View.OnLongClickListener() {
                        @Override
                        public boolean onLongClick(View view) {
                            OnChildLongClickListener listener = mChildLongClickListeners.get(view.getId());
                            if (listener != null) {
                                return listener.onChildLongClick(mRecyclerView, view, holder.getLayoutPosition());
                            }
                            return false;
                        }
                    });
                }
            }
        }
        onBind(holder,position);
    }
    @Override
    public int getItemCount() {
        return mDataList == null ? 0 : mDataList.size();
    }
    @Override
    public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
        mRecyclerView = recyclerView;
        // 用户设置了滚动监听,需要给 RecyclerView 设置监听
        if (myOnScrollListener != null) {
            // 添加滚动监听
            mRecyclerView.addOnScrollListener(myOnScrollListener);
        }
        // 判断当前的布局管理器是否为空,如果为空则设置默认的布局管理器
        if (mRecyclerView.getLayoutManager() == null) {
            RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(mContext);;
            mRecyclerView.setLayoutManager(layoutManager);
        }
    }
    @Override
    public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) {
        // 移除滚动监听
        if (myOnScrollListener != null) {
            mRecyclerView.removeOnScrollListener(myOnScrollListener);
        }
        mRecyclerView = null;
    }
    public RecyclerView getRecyclerView() {
        return mRecyclerView;
    }
    /**
     * 自定义ViewHolder,基类
     * @param <VDB>
     */
    public static class BaseViewHolder<VDB extends ViewDataBinding> extends RecyclerView.ViewHolder{
        private VDB binding;
        public BaseViewHolder(@NonNull View itemView) {
            super(itemView);
        }
        public BaseViewHolder(VDB binding) {
            this(binding.getRoot());
            this.binding = binding;
        }
        public VDB getBinding() {
            return binding;
        }
    }
    /**
     * 设置新的数据
     */
    public void setData(@Nullable List<T> data) {
        mDataList = data;
        notifyDataSetChanged();
    }
    /**
     * 获取当前数据
     */
    @Nullable
    public List<T> getData() {
        return mDataList;
    }
    /**
     * 追加一些数据
     */
    public void addData(List<T> data) {
        if (data == null || data.size() == 0) {
            return;
        }
        if (mDataList == null || mDataList.size() == 0) {
            setData(data);
        } else {
            mDataList.addAll(data);
            notifyDataSetChanged();
        }
    }
    /**
     * 从头部 追加一些数据 by hyz
     */
    public void addDataFormHead(List<T> data) {
        if (data == null || data.size() == 0) {
            return;
        }
        if (mDataList == null || mDataList.size() == 0) {
            setData(data);
        } else {
            mDataList.addAll(0, data);
            notifyDataSetChanged();
        }
    }
    /**
     * 清空当前数据
     */
    public void clearData() {
        if (mDataList == null || mDataList.size() == 0) {
            return;
        }
        mDataList.clear();
        notifyDataSetChanged();
    }
    /**
     * 获取某个位置上的数据
     */
    public T getItem(@IntRange(from = 0) int position) {
        return mDataList.get(position);
    }
    /**
     * 更新某个位置上的数据
     */
    public void setItem(@IntRange(from = 0) int position, @NonNull T item) {
        if (mDataList == null) {
            mDataList = new ArrayList<>();
        }
        mDataList.set(position, item);
        notifyItemChanged(position);
    }
    /**
     * 添加单条数据
     */
    public void addItem(@NonNull T item) {
        if (mDataList == null) {
            mDataList = new ArrayList<>();
        }
        addItem(mDataList.size(), item);
    }
    public void addItem(@IntRange(from = 0) int position, @NonNull T item) {
        if (mDataList == null) {
            mDataList = new ArrayList<>();
        }
        if (position < mDataList.size()) {
            mDataList.add(position, item);
        } else {
            mDataList.add(item);
            position = mDataList.size() - 1;
        }
        notifyItemInserted(position);
    }
    /**
     * 删除单条数据
     */
    public void removeItem(@NonNull T item) {
        int index = mDataList.indexOf(item);
        if (index != -1) {
            removeItem(index);
        }
    }
    public void removeItem(@IntRange(from = 0) int position) {
        // 如果是在for循环删除后要记得i--
        mDataList.remove(position);
        // 告诉适配器删除数据的位置,会有动画效果
        notifyItemRemoved(position);
    }
    /**
     * 设置 RecyclerView 条目点击监听
     */
    public void setOnItemClickListener(OnItemClickListener listener) {
        checkRecyclerViewState();
        mItemClickListener = listener;
    }
    /**
     * 设置 RecyclerView 条目子 View 点击监听
     */
    public void setOnChildClickListener(@IdRes int id, OnChildClickListener listener) {
        checkRecyclerViewState();
        if (mChildClickListeners == null) {
            mChildClickListeners = new SparseArray<>();
        }
        mChildClickListeners.put(id, listener);
    }
    /**
     * 设置RecyclerView条目长按监听
     */
    public void setOnItemLongClickListener(OnItemLongClickListener listener) {
        checkRecyclerViewState();
        mItemLongClickListener = listener;
    }
    /**
     * 设置 RecyclerView 条目子 View 长按监听
     */
    public void setOnChildLongClickListener(@IdRes int id, OnChildLongClickListener listener) {
        checkRecyclerViewState();
        if (mChildLongClickListeners == null) {
            mChildLongClickListeners = new SparseArray<>();
        }
        mChildLongClickListeners.put(id, listener);
    }
    private void checkRecyclerViewState() {
        if (mRecyclerView != null) {
            // 必须在 RecyclerView.setAdapter() 之前设置监听
            throw new IllegalStateException("are you ok?");
        }
    }
    /**
     * 设置 RecyclerView 条目滚动监听
     */
    public void setOnScrollingListener(OnScrollingListener listener) {
        /** RecyclerView 滚动事件监听 */
        //如果当前已经有设置滚动监听,再次设置需要移除原有的监听器
        if (myOnScrollListener == null) {
            myOnScrollListener = new MyOnScrollListener(listener);
        } else {
            mRecyclerView.removeOnScrollListener(myOnScrollListener);
        }
        //用户设置了滚动监听,需要给RecyclerView设置监听
        //用户设置了滚动监听,需要给RecyclerView设置监听
        if (mRecyclerView != null) {
            //添加滚动监听
            mRecyclerView.addOnScrollListener(new MyOnScrollListener(listener));
        }
    }
    /**
     * RecyclerView 条目点击监听类
     */
    public interface OnItemClickListener{
        /**
         * 当 RecyclerView 某个条目被点击时回调
         *
         * @param recyclerView      RecyclerView 对象
         * @param itemView          被点击的条目对象
         * @param position          被点击的条目位置
         */
        void onItemClick(RecyclerView recyclerView, View itemView, int position);
    }
    /**
     * RecyclerView 条目长按监听类
     */
    public interface OnItemLongClickListener {
        /**
         * 当 RecyclerView 某个条目被长按时回调
         *
         * @param recyclerView      RecyclerView 对象
         * @param itemView          被点击的条目对象
         * @param position          被点击的条目位置
         * @return                  是否拦截事件
         */
        boolean onItemLongClick(RecyclerView recyclerView, View itemView, int position);
    }
    /**
     * RecyclerView 条目子 View 点击监听类
     */
    public interface OnChildClickListener {
        /**
         * 当 RecyclerView 某个条目 子 View 被点击时回调
         *
         * @param recyclerView      RecyclerView 对象
         * @param childView         被点击的条目子 View
         * @param position          被点击的条目位置
         */
        void onChildClick(RecyclerView recyclerView, View childView, int position);
    }
    /**
     * RecyclerView 条目子 View 长按监听类
     */
    public interface OnChildLongClickListener {
        /**
         * 当 RecyclerView 某个条目子 View 被长按时回调
         *
         * @param recyclerView      RecyclerView 对象
         * @param childView         被点击的条目子 View
         * @param position          被点击的条目位置
         */
        boolean onChildLongClick(RecyclerView recyclerView, View childView, int position);
    }
    /**
     * RecyclerView 滚动监听接口
     */
    public interface OnScrollingListener {
        /**
         * 列表滚动到最顶部
         *
         * @param recyclerView      RecyclerView 对象
         */
        void onScrollTop(RecyclerView recyclerView);
        /**
         * 列表滚动中
         *
         * @param recyclerView      RecyclerView 对象
         */
        void onScrolling(RecyclerView recyclerView);
        /**
         * 列表滚动到最底部
         *
         * @param recyclerView      RecyclerView 对象
         */
        void onScrollDown(RecyclerView recyclerView);
    }
    /**
     * 自定义RecyclerView的滑动监听器
     */
    public static class MyOnScrollListener extends RecyclerView.OnScrollListener {
        private OnScrollingListener onScrollingListener;
        MyOnScrollListener(OnScrollingListener onScrollingListener){
            this.onScrollingListener = onScrollingListener;
        }
        @Override
        public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
            if (onScrollingListener == null) {
                return;
            }
            if (newState == RecyclerView.SCROLL_STATE_IDLE) {
                if (!recyclerView.canScrollVertically(1)) {
                    // 已经到底了
                    onScrollingListener.onScrollDown(recyclerView);
                } else if (!recyclerView.canScrollVertically(-1)) {
                    // 已经到顶了
                    onScrollingListener.onScrollTop(recyclerView);
                }
            } else if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
                // 正在滚动中
                onScrollingListener.onScrolling(recyclerView);
            }
        }
    }
}
app_base/src/main/java/com/android/app_base/base/model/BaseModel.java
New file
@@ -0,0 +1,9 @@
package com.android.app_base.base.model;
public abstract class BaseModel {
    public BaseModel() {
    }
    public abstract void onCleared();
}
app_base/src/main/java/com/android/app_base/base/view/BaseActivity.java
New file
@@ -0,0 +1,177 @@
package com.android.app_base.base.view;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;
import androidx.databinding.ViewDataBinding;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelStoreOwner;
import com.android.app_base.base.viewmodel.BaseViewModel;
import com.android.app_base.base.StateViewEnum;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
/**
 *  Activity基类,所有的 Activity 都要继承此类
 */
public abstract class BaseActivity<V extends ViewDataBinding,VM extends BaseViewModel> extends AppCompatActivity {
    protected V binding;
    protected VM viewModel;
    private int viewModelId;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //绑定 ViewDataBinding 和 ViewModel
        initViewDataBindingAndViewModel();
        //初始化状态视图
        initStateView();
        //页面参数初始化方法
        initParam();
        //页面view初始化方法
        initView();
        //页面数据初始化方法
        initData();
        //页面事件监听的方法,用于ViewModel层转到View层的事件注册
        initLiveDataObserve();
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        //解除Messenger注册
//        Messenger.getDefault().unregister(viewModel);
        if(binding != null){
            binding.unbind();
        }
    }
    /**
     *  ViewDataBinding和 ViewModel的获取及相关绑定
     */
    private void initViewDataBindingAndViewModel() {
        binding = DataBindingUtil.setContentView(this, getLayoutId());
        viewModelId = getVariableId();
        viewModel = initViewModel();
        //关联ViewModel
        binding.setVariable(viewModelId, viewModel);
        //支持LiveData绑定xml,数据改变,UI自动会更新
        binding.setLifecycleOwner(this);
        //让ViewModel拥有View的生命周期感应
        getLifecycle().addObserver(viewModel);
    }
    /**
     * 初始化ViewModel
     * @return 返回一个ViewModel
     */
    private VM initViewModel() {
        Class<VM> vmClass;
        Type type = getClass().getGenericSuperclass();
        if (type instanceof ParameterizedType){
            vmClass = (Class) ((ParameterizedType) type).getActualTypeArguments()[1];
        } else {
            //如果没有指定泛型参数,则默认使用BaseViewModel
            vmClass = (Class<VM>) BaseViewModel.class;
        }
        return new ViewModelProvider(this, (ViewModelProvider.Factory) ViewModelProvider.AndroidViewModelFactory.getInstance(getApplication())).get(vmClass);
    }
    /**
     * 对状态视图liveData进行观察监听
     */
    private void initStateView() {
        viewModel.getStateViewLiveData().observe(this, new Observer<StateViewEnum>() {
            @Override
            public void onChanged(StateViewEnum stateViewEnum) {
                switch (stateViewEnum) {
                    case DIALOG_LOADING:
                        dialogLoading();
                        break;
                    case DIALOG_DISMISS:
                        dialogDismiss();
                        break;
                    case DATA_LOADING:
                        dataLoading();
                        break;
                    case DATA_ERROR:
                        dataError();
                        break;
                    case DATA_NULL:
                        dataNull();
                        break;
                    case NET_ERROR:
                        netError();
                        break;
                    case HIDE:
                        hide();
                        break;
                    default:
                        break;
                }
            }
        });
    }
    /**
     * 缺省页等状态视图的更新
     * 有需求的,在子类选择重写
     */
    protected void dialogLoading() {
    }
    protected void dialogDismiss() {
    }
    protected void dataLoading() {
    }
    protected void dataError() {
    }
    protected void dataNull() {
    }
    protected void netError() {
    }
    protected void hide() {
    }
    /**
     * 获取根布局的id,由子类实现返回
     * @return layout的id
     */
    public abstract int getLayoutId();
    /**
     * 获取ViewModel的id,由子类实现返回
     * @return BR中viewModel的id
     */
    public abstract int getVariableId();
    /**
     * 初始化页面参数,例如从上一个activity传递过来的参数
     */
    public abstract void initParam();
    /**
     * 初始化页面view,例如一些view的隐藏或显示,以及点击逻辑等用户交互
     */
    public abstract void initView();
    /**
     * 初始化页面数据
     */
    public abstract void initData();
    /**
     * 初始化LiveData的监听
     * 简单的数据展示可通过DataBinding直接双向绑定到xml中
     */
    public abstract void initLiveDataObserve();
}
app_base/src/main/java/com/android/app_base/base/view/BaseFragment.java
New file
@@ -0,0 +1,191 @@
package com.android.app_base.base.view;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.databinding.DataBindingUtil;
import androidx.databinding.ViewDataBinding;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import com.android.app_base.base.StateViewEnum;
import com.android.app_base.base.viewmodel.BaseViewModel;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
/**
 * @author Ljj
 * @date 2023.03.02. 15:50
 * @desc Fragment基类
 */
public abstract class BaseFragment<V extends ViewDataBinding,VM extends BaseViewModel> extends Fragment {
    protected V binding;
    protected VM viewModel;
    private int viewModelId;
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //页面参数初始化方法
        initParam();
    }
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        binding = DataBindingUtil.inflate(inflater,getLayoutId(), container, false);
        return binding.getRoot();
    }
    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        //绑定 ViewDataBinding 和 ViewModel
        initViewDataBindingAndViewModel();
        //初始化状态视图
        initStateView();
        //页面view初始化方法
        initView();
        //页面数据初始化方法
        initData();
        //页面事件监听的方法,用于ViewModel层转到View层的事件注册
        initLiveDataObserve();
    }
    @Override
    public void onDestroyView() {
        super.onDestroyView();
        //解除Messenger注册
//        Messenger.getDefault().unregister(viewModel);
        if(binding != null){
            binding.unbind();
        }
    }
    /**
     *  ViewDataBinding和 ViewModel的获取及相关绑定
     */
    private void initViewDataBindingAndViewModel() {
        viewModelId = getVariableId();
        viewModel = initViewModel();
        //关联ViewModel
        binding.setVariable(viewModelId, viewModel);
        //支持LiveData绑定xml,数据改变,UI自动会更新
        binding.setLifecycleOwner(this);
        //让ViewModel拥有View的生命周期感应
        getLifecycle().addObserver(viewModel);
    }
    /**
     * 初始化ViewModel
     * @return 返回一个ViewModel
     */
    private VM initViewModel() {
        Class<VM> vmClass;
        Type type = getClass().getGenericSuperclass();
        if (type instanceof ParameterizedType){
            vmClass = (Class) ((ParameterizedType) type).getActualTypeArguments()[1];
        } else {
            //如果没有指定泛型参数,则默认使用BaseViewModel
            vmClass = (Class<VM>) BaseViewModel.class;
        }
        return new ViewModelProvider(this, (ViewModelProvider.Factory) ViewModelProvider.AndroidViewModelFactory.getInstance(requireActivity().getApplication())).get(vmClass);
    }
    /**
     * 对状态视图liveData进行观察监听
     */
    private void initStateView() {
        viewModel.getStateViewLiveData().observe(getViewLifecycleOwner(), new Observer<StateViewEnum>() {
            @Override
            public void onChanged(StateViewEnum stateViewEnum) {
                switch (stateViewEnum) {
                    case DIALOG_LOADING:
                        dialogLoading();
                        break;
                    case DIALOG_DISMISS:
                        dialogDismiss();
                        break;
                    case DATA_LOADING:
                        dataLoading();
                        break;
                    case DATA_ERROR:
                        dataError();
                        break;
                    case DATA_NULL:
                        dataNull();
                        break;
                    case NET_ERROR:
                        netError();
                        break;
                    case HIDE:
                        hide();
                        break;
                    default:
                        break;
                }
            }
        });
    }
    /**
     * 缺省页等状态视图的更新
     * 有需求的,在子类选择重写
     */
    protected void dialogLoading() {
    }
    protected void dialogDismiss() {
    }
    protected void dataLoading() {
    }
    protected void dataError() {
    }
    protected void dataNull() {
    }
    protected void netError() {
    }
    protected void hide() {
    }
    /**
     * 获取根布局的id,由子类实现返回
     * @return layout的id
     */
    public abstract int getLayoutId();
    /**
     * 获取ViewModel的id,由子类实现返回
     * @return BR中viewModel的id
     */
    public abstract int getVariableId();
    /**
     * 初始化页面参数,例如从上一个activity传递过来的参数
     */
    abstract void initParam();
    /**
     * 初始化页面view,例如一些view的隐藏或显示,以及点击逻辑等用户交互
     */
    abstract void initView();
    /**
     * 初始化页面数据
     */
    abstract void initData();
    /**
     * 初始化LiveData的监听
     * 简单的数据展示可通过DataBinding直接双向绑定到xml中
     */
    abstract void initLiveDataObserve();
}
app_base/src/main/java/com/android/app_base/base/viewmodel/BaseViewModel.java
New file
@@ -0,0 +1,120 @@
package com.android.app_base.base.viewmodel;
import android.app.Application;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import com.android.app_base.base.StateViewEnum;
import com.android.app_base.base.model.BaseModel;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
/**
 * ViewModel基类
 */
public abstract class BaseViewModel<M extends BaseModel> extends AndroidViewModel implements DefaultLifecycleObserver {
    protected Application application;
    /**
     * 数据仓库model
     */
    protected M model;
    /**
     * 控制状态视图的LiveData
     */
    private MutableLiveData<StateViewEnum> stateViewLiveData;
    //管理RxJava,主要针对RxJava异步操作造成的内存泄漏
    private CompositeDisposable mCompositeDisposable;
    public BaseViewModel(@NonNull Application application) {
        super(application);
        this.application = application;
        model = initModel();
        mCompositeDisposable = new CompositeDisposable();
    }
    /**
     * 数据仓库初始化,由子类实现返回
     */
    protected abstract M initModel();
    /**
     * Rxjava的异步操作都要调用此方法添加到 mCompositeDisposable里,
     * @param disposable
     */
    protected void addSubscribe(Disposable disposable) {
        if (mCompositeDisposable == null) {
            mCompositeDisposable = new CompositeDisposable();
        }
        mCompositeDisposable.add(disposable);
    }
    /**
     * 获取状态视图LiveData
     */
    public LiveData<StateViewEnum> getStateViewLiveData() {
        if(stateViewLiveData == null){
            stateViewLiveData = new MutableLiveData<>();
        }
        return stateViewLiveData;
    }
    /**
     * 更改状态视图的状态
     */
    public void changeStateView(StateViewEnum state) {
        stateViewLiveData.postValue(state);
    }
    @Override
    protected void onCleared() {
        super.onCleared();
        if (model != null) {
            model.onCleared();
        }
        //ViewModel销毁时会执行,同时取消所有异步任务
        if (mCompositeDisposable != null) {
            mCompositeDisposable.clear();
        }
    }
    /**
     * 子类可以重写生命周期方法,便于生命周期的考虑
     */
    @Override
    public void onCreate(@NonNull LifecycleOwner owner) {
        DefaultLifecycleObserver.super.onCreate(owner);
    }
    @Override
    public void onStart(@NonNull LifecycleOwner owner) {
        DefaultLifecycleObserver.super.onStart(owner);
    }
    @Override
    public void onResume(@NonNull LifecycleOwner owner) {
        DefaultLifecycleObserver.super.onResume(owner);
    }
    @Override
    public void onPause(@NonNull LifecycleOwner owner) {
        DefaultLifecycleObserver.super.onPause(owner);
    }
    @Override
    public void onStop(@NonNull LifecycleOwner owner) {
        DefaultLifecycleObserver.super.onStop(owner);
    }
    @Override
    public void onDestroy(@NonNull LifecycleOwner owner) {
        DefaultLifecycleObserver.super.onDestroy(owner);
    }
}
app_base/src/main/java/com/android/app_base/helper/DoubleClickHelper.java
New file
@@ -0,0 +1,29 @@
package com.android.app_base.helper;
import android.os.SystemClock;
/**
 *  @desc 防双击判断工具类
 */
public final class DoubleClickHelper {
    /** 数组的长度为2代表只记录双击操作 */
    private static final long[] TIME_ARRAY = new long[2];
    /**
     * 是否在短时间内进行了双击操作
     */
    public static boolean isOnDoubleClick() {
        // 默认间隔时长
        return isOnDoubleClick(1500);
    }
    /**
     * 是否在短时间内进行了双击操作
     */
    public static boolean isOnDoubleClick(int time) {
        System.arraycopy(TIME_ARRAY, 1, TIME_ARRAY, 0, TIME_ARRAY.length - 1);
        TIME_ARRAY[TIME_ARRAY.length - 1] = SystemClock.uptimeMillis();
        return TIME_ARRAY[0] >= (SystemClock.uptimeMillis() - time);
    }
}
app_base/src/main/java/com/android/app_base/http/OkHttpHelper.java
New file
@@ -0,0 +1,96 @@
package com.android.app_base.http;
import android.content.Context;
import com.android.app_base.BuildConfig;
import com.android.app_base.base.BaseApplication;
import com.android.app_base.http.interceptor.CacheInterceptor;
import com.blankj.utilcode.util.LogUtils;
import com.franmontiel.persistentcookiejar.PersistentCookieJar;
import com.franmontiel.persistentcookiejar.cache.SetCookieCache;
import com.franmontiel.persistentcookiejar.persistence.SharedPrefsCookiePersistor;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.concurrent.TimeUnit;
import okhttp3.Cache;
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
/**
 * @author Ljj
 * @date 2023.03.01. 22:36
 * @desc 网络请求OkHttp配置
 */
public class OkHttpHelper {
    //超时时间
    private final static int READ_TIMEOUT = 15;
    private final static int CONNECT_TIMEOUT = 15;
    private final static int WRITE_TIMEOUT = 15;
    //缓存时间
    private final static int CACHE_SIZE = 10 * 1024 * 1024;
    private static Context mContext = BaseApplication.getInstance();
    private static volatile OkHttpClient okHttpClient;
    private OkHttpHelper() {
        OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder();
        //读取超时
        clientBuilder.readTimeout(READ_TIMEOUT, TimeUnit.SECONDS);
        //连接超时
        clientBuilder.connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS);
        //写入超时
        clientBuilder.writeTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS);
        //错误重连
        clientBuilder.retryOnConnectionFailure(true);
        //cookie
        clientBuilder.cookieJar(new PersistentCookieJar(new SetCookieCache(),new SharedPrefsCookiePersistor(mContext)));
        //缓存拦截
        Cache cache = new Cache(new File(mContext.getCacheDir(),"http_cache"),CACHE_SIZE);
        clientBuilder.cache(cache).addInterceptor(new CacheInterceptor());
//        //增加头部信息拦截
//        clientBuilder.addInterceptor(new Interceptor() {
//            @Override
//            public Response intercept(Chain chain) throws IOException {
//                Request build = chain.request().newBuilder()
//                        .addHeader("Content-Type", "application/json")
//                        .build();
//                return chain.proceed(build);
//            }
//        });
        //log日志拦截
        if (BuildConfig.DEBUG) {
            clientBuilder.addInterceptor(new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
                @Override
                public void log(String message) {
                    try {
//                        String msg = message.replaceAll("%(?![0-9a-fA-F]{2})","%25");
                        String text = URLDecoder.decode(message, "utf-8");
                        LogUtils.dTag("LOG","网络请求"+text);
                    } catch (UnsupportedEncodingException e) {
                        e.printStackTrace();
                        LogUtils.dTag("LOG","网络请求错误信息"+e.getMessage());
                    }
                }
            }).setLevel(HttpLoggingInterceptor.Level.BODY));
        }
        okHttpClient = clientBuilder.build();
    }
    public static OkHttpClient getOkHttpClient() {
        if (null == okHttpClient) {
            synchronized (OkHttpHelper.class) {
                if (okHttpClient == null) {
                    new OkHttpHelper();
                    return okHttpClient;
                }
            }
        }
        return okHttpClient;
    }
}
app_base/src/main/java/com/android/app_base/http/ResultData.java
New file
@@ -0,0 +1,45 @@
package com.android.app_base.http;
/**
 * @author Ljj
 * @date 2023.03.01. 20:32
 * @desc 网络请求接口数据封装
 */
public class ResultData<T> {
    private int code;
    private String message;
    private boolean success;
    private T data;
    public T getData() {
        return data;
    }
    public void setData(T data) {
        this.data = data;
    }
    public int getCode() {
        return code;
    }
    public void setCode(int code) {
        this.code = code;
    }
    public String getMessage() {
        return message;
    }
    public void setMessage(String message) {
        this.message = message;
    }
    public boolean isSuccess() {
        return success;
    }
    public void setSuccess(boolean success) {
        this.success = success;
    }
}
app_base/src/main/java/com/android/app_base/http/RetrofitManager.java
New file
@@ -0,0 +1,60 @@
package com.android.app_base.http;
import androidx.collection.CircularArray;
import java.util.HashMap;
import java.util.Map;
import okhttp3.OkHttpClient;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;
/**
 * @author Ljj
 * @date 2023.03.02. 14:11
 * @desc Retrofit管理,可以有不同的 baseUrl
 */
public class RetrofitManager {
    private static RetrofitManager instance;
    private final Map<String,Retrofit> retrofitMap;
    private OkHttpClient mClient;
    public static RetrofitManager getInstance(){
        if (instance == null){
            instance = new RetrofitManager();
        }
        return instance;
    }
    private RetrofitManager(){
        retrofitMap = new HashMap<>();
        mClient = OkHttpHelper.getOkHttpClient();
    }
    /**
     * 获取Retrofit对象
     */
    public Retrofit getRetrofit(String baseUrl) {
        Retrofit retrofit = retrofitMap.get(baseUrl);
        if (retrofit == null) {
            retrofit = new Retrofit.Builder()
                    .client(mClient)
                    .addConverterFactory(GsonConverterFactory.create())
                    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                    .baseUrl(baseUrl)
                    .build();
            retrofitMap.put(baseUrl, retrofit);
        }
        return retrofit;
    }
    /**
     * 设置 OkHttpClient
     */
    public RetrofitManager setOkHttpClient(OkHttpClient client) {
        this.mClient = client;
        return instance;
    }
}
app_base/src/main/java/com/android/app_base/http/interceptor/CacheInterceptor.java
New file
@@ -0,0 +1,56 @@
package com.android.app_base.http.interceptor;
import android.content.Context;
import com.blankj.utilcode.util.NetworkUtils;
import java.io.IOException;
import okhttp3.CacheControl;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
/**
 * @author Ljj
 * @date 2023.03.02. 10:56
 * @desc 网络请求缓存拦截器
 */
public class CacheInterceptor implements Interceptor {
    private Context context;
    public CacheInterceptor(){
    }
    public CacheInterceptor(Context context) {
        this.context = context;
    }
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        if (NetworkUtils.isAvailable()) {
            Response response = chain.proceed(request);
            // read from cache for 60 s
            int maxAge = 60;
            return response.newBuilder()
                    .removeHeader("Pragma")
                    .removeHeader("Cache-Control")
                    .header("Cache-Control", "public, max-age=" + maxAge)
                    .build();
        } else {
            //读取缓存信息
            request = request.newBuilder()
                    .cacheControl(CacheControl.FORCE_CACHE)
                    .build();
            Response response = chain.proceed(request);
            //set cache times is 3 days
            int maxStale = 60 * 60 * 24 * 3;
            return response.newBuilder()
                    .removeHeader("Pragma")
                    .removeHeader("Cache-Control")
                    .header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
                    .build();
        }
    }
}
app_base/src/main/java/com/android/app_base/manager/AppManager.java
New file
@@ -0,0 +1,212 @@
package com.android.app_base.manager;
import android.app.Activity;
import android.content.Intent;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import com.blankj.utilcode.util.ActivityUtils;
import java.util.Stack;
/**
 * @author ljj
 * @date 2023.3.1
 * @desc APP行为管理
 */
public class AppManager {
    private static Stack<Activity> activityStack;
    private static Stack<Fragment> fragmentStack;
    private static AppManager instance;
    private AppManager() {
    }
    /**
     * 单例模式
     *
     * @return AppManager
     */
    public static AppManager getAppManager() {
        if (instance == null) {
            instance = new AppManager();
        }
        return instance;
    }
    public static Stack<Activity> getActivityStack() {
        return activityStack;
    }
    public static Stack<Fragment> getFragmentStack() {
        return fragmentStack;
    }
    /**
     * 添加Activity到堆栈
     */
    public void addActivity(Activity activity) {
        if (activityStack == null) {
            activityStack = new Stack<Activity>();
        }
        activityStack.add(activity);
    }
    /**
     * 移除指定的Activity
     */
    public void removeActivity(Activity activity) {
        if (activity != null) {
            activityStack.remove(activity);
        }
    }
    /**
     * 是否有activity
     */
    public boolean isActivity() {
        if (activityStack != null) {
            return !activityStack.isEmpty();
        }
        return false;
    }
    /**
     * 获取当前Activity(堆栈中最后一个压入的)
     */
    public Activity currentActivity() {
        return activityStack.lastElement();
    }
    /**
     * 开始指定类名的 Activity
     */
    public void startActivity(Class T){
        ActivityUtils.startActivity(T);
    }
    public boolean startActivity(@NonNull Intent intent) {
        return ActivityUtils.startActivity(intent);
    }
    /**
     * 结束当前Activity(堆栈中最后一个压入的)
     */
    public void finishCurrentActivity() {
        Activity activity = activityStack.lastElement();
        finishActivity(activity);
    }
    /**
     * 结束指定的Activity
     */
    public void finishActivity(Activity activity) {
        if (activity != null) {
            if (!activity.isFinishing()) {
                activity.finish();
            }
        }
    }
    /**
     * 结束指定类名的Activity
     */
    public void finishActivity(Class<?> cls) {
        for (Activity activity : activityStack) {
            if (activity.getClass().equals(cls)) {
                finishActivity(activity);
                break;
            }
        }
    }
    /**
     * 结束所有Activity
     */
    public void finishAllActivity() {
        for (int i = 0, size = activityStack.size(); i < size; i++) {
            if (null != activityStack.get(i)) {
                finishActivity(activityStack.get(i));
            }
        }
        activityStack.clear();
    }
    /**
     * 获取指定的Activity
     */
    public Activity getActivity(Class<?> cls) {
        if (activityStack != null)
            for (Activity activity : activityStack) {
                if (activity.getClass().equals(cls)) {
                    return activity;
                }
            }
        return null;
    }
    /**
     * 添加Fragment到堆栈
     */
    public void addFragment(Fragment fragment) {
        if (fragmentStack == null) {
            fragmentStack = new Stack<Fragment>();
        }
        fragmentStack.add(fragment);
    }
    /**
     * 移除指定的Fragment
     */
    public void removeFragment(Fragment fragment) {
        if (fragment != null) {
            fragmentStack.remove(fragment);
        }
    }
    /**
     * 是否有Fragment
     */
    public boolean isFragment() {
        if (fragmentStack != null) {
            return !fragmentStack.isEmpty();
        }
        return false;
    }
    /**
     * 获取当前Activity(堆栈中最后一个压入的)
     */
    public Fragment currentFragment() {
        if (fragmentStack != null) {
            Fragment fragment = fragmentStack.lastElement();
            return fragment;
        }
        return null;
    }
    /**
     * 退出应用程序
     */
    public void AppExit() {
        try {
            finishAllActivity();
            // 杀死该应用进程
//          android.os.Process.killProcess(android.os.Process.myPid());
//            调用 System.exit(n) 实际上等效于调用:
//            Runtime.getRuntime().exit(n)
//            finish()是Activity的类方法,仅仅针对Activity,当调用finish()时,只是将活动推向后台,并没有立即释放内存,活动的资源并没有被清理;当调用System.exit(0)时,退出当前Activity并释放资源(内存),但是该方法不可以结束整个App如有多个Activty或者有其他组件service等不会结束。
//            其实android的机制决定了用户无法完全退出应用,当你的application最长时间没有被用过的时候,android自身会决定将application关闭了。
            //System.exit(0);
        } catch (Exception e) {
            activityStack.clear();
            e.printStackTrace();
        }
    }
}
app_base/src/main/java/com/android/app_base/utils/SPUtils.java
New file
@@ -0,0 +1,282 @@
package com.android.app_base.utils;
import android.content.Context;
import android.content.SharedPreferences;
import androidx.annotation.NonNull;
import com.android.app_base.base.BaseApplication;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
 * @desc SharedPreferences工具类
 */
public final class SPUtils {
    private static Map<String, SPUtils> sSPMap = new HashMap<>();
    private SharedPreferences sp;
    /**
     * 获取SP实例
     *
     * @return {@link SPUtils}
     */
    public static SPUtils getInstance() {
        return getInstance("");
    }
    /**
     * 获取SP实例
     *
     * @param spName sp名
     * @return {@link SPUtils}
     */
    public static SPUtils getInstance(String spName) {
        if (isSpace(spName)) spName = "spUtils";
        SPUtils sp = sSPMap.get(spName);
        if (sp == null) {
            sp = new SPUtils(spName);
            sSPMap.put(spName, sp);
        }
        return sp;
    }
    private SPUtils(final String spName) {
        sp = BaseApplication.getInstance().getSharedPreferences(spName, Context.MODE_PRIVATE);
    }
    /**
     * SP中写入String
     *
     * @param key 键
     * @param value 值
     */
    public void put(@NonNull final String key, @NonNull final String value) {
        sp.edit().putString(key, value).apply();
    }
    /**
     * SP中读取String
     *
     * @param key 键
     * @return 存在返回对应值,不存在返回默认值{@code ""}
     */
    public String getString(@NonNull final String key) {
        return getString(key, "");
    }
    /**
     * SP中读取String
     *
     * @param key 键
     * @param defaultValue 默认值
     * @return 存在返回对应值,不存在返回默认值{@code defaultValue}
     */
    public String getString(@NonNull final String key, @NonNull final String defaultValue) {
        return sp.getString(key, defaultValue);
    }
    /**
     * SP中写入int
     *
     * @param key 键
     * @param value 值
     */
    public void put(@NonNull final String key, final int value) {
        sp.edit().putInt(key, value).apply();
    }
    /**
     * SP中读取int
     *
     * @param key 键
     * @return 存在返回对应值,不存在返回默认值-1
     */
    public int getInt(@NonNull final String key) {
        return getInt(key, -1);
    }
    /**
     * SP中读取int
     *
     * @param key 键
     * @param defaultValue 默认值
     * @return 存在返回对应值,不存在返回默认值{@code defaultValue}
     */
    public int getInt(@NonNull final String key, final int defaultValue) {
        return sp.getInt(key, defaultValue);
    }
    /**
     * SP中写入long
     *
     * @param key 键
     * @param value 值
     */
    public void put(@NonNull final String key, final long value) {
        sp.edit().putLong(key, value).apply();
    }
    /**
     * SP中读取long
     *
     * @param key 键
     * @return 存在返回对应值,不存在返回默认值-1
     */
    public long getLong(@NonNull final String key) {
        return getLong(key, -1L);
    }
    /**
     * SP中读取long
     *
     * @param key 键
     * @param defaultValue 默认值
     * @return 存在返回对应值,不存在返回默认值{@code defaultValue}
     */
    public long getLong(@NonNull final String key, final long defaultValue) {
        return sp.getLong(key, defaultValue);
    }
    /**
     * SP中写入float
     *
     * @param key 键
     * @param value 值
     */
    public void put(@NonNull final String key, final float value) {
        sp.edit().putFloat(key, value).apply();
    }
    /**
     * SP中读取float
     *
     * @param key 键
     * @return 存在返回对应值,不存在返回默认值-1
     */
    public float getFloat(@NonNull final String key) {
        return getFloat(key, -1f);
    }
    /**
     * SP中读取float
     *
     * @param key 键
     * @param defaultValue 默认值
     * @return 存在返回对应值,不存在返回默认值{@code defaultValue}
     */
    public float getFloat(@NonNull final String key, final float defaultValue) {
        return sp.getFloat(key, defaultValue);
    }
    /**
     * SP中写入boolean
     *
     * @param key 键
     * @param value 值
     */
    public void put(@NonNull final String key, final boolean value) {
        sp.edit().putBoolean(key, value).apply();
    }
    /**
     * SP中读取boolean
     *
     * @param key 键
     * @return 存在返回对应值,不存在返回默认值{@code false}
     */
    public boolean getBoolean(@NonNull final String key) {
        return getBoolean(key, false);
    }
    /**
     * SP中读取boolean
     *
     * @param key 键
     * @param defaultValue 默认值
     * @return 存在返回对应值,不存在返回默认值{@code defaultValue}
     */
    public boolean getBoolean(@NonNull final String key, final boolean defaultValue) {
        return sp.getBoolean(key, defaultValue);
    }
    /**
     * SP中写入String集合
     *
     * @param key 键
     * @param values 值
     */
    public void put(@NonNull final String key, @NonNull final Set<String> values) {
        sp.edit().putStringSet(key, values).apply();
    }
    /**
     * SP中读取StringSet
     *
     * @param key 键
     * @return 存在返回对应值,不存在返回默认值{@code Collections.<String>emptySet()}
     */
    public Set<String> getStringSet(@NonNull final String key) {
        return getStringSet(key, Collections.<String>emptySet());
    }
    /**
     * SP中读取StringSet
     *
     * @param key 键
     * @param defaultValue 默认值
     * @return 存在返回对应值,不存在返回默认值{@code defaultValue}
     */
    public Set<String> getStringSet(@NonNull final String key, @NonNull final Set<String> defaultValue) {
        return sp.getStringSet(key, defaultValue);
    }
    /**
     * SP中获取所有键值对
     *
     * @return Map对象
     */
    public Map<String, ?> getAll() {
        return sp.getAll();
    }
    /**
     * SP中是否存在该key
     *
     * @param key 键
     * @return {@code true}: 存在<br>{@code false}: 不存在
     */
    public boolean contains(@NonNull final String key) {
        return sp.contains(key);
    }
    /**
     * SP中移除该key
     *
     * @param key 键
     */
    public void remove(@NonNull final String key) {
        sp.edit().remove(key).apply();
    }
    /**
     * SP中清除所有数据
     */
    public void clear() {
        sp.edit().clear().apply();
    }
    private static boolean isSpace(final String s) {
        if (s == null) return true;
        for (int i = 0, len = s.length(); i < len; ++i) {
            if (!Character.isWhitespace(s.charAt(i))) {
                return false;
            }
        }
        return true;
    }
}
app_base/src/main/java/com/android/app_base/utils/ToastUtils.java
New file
@@ -0,0 +1,429 @@
package com.android.app_base.utils;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.style.ForegroundColorSpan;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Toast;
import androidx.annotation.ColorInt;
import androidx.annotation.DrawableRes;
import androidx.annotation.LayoutRes;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import com.android.app_base.base.BaseApplication;
import com.blankj.utilcode.util.Utils;
import java.lang.ref.WeakReference;
/**
 * 吐司工具类
 */
public final class ToastUtils {
    private static final int DEFAULT_COLOR = 0x12000000;
    private static Toast sToast;
    private static int gravity         = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
    private static int xOffset         = 0;
    private static int yOffset         = (int) (64 * BaseApplication.getInstance().getResources().getDisplayMetrics().density + 0.5);
    private static int backgroundColor = DEFAULT_COLOR;
    private static int bgResource      = -1;
    private static int messageColor    = DEFAULT_COLOR;
    private static WeakReference<View> sViewWeakReference;
    private static Handler sHandler = new Handler(Looper.getMainLooper());
    private ToastUtils() {
        throw new UnsupportedOperationException("u can't instantiate me...");
    }
    /**
     * 设置吐司位置
     *
     * @param gravity 位置
     * @param xOffset x偏移
     * @param yOffset y偏移
     */
    public static void setGravity(int gravity, int xOffset, int yOffset) {
        ToastUtils.gravity = gravity;
        ToastUtils.xOffset = xOffset;
        ToastUtils.yOffset = yOffset;
    }
    /**
     * 设置吐司view
     *
     * @param layoutId 视图
     */
    public static void setView(@LayoutRes int layoutId) {
        LayoutInflater inflate = (LayoutInflater) BaseApplication.getInstance().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        sViewWeakReference = new WeakReference<>(inflate.inflate(layoutId, null));
    }
    /**
     * 设置吐司view
     *
     * @param view 视图
     */
    public static void setView(@Nullable View view) {
        sViewWeakReference = view == null ? null : new WeakReference<>(view);
    }
    /**
     * 获取吐司view
     *
     * @return view
     */
    public static View getView() {
        if (sViewWeakReference != null) {
            final View view = sViewWeakReference.get();
            if (view != null) {
                return view;
            }
        }
        if (sToast != null) return sToast.getView();
        return null;
    }
    /**
     * 设置背景颜色
     *
     * @param backgroundColor 背景色
     */
    public static void setBackgroundColor(@ColorInt int backgroundColor) {
        ToastUtils.backgroundColor = backgroundColor;
    }
    /**
     * 设置背景资源
     *
     * @param bgResource 背景资源
     */
    public static void setBgResource(@DrawableRes int bgResource) {
        ToastUtils.bgResource = bgResource;
    }
    /**
     * 设置消息颜色
     *
     * @param messageColor 颜色
     */
    public static void setMessageColor(@ColorInt int messageColor) {
        ToastUtils.messageColor = messageColor;
    }
    /**
     * 安全地显示短时吐司
     *
     * @param text 文本
     */
    public static void showShortSafe(final CharSequence text) {
        sHandler.post(new Runnable() {
            @Override
            public void run() {
                show(text, Toast.LENGTH_SHORT);
            }
        });
    }
    /**
     * 安全地显示短时吐司
     *
     * @param resId 资源Id
     */
    public static void showShortSafe(final @StringRes int resId) {
        sHandler.post(new Runnable() {
            @Override
            public void run() {
                show(resId, Toast.LENGTH_SHORT);
            }
        });
    }
    /**
     * 安全地显示短时吐司
     *
     * @param resId 资源Id
     * @param args  参数
     */
    public static void showShortSafe(final @StringRes int resId, final Object... args) {
        sHandler.post(new Runnable() {
            @Override
            public void run() {
                show(resId, Toast.LENGTH_SHORT, args);
            }
        });
    }
    /**
     * 安全地显示短时吐司
     *
     * @param format 格式
     * @param args   参数
     */
    public static void showShortSafe(final String format, final Object... args) {
        sHandler.post(new Runnable() {
            @Override
            public void run() {
                show(format, Toast.LENGTH_SHORT, args);
            }
        });
    }
    /**
     * 安全地显示长时吐司
     *
     * @param text 文本
     */
    public static void showLongSafe(final CharSequence text) {
        sHandler.post(new Runnable() {
            @Override
            public void run() {
                show(text, Toast.LENGTH_LONG);
            }
        });
    }
    /**
     * 安全地显示长时吐司
     *
     * @param resId 资源Id
     */
    public static void showLongSafe(final @StringRes int resId) {
        sHandler.post(new Runnable() {
            @Override
            public void run() {
                show(resId, Toast.LENGTH_LONG);
            }
        });
    }
    /**
     * 安全地显示长时吐司
     *
     * @param resId 资源Id
     * @param args  参数
     */
    public static void showLongSafe(final @StringRes int resId, final Object... args) {
        sHandler.post(new Runnable() {
            @Override
            public void run() {
                show(resId, Toast.LENGTH_LONG, args);
            }
        });
    }
    /**
     * 安全地显示长时吐司
     *
     * @param format 格式
     * @param args   参数
     */
    public static void showLongSafe(final String format, final Object... args) {
        sHandler.post(new Runnable() {
            @Override
            public void run() {
                show(format, Toast.LENGTH_LONG, args);
            }
        });
    }
    /**
     * 显示短时吐司
     *
     * @param text 文本
     */
    public static void showShort(CharSequence text) {
        show(text, Toast.LENGTH_SHORT);
    }
    /**
     * 显示短时吐司
     *
     * @param resId 资源Id
     */
    public static void showShort(@StringRes int resId) {
        show(resId, Toast.LENGTH_SHORT);
    }
    /**
     * 显示短时吐司
     *
     * @param resId 资源Id
     * @param args  参数
     */
    public static void showShort(@StringRes int resId, Object... args) {
        show(resId, Toast.LENGTH_SHORT, args);
    }
    /**
     * 显示短时吐司
     *
     * @param format 格式
     * @param args   参数
     */
    public static void showShort(String format, Object... args) {
        show(format, Toast.LENGTH_SHORT, args);
    }
    /**
     * 显示长时吐司
     *
     * @param text 文本
     */
    public static void showLong(CharSequence text) {
        show(text, Toast.LENGTH_LONG);
    }
    /**
     * 显示长时吐司
     *
     * @param resId 资源Id
     */
    public static void showLong(@StringRes int resId) {
        show(resId, Toast.LENGTH_LONG);
    }
    /**
     * 显示长时吐司
     *
     * @param resId 资源Id
     * @param args  参数
     */
    public static void showLong(@StringRes int resId, Object... args) {
        show(resId, Toast.LENGTH_LONG, args);
    }
    /**
     * 显示长时吐司
     *
     * @param format 格式
     * @param args   参数
     */
    public static void showLong(String format, Object... args) {
        show(format, Toast.LENGTH_LONG, args);
    }
    /**
     * 安全地显示短时自定义吐司
     */
    public static void showCustomShortSafe() {
        sHandler.post(new Runnable() {
            @Override
            public void run() {
                show("", Toast.LENGTH_SHORT);
            }
        });
    }
    /**
     * 安全地显示长时自定义吐司
     */
    public static void showCustomLongSafe() {
        sHandler.post(new Runnable() {
            @Override
            public void run() {
                show("", Toast.LENGTH_LONG);
            }
        });
    }
    /**
     * 显示短时自定义吐司
     */
    public static void showCustomShort() {
        show("", Toast.LENGTH_SHORT);
    }
    /**
     * 显示长时自定义吐司
     */
    public static void showCustomLong() {
        show("", Toast.LENGTH_LONG);
    }
    /**
     * 显示吐司
     *
     * @param resId    资源Id
     * @param duration 显示时长
     */
    private static void show(@StringRes int resId, int duration) {
        show(BaseApplication.getInstance().getResources().getText(resId).toString(), duration);
    }
    /**
     * 显示吐司
     *
     * @param resId    资源Id
     * @param duration 显示时长
     * @param args     参数
     */
    private static void show(@StringRes int resId, int duration, Object... args) {
        show(String.format(BaseApplication.getInstance().getResources().getString(resId), args), duration);
    }
    /**
     * 显示吐司
     *
     * @param format   格式
     * @param duration 显示时长
     * @param args     参数
     */
    private static void show(String format, int duration, Object... args) {
        show(String.format(format, args), duration);
    }
    /**
     * 显示吐司
     *
     * @param text     文本
     * @param duration 显示时长
     */
    private static void show(CharSequence text, int duration) {
        cancel();
        boolean isCustom = false;
        if (sViewWeakReference != null) {
            final View view = sViewWeakReference.get();
            if (view != null) {
                sToast = new Toast(BaseApplication.getInstance());
                sToast.setView(view);
                sToast.setDuration(duration);
                isCustom = true;
            }
        }
        if (!isCustom) {
            if (messageColor != DEFAULT_COLOR) {
                SpannableString spannableString = new SpannableString(text);
                ForegroundColorSpan colorSpan = new ForegroundColorSpan(messageColor);
                spannableString.setSpan(colorSpan, 0, spannableString.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                sToast = Toast.makeText(BaseApplication.getInstance(), spannableString, duration);
            } else {
                sToast = Toast.makeText(BaseApplication.getInstance(), text, duration);
            }
        }
        View view = sToast.getView();
        if (bgResource != -1) {
            view.setBackgroundResource(bgResource);
        } else if (backgroundColor != DEFAULT_COLOR) {
            view.setBackgroundColor(backgroundColor);
        }
        sToast.setGravity(gravity, xOffset, yOffset);
        sToast.show();
    }
    /**
     * 取消吐司显示
     */
    public static void cancel() {
        if (sToast != null) {
            sToast.cancel();
            sToast = null;
        }
    }
}
app_base/src/test/java/com/android/app_base/ExampleUnitTest.java
New file
@@ -0,0 +1,17 @@
package com.android.app_base;
import org.junit.Test;
import static org.junit.Assert.*;
/**
 * Example local unit test, which will execute on the development machine (host).
 *
 * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
 */
public class ExampleUnitTest {
    @Test
    public void addition_isCorrect() {
        assertEquals(4, 2 + 2);
    }
}
build.gradle
New file
@@ -0,0 +1,26 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
    repositories {
        google()
        mavenCentral()
        maven {
            url 'https://jitpack.io'
        }
        maven {
            allowInsecureProtocol = true
            url 'http://maven.aliyun.com/nexus/content/groups/public/'
        }
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:7.1.3'
        classpath 'com.blankj:bus-gradle-plugin:latest.release'
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}
task clean(type: Delete) {
    delete rootProject.buildDir
}
gradle.properties
New file
@@ -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
# 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
android.injected.testOnly=false
gradle/wrapper/gradle-wrapper.jar
Binary files differ
gradle/wrapper/gradle-wrapper.properties
New file
@@ -0,0 +1,6 @@
#Wed Mar 01 13:44:42 CST 2023
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
gradlew
New file
@@ -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" "$@"
gradlew.bat
New file
@@ -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
settings.gradle
New file
@@ -0,0 +1,19 @@
pluginManagement {
    repositories {
        gradlePluginPortal()
        google()
        mavenCentral()
    }
}
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
        jcenter()
        maven { url 'https://jitpack.io' }
    }
}
rootProject.name = "zhangshi_app_android"
include ':app'
include ':app_base'