From 99f403b54a2633a07174144ce2f6011e39aa19f7 Mon Sep 17 00:00:00 2001
From: Linjiajia <319408893@qq.com>
Date: 星期四, 09 三月 2023 18:42:33 +0800
Subject: [PATCH] mvvm基础框架
---
app_base/src/main/java/com/android/app_base/base/adapter/BaseRVAdapter.java | 481 +++++++
.idea/inspectionProfiles/Project_Default.xml | 11
app_base/proguard-rules.pro | 21
app_base/src/androidTest/java/com/android/app_base/ExampleInstrumentedTest.java | 26
app/src/main/java/com/application/zhangshi_app_android/model/source/HttpDataSource.java | 9
app/src/main/java/com/application/zhangshi_app_android/view/MainActivity.java | 55
app/src/androidTest/java/com/application/zhangshi_app_android/ExampleInstrumentedTest.java | 26
.idea/dbnavigator.xml | 420 ++++++
app/src/main/java/com/application/zhangshi_app_android/model/source/LocalDataSource.java | 9
gradlew.bat | 89 +
.idea/misc.xml | 10
app_base/src/main/java/com/android/app_base/utils/SPUtils.java | 282 ++++
app/src/main/res/layout/activity_main.xml | 36
app_base/src/main/java/com/android/app_base/utils/ToastUtils.java | 429 ++++++
app/build.gradle | 61
app_base/src/main/java/com/android/app_base/helper/DoubleClickHelper.java | 29
gradlew | 185 ++
app/src/main/java/com/application/zhangshi_app_android/model/source/http/HttpDataSourceImpl.java | 38
app/src/main/res/values/strings.xml | 3
app_base/src/main/java/com/android/app_base/base/viewmodel/BaseViewModel.java | 120 +
.idea/.gitignore | 3
app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml | 5
app/src/main/res/mipmap-xhdpi/ic_launcher.webp | 0
app_base/src/test/java/com/android/app_base/ExampleUnitTest.java | 17
app_base/src/main/java/com/android/app_base/base/StateViewEnum.java | 14
app/src/main/res/mipmap-hdpi/ic_launcher.webp | 0
.idea/compiler.xml | 6
app_base/src/main/java/com/android/app_base/manager/AppManager.java | 212 +++
app/src/main/res/mipmap-mdpi/ic_launcher.webp | 0
app/proguard-rules.pro | 21
app/src/main/res/xml/backup_rules.xml | 13
.gitignore | 88 +
app/src/main/res/xml/data_extraction_rules.xml | 19
app/src/main/java/com/application/zhangshi_app_android/model/source/http/ApiService.java | 9
app_base/src/main/java/com/android/app_base/base/view/BaseFragment.java | 191 ++
app_base/src/main/java/com/android/app_base/http/interceptor/CacheInterceptor.java | 56
app/src/main/res/values/themes.xml | 16
app_base/src/main/java/com/android/app_base/http/RetrofitManager.java | 60
gradle/wrapper/gradle-wrapper.jar | 0
app/src/main/res/drawable/ic_launcher_background.xml | 170 ++
app_base/src/main/java/com/android/app_base/base/BaseApplication.java | 73 +
gradle/wrapper/gradle-wrapper.properties | 6
app_base/build.gradle | 58
app/src/main/res/mipmap-hdpi/ic_launcher_round.webp | 0
app/src/main/res/values/colors.xml | 10
app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp | 0
app/src/main/res/values-night/themes.xml | 16
app_base/.gitignore | 1
build.gradle | 26
app/src/main/java/com/application/zhangshi_app_android/config/BaseConfig.java | 10
app_base/src/main/AndroidManifest.xml | 5
app/src/test/java/com/application/zhangshi_app_android/ExampleUnitTest.java | 17
app/src/main/res/drawable-v24/ic_launcher_foreground.xml | 30
app/src/main/res/mipmap-xxhdpi/ic_launcher.webp | 0
app_base/src/main/java/com/android/app_base/http/ResultData.java | 45
app/.gitignore | 1
app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp | 0
settings.gradle | 19
app/src/main/res/mipmap-anydpi-v33/ic_launcher.xml | 6
app/src/main/java/com/application/zhangshi_app_android/MyApplication.java | 11
app/src/main/AndroidManifest.xml | 29
app_base/consumer-rules.pro | 0
app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp | 0
app/src/main/java/com/application/zhangshi_app_android/model/DataRepository.java | 45
app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml | 5
app/src/main/java/com/application/zhangshi_app_android/model/source/local/LocalDataSourceImpl.java | 32
app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp | 0
gradle.properties | 23
app_base/src/main/java/com/android/app_base/base/view/BaseActivity.java | 177 ++
app/src/main/res/mipmap-mdpi/ic_launcher_round.webp | 0
app_base/src/main/java/com/android/app_base/http/OkHttpHelper.java | 96 +
app/src/main/java/com/application/zhangshi_app_android/viewmodel/MainViewModel.java | 25
app_base/src/main/java/com/android/app_base/base/model/BaseModel.java | 9
73 files changed, 4,015 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..23de6e2
--- /dev/null
+++ b/.gitignore
@@ -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
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..26d3352
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..fb7f4a8
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="CompilerConfiguration">
+ <bytecodeTargetLevel target="11" />
+ </component>
+</project>
\ No newline at end of file
diff --git a/.idea/dbnavigator.xml b/.idea/dbnavigator.xml
new file mode 100644
index 0000000..f0bf29e
--- /dev/null
+++ b/.idea/dbnavigator.xml
@@ -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>
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..1d88dea
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -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>
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..360e6d4
--- /dev/null
+++ b/.idea/misc.xml
@@ -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>
\ No newline at end of file
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..4ddb9db
--- /dev/null
+++ b/app/build.gradle
@@ -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 //鏄惁娓呯悊鏃犵敤璧勬簮,渚濊禆浜巑inifyEnabled
+ 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 //涓嶅惎鐢≒roguard
+ shrinkResources false //鏄惁娓呯悊鏃犵敤璧勬簮,渚濊禆浜巑inifyEnabled
+ 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')
+
+}
\ No newline at end of file
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/app/src/androidTest/java/com/application/zhangshi_app_android/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/application/zhangshi_app_android/ExampleInstrumentedTest.java
new file mode 100644
index 0000000..877f5e7
--- /dev/null
+++ b/app/src/androidTest/java/com/application/zhangshi_app_android/ExampleInstrumentedTest.java
@@ -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());
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..667c88a
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -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>
\ No newline at end of file
diff --git a/app/src/main/java/com/application/zhangshi_app_android/MyApplication.java b/app/src/main/java/com/application/zhangshi_app_android/MyApplication.java
new file mode 100644
index 0000000..b0ad674
--- /dev/null
+++ b/app/src/main/java/com/application/zhangshi_app_android/MyApplication.java
@@ -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 {
+}
diff --git a/app/src/main/java/com/application/zhangshi_app_android/config/BaseConfig.java b/app/src/main/java/com/application/zhangshi_app_android/config/BaseConfig.java
new file mode 100644
index 0000000..7d89f79
--- /dev/null
+++ b/app/src/main/java/com/application/zhangshi_app_android/config/BaseConfig.java
@@ -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 = "";
+}
diff --git a/app/src/main/java/com/application/zhangshi_app_android/model/DataRepository.java b/app/src/main/java/com/application/zhangshi_app_android/model/DataRepository.java
new file mode 100644
index 0000000..4a3827c
--- /dev/null
+++ b/app/src/main/java/com/application/zhangshi_app_android/model/DataRepository.java
@@ -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;
+ }
+}
diff --git a/app/src/main/java/com/application/zhangshi_app_android/model/source/HttpDataSource.java b/app/src/main/java/com/application/zhangshi_app_android/model/source/HttpDataSource.java
new file mode 100644
index 0000000..effe0c0
--- /dev/null
+++ b/app/src/main/java/com/application/zhangshi_app_android/model/source/HttpDataSource.java
@@ -0,0 +1,9 @@
+package com.application.zhangshi_app_android.model.source;
+
+/**
+ * @author Ljj
+ * @date 2023.03.01. 21:18
+ * @desc 缃戠粶璇锋眰鏁版嵁婧� 鎺ュ彛
+ */
+public interface HttpDataSource {
+}
diff --git a/app/src/main/java/com/application/zhangshi_app_android/model/source/LocalDataSource.java b/app/src/main/java/com/application/zhangshi_app_android/model/source/LocalDataSource.java
new file mode 100644
index 0000000..6ab6a4c
--- /dev/null
+++ b/app/src/main/java/com/application/zhangshi_app_android/model/source/LocalDataSource.java
@@ -0,0 +1,9 @@
+package com.application.zhangshi_app_android.model.source;
+
+/**
+ * @author Ljj
+ * @date 2023.03.01. 21:19
+ * @desc 鏈湴鏁版嵁婧� 鎺ュ彛
+ */
+public interface LocalDataSource {
+}
diff --git a/app/src/main/java/com/application/zhangshi_app_android/model/source/http/ApiService.java b/app/src/main/java/com/application/zhangshi_app_android/model/source/http/ApiService.java
new file mode 100644
index 0000000..9053efc
--- /dev/null
+++ b/app/src/main/java/com/application/zhangshi_app_android/model/source/http/ApiService.java
@@ -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 {
+}
diff --git a/app/src/main/java/com/application/zhangshi_app_android/model/source/http/HttpDataSourceImpl.java b/app/src/main/java/com/application/zhangshi_app_android/model/source/http/HttpDataSourceImpl.java
new file mode 100644
index 0000000..8ad8889
--- /dev/null
+++ b/app/src/main/java/com/application/zhangshi_app_android/model/source/http/HttpDataSourceImpl.java
@@ -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;
+ }
+
+
+}
diff --git a/app/src/main/java/com/application/zhangshi_app_android/model/source/local/LocalDataSourceImpl.java b/app/src/main/java/com/application/zhangshi_app_android/model/source/local/LocalDataSourceImpl.java
new file mode 100644
index 0000000..83596c9
--- /dev/null
+++ b/app/src/main/java/com/application/zhangshi_app_android/model/source/local/LocalDataSourceImpl.java
@@ -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() {
+
+ }
+}
diff --git a/app/src/main/java/com/application/zhangshi_app_android/view/MainActivity.java b/app/src/main/java/com/application/zhangshi_app_android/view/MainActivity.java
new file mode 100644
index 0000000..1a5269c
--- /dev/null
+++ b/app/src/main/java/com/application/zhangshi_app_android/view/MainActivity.java
@@ -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("鍐嶆寜涓�娆¢��鍑�");
+ }
+ }
+}
diff --git a/app/src/main/java/com/application/zhangshi_app_android/viewmodel/MainViewModel.java b/app/src/main/java/com/application/zhangshi_app_android/viewmodel/MainViewModel.java
new file mode 100644
index 0000000..f13fb10
--- /dev/null
+++ b/app/src/main/java/com/application/zhangshi_app_android/viewmodel/MainViewModel.java
@@ -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;
+ }
+}
diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..2b068d1
--- /dev/null
+++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -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>
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..07d5da9
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="108dp"
+ android:height="108dp"
+ android:viewportWidth="108"
+ android:viewportHeight="108">
+ <path
+ android:fillColor="#3DDC84"
+ android:pathData="M0,0h108v108h-108z" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M9,0L9,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,0L19,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M29,0L29,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M39,0L39,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M49,0L49,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M59,0L59,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M69,0L69,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M79,0L79,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M89,0L89,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M99,0L99,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,9L108,9"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,19L108,19"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,29L108,29"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,39L108,39"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,49L108,49"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,59L108,59"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,69L108,69"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,79L108,79"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,89L108,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,99L108,99"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,29L89,29"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,39L89,39"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,49L89,49"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,59L89,59"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,69L89,69"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,79L89,79"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M29,19L29,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M39,19L39,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M49,19L49,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M59,19L59,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M69,19L69,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M79,19L79,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+</vector>
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..bd629de
--- /dev/null
+++ b/app/src/main/res/layout/activity_main.xml
@@ -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>
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@drawable/ic_launcher_background" />
+ <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@drawable/ic_launcher_background" />
+ <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v33/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v33/ic_launcher.xml
new file mode 100644
index 0000000..6f3b755
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v33/ic_launcher.xml
@@ -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>
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 0000000..c209e78
--- /dev/null
+++ b/app/src/main/res/mipmap-hdpi/ic_launcher.webp
Binary files differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..b2dfe3d
--- /dev/null
+++ b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
Binary files differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 0000000..4f0f1d6
--- /dev/null
+++ b/app/src/main/res/mipmap-mdpi/ic_launcher.webp
Binary files differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..62b611d
--- /dev/null
+++ b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
Binary files differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 0000000..948a307
--- /dev/null
+++ b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
Binary files differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..1b9a695
--- /dev/null
+++ b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..28d4b77
--- /dev/null
+++ b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
Binary files differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9287f50
--- /dev/null
+++ b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..aa7d642
--- /dev/null
+++ b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
Binary files differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9126ae3
--- /dev/null
+++ b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml
new file mode 100644
index 0000000..19ed650
--- /dev/null
+++ b/app/src/main/res/values-night/themes.xml
@@ -0,0 +1,16 @@
+<resources xmlns:tools="http://schemas.android.com/tools">
+ <!-- Base application theme. -->
+ <style name="Theme.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>
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..f8c6127
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <color name="purple_200">#FFBB86FC</color>
+ <color name="purple_500">#FF6200EE</color>
+ <color name="purple_700">#FF3700B3</color>
+ <color name="teal_200">#FF03DAC5</color>
+ <color name="teal_700">#FF018786</color>
+ <color name="black">#FF000000</color>
+ <color name="white">#FFFFFFFF</color>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..d45f873
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+<resources>
+ <string name="app_name">zhangshi_app_android</string>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
new file mode 100644
index 0000000..ae6096a
--- /dev/null
+++ b/app/src/main/res/values/themes.xml
@@ -0,0 +1,16 @@
+<resources xmlns:tools="http://schemas.android.com/tools">
+ <!-- Base application theme. -->
+ <style name="Theme.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>
\ No newline at end of file
diff --git a/app/src/main/res/xml/backup_rules.xml b/app/src/main/res/xml/backup_rules.xml
new file mode 100644
index 0000000..fa0f996
--- /dev/null
+++ b/app/src/main/res/xml/backup_rules.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Sample backup rules file; uncomment and customize as necessary.
+ See https://developer.android.com/guide/topics/data/autobackup
+ for details.
+ Note: This file is ignored for devices older that API 31
+ See https://developer.android.com/about/versions/12/backup-restore
+-->
+<full-backup-content>
+ <!--
+ <include domain="sharedpref" path="."/>
+ <exclude domain="sharedpref" path="device.xml"/>
+-->
+</full-backup-content>
\ No newline at end of file
diff --git a/app/src/main/res/xml/data_extraction_rules.xml b/app/src/main/res/xml/data_extraction_rules.xml
new file mode 100644
index 0000000..9ee9997
--- /dev/null
+++ b/app/src/main/res/xml/data_extraction_rules.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Sample data extraction rules file; uncomment and customize as necessary.
+ See https://developer.android.com/about/versions/12/backup-restore#xml-changes
+ for details.
+-->
+<data-extraction-rules>
+ <cloud-backup>
+ <!-- TODO: Use <include> and <exclude> to control what is backed up.
+ <include .../>
+ <exclude .../>
+ -->
+ </cloud-backup>
+ <!--
+ <device-transfer>
+ <include .../>
+ <exclude .../>
+ </device-transfer>
+ -->
+</data-extraction-rules>
\ No newline at end of file
diff --git a/app/src/test/java/com/application/zhangshi_app_android/ExampleUnitTest.java b/app/src/test/java/com/application/zhangshi_app_android/ExampleUnitTest.java
new file mode 100644
index 0000000..eb53a85
--- /dev/null
+++ b/app/src/test/java/com/application/zhangshi_app_android/ExampleUnitTest.java
@@ -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);
+ }
+}
\ No newline at end of file
diff --git a/app_base/.gitignore b/app_base/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app_base/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app_base/build.gradle b/app_base/build.gradle
new file mode 100644
index 0000000..0656887
--- /dev/null
+++ b/app_base/build.gradle
@@ -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鍐呯疆浜唎khttp)
+ 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'//瀵筊xjava閫傞厤
+ api 'com.squareup.retrofit2:retrofit-converters:2.4.0'//鏁版嵁瑙f瀽鍣�
+ 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'
+
+}
\ No newline at end of file
diff --git a/app_base/consumer-rules.pro b/app_base/consumer-rules.pro
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app_base/consumer-rules.pro
diff --git a/app_base/proguard-rules.pro b/app_base/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/app_base/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/app_base/src/androidTest/java/com/android/app_base/ExampleInstrumentedTest.java b/app_base/src/androidTest/java/com/android/app_base/ExampleInstrumentedTest.java
new file mode 100644
index 0000000..8363d29
--- /dev/null
+++ b/app_base/src/androidTest/java/com/android/app_base/ExampleInstrumentedTest.java
@@ -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());
+ }
+}
\ No newline at end of file
diff --git a/app_base/src/main/AndroidManifest.xml b/app_base/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..294dbaa
--- /dev/null
+++ b/app_base/src/main/AndroidManifest.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <uses-permission android:name="android.permission.INTERNET" />
+</manifest>
\ No newline at end of file
diff --git a/app_base/src/main/java/com/android/app_base/base/BaseApplication.java b/app_base/src/main/java/com/android/app_base/base/BaseApplication.java
new file mode 100644
index 0000000..f903b93
--- /dev/null
+++ b/app_base/src/main/java/com/android/app_base/base/BaseApplication.java
@@ -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鏂规硶鍒濆鍖朆aseApplication
+ * @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杩愯鐨凙pplication
+ */
+ public static Application getInstance() {
+ if (sInstance == null) {
+ throw new NullPointerException("please inherit BaseApplication or call setApplication.");
+ }
+ return sInstance;
+ }
+}
diff --git a/app_base/src/main/java/com/android/app_base/base/StateViewEnum.java b/app_base/src/main/java/com/android/app_base/base/StateViewEnum.java
new file mode 100644
index 0000000..454ffe1
--- /dev/null
+++ b/app_base/src/main/java/com/android/app_base/base/StateViewEnum.java
@@ -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,// 闅愯棌
+}
diff --git a/app_base/src/main/java/com/android/app_base/base/adapter/BaseRVAdapter.java b/app_base/src/main/java/com/android/app_base/base/adapter/BaseRVAdapter.java
new file mode 100644
index 0000000..da0d572
--- /dev/null
+++ b/app_base/src/main/java/com/android/app_base/base/adapter/BaseRVAdapter.java
@@ -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;
+ }
+
+ /**
+ * 鑷畾涔塚iewHolder,鍩虹被
+ * @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);
+ }
+
+ /**
+ * 鑷畾涔塕ecyclerView鐨勬粦鍔ㄧ洃鍚櫒
+ */
+ 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) {
+ // 姝e湪婊氬姩涓�
+ onScrollingListener.onScrolling(recyclerView);
+ }
+ }
+ }
+}
+
diff --git a/app_base/src/main/java/com/android/app_base/base/model/BaseModel.java b/app_base/src/main/java/com/android/app_base/base/model/BaseModel.java
new file mode 100644
index 0000000..5456c9a
--- /dev/null
+++ b/app_base/src/main/java/com/android/app_base/base/model/BaseModel.java
@@ -0,0 +1,9 @@
+package com.android.app_base.base.model;
+
+public abstract class BaseModel {
+
+ public BaseModel() {
+ }
+
+ public abstract void onCleared();
+}
diff --git a/app_base/src/main/java/com/android/app_base/base/view/BaseActivity.java b/app_base/src/main/java/com/android/app_base/base/view/BaseActivity.java
new file mode 100644
index 0000000..be3f169
--- /dev/null
+++ b/app_base/src/main/java/com/android/app_base/base/view/BaseActivity.java
@@ -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灞傝浆鍒癡iew灞傜殑浜嬩欢娉ㄥ唽
+ initLiveDataObserve();
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ //瑙i櫎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);
+ //璁¬iewModel鎷ユ湁View鐨勭敓鍛藉懆鏈熸劅搴�
+ getLifecycle().addObserver(viewModel);
+
+ }
+
+ /**
+ * 鍒濆鍖朧iewModel
+ * @return 杩斿洖涓�涓猇iewModel
+ */
+ 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);
+ }
+
+ /**
+ * 瀵圭姸鎬佽鍥緇iveData杩涜瑙傚療鐩戝惉
+ */
+ 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() {
+ }
+
+ /**
+ * 鑾峰彇鏍瑰竷灞�鐨刬d锛岀敱瀛愮被瀹炵幇杩斿洖
+ * @return layout鐨刬d
+ */
+ public abstract int getLayoutId();
+
+ /**
+ * 鑾峰彇ViewModel鐨刬d锛岀敱瀛愮被瀹炵幇杩斿洖
+ * @return BR涓璿iewModel鐨刬d
+ */
+ public abstract int getVariableId();
+
+ /**
+ * 鍒濆鍖栭〉闈㈠弬鏁帮紝渚嬪浠庝笂涓�涓猘ctivity浼犻�掕繃鏉ョ殑鍙傛暟
+ */
+ public abstract void initParam();
+
+ /**
+ * 鍒濆鍖栭〉闈iew锛屼緥濡備竴浜泇iew鐨勯殣钘忔垨鏄剧ず,浠ュ強鐐瑰嚮閫昏緫绛夌敤鎴蜂氦浜�
+ */
+ public abstract void initView();
+
+ /**
+ * 鍒濆鍖栭〉闈㈡暟鎹�
+ */
+ public abstract void initData();
+
+ /**
+ * 鍒濆鍖朙iveData鐨勭洃鍚�
+ * 绠�鍗曠殑鏁版嵁灞曠ず鍙�氳繃DataBinding鐩存帴鍙屽悜缁戝畾鍒皒ml涓�
+ */
+ public abstract void initLiveDataObserve();
+
+}
diff --git a/app_base/src/main/java/com/android/app_base/base/view/BaseFragment.java b/app_base/src/main/java/com/android/app_base/base/view/BaseFragment.java
new file mode 100644
index 0000000..d76223b
--- /dev/null
+++ b/app_base/src/main/java/com/android/app_base/base/view/BaseFragment.java
@@ -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灞傝浆鍒癡iew灞傜殑浜嬩欢娉ㄥ唽
+ initLiveDataObserve();
+ }
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ //瑙i櫎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);
+ //璁¬iewModel鎷ユ湁View鐨勭敓鍛藉懆鏈熸劅搴�
+ getLifecycle().addObserver(viewModel);
+
+ }
+
+ /**
+ * 鍒濆鍖朧iewModel
+ * @return 杩斿洖涓�涓猇iewModel
+ */
+ 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);
+ }
+
+ /**
+ * 瀵圭姸鎬佽鍥緇iveData杩涜瑙傚療鐩戝惉
+ */
+ 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() {
+ }
+
+ /**
+ * 鑾峰彇鏍瑰竷灞�鐨刬d锛岀敱瀛愮被瀹炵幇杩斿洖
+ * @return layout鐨刬d
+ */
+ public abstract int getLayoutId();
+
+ /**
+ * 鑾峰彇ViewModel鐨刬d锛岀敱瀛愮被瀹炵幇杩斿洖
+ * @return BR涓璿iewModel鐨刬d
+ */
+ public abstract int getVariableId();
+
+ /**
+ * 鍒濆鍖栭〉闈㈠弬鏁帮紝渚嬪浠庝笂涓�涓猘ctivity浼犻�掕繃鏉ョ殑鍙傛暟
+ */
+ abstract void initParam();
+
+ /**
+ * 鍒濆鍖栭〉闈iew锛屼緥濡備竴浜泇iew鐨勯殣钘忔垨鏄剧ず,浠ュ強鐐瑰嚮閫昏緫绛夌敤鎴蜂氦浜�
+ */
+ abstract void initView();
+
+ /**
+ * 鍒濆鍖栭〉闈㈡暟鎹�
+ */
+ abstract void initData();
+
+ /**
+ * 鍒濆鍖朙iveData鐨勭洃鍚�
+ * 绠�鍗曠殑鏁版嵁灞曠ず鍙�氳繃DataBinding鐩存帴鍙屽悜缁戝畾鍒皒ml涓�
+ */
+ abstract void initLiveDataObserve();
+
+}
diff --git a/app_base/src/main/java/com/android/app_base/base/viewmodel/BaseViewModel.java b/app_base/src/main/java/com/android/app_base/base/viewmodel/BaseViewModel.java
new file mode 100644
index 0000000..078d3c0
--- /dev/null
+++ b/app_base/src/main/java/com/android/app_base/base/viewmodel/BaseViewModel.java
@@ -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锛屼富瑕侀拡瀵筊xJava寮傛鎿嶄綔閫犳垚鐨勫唴瀛樻硠婕�
+ 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);
+ }
+
+ /**
+ * 鑾峰彇鐘舵�佽鍥綥iveData
+ */
+ 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);
+ }
+}
diff --git a/app_base/src/main/java/com/android/app_base/helper/DoubleClickHelper.java b/app_base/src/main/java/com/android/app_base/helper/DoubleClickHelper.java
new file mode 100644
index 0000000..b565e02
--- /dev/null
+++ b/app_base/src/main/java/com/android/app_base/helper/DoubleClickHelper.java
@@ -0,0 +1,29 @@
+package com.android.app_base.helper;
+
+import android.os.SystemClock;
+
+/**
+ * @desc 闃插弻鍑诲垽鏂伐鍏风被
+ */
+public final class DoubleClickHelper {
+
+ /** 鏁扮粍鐨勯暱搴︿负2浠h〃鍙褰曞弻鍑绘搷浣� */
+ 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);
+ }
+}
\ No newline at end of file
diff --git a/app_base/src/main/java/com/android/app_base/http/OkHttpHelper.java b/app_base/src/main/java/com/android/app_base/http/OkHttpHelper.java
new file mode 100644
index 0000000..3f75c98
--- /dev/null
+++ b/app_base/src/main/java/com/android/app_base/http/OkHttpHelper.java
@@ -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;
+ }
+}
diff --git a/app_base/src/main/java/com/android/app_base/http/ResultData.java b/app_base/src/main/java/com/android/app_base/http/ResultData.java
new file mode 100644
index 0000000..5673187
--- /dev/null
+++ b/app_base/src/main/java/com/android/app_base/http/ResultData.java
@@ -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;
+ }
+}
diff --git a/app_base/src/main/java/com/android/app_base/http/RetrofitManager.java b/app_base/src/main/java/com/android/app_base/http/RetrofitManager.java
new file mode 100644
index 0000000..d997198
--- /dev/null
+++ b/app_base/src/main/java/com/android/app_base/http/RetrofitManager.java
@@ -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;
+ }
+}
diff --git a/app_base/src/main/java/com/android/app_base/http/interceptor/CacheInterceptor.java b/app_base/src/main/java/com/android/app_base/http/interceptor/CacheInterceptor.java
new file mode 100644
index 0000000..b0927d3
--- /dev/null
+++ b/app_base/src/main/java/com/android/app_base/http/interceptor/CacheInterceptor.java
@@ -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();
+ }
+ }
+}
diff --git a/app_base/src/main/java/com/android/app_base/manager/AppManager.java b/app_base/src/main/java/com/android/app_base/manager/AppManager.java
new file mode 100644
index 0000000..2ed016f
--- /dev/null
+++ b/app_base/src/main/java/com/android/app_base/manager/AppManager.java
@@ -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);
+ }
+
+ /**
+ * 绉婚櫎鎸囧畾鐨凙ctivity
+ */
+ public void removeActivity(Activity activity) {
+ if (activity != null) {
+ activityStack.remove(activity);
+ }
+ }
+
+
+ /**
+ * 鏄惁鏈塧ctivity
+ */
+ 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);
+ }
+
+ /**
+ * 缁撴潫鎸囧畾鐨凙ctivity
+ */
+ public void finishActivity(Activity activity) {
+ if (activity != null) {
+ if (!activity.isFinishing()) {
+ activity.finish();
+ }
+ }
+ }
+ /**
+ * 缁撴潫鎸囧畾绫诲悕鐨凙ctivity
+ */
+ public void finishActivity(Class<?> cls) {
+ for (Activity activity : activityStack) {
+ if (activity.getClass().equals(cls)) {
+ finishActivity(activity);
+ break;
+ }
+ }
+ }
+
+ /**
+ * 缁撴潫鎵�鏈堿ctivity
+ */
+ public void finishAllActivity() {
+ for (int i = 0, size = activityStack.size(); i < size; i++) {
+ if (null != activityStack.get(i)) {
+ finishActivity(activityStack.get(i));
+ }
+ }
+ activityStack.clear();
+ }
+
+ /**
+ * 鑾峰彇鎸囧畾鐨凙ctivity
+ */
+ 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);
+ }
+
+ /**
+ * 绉婚櫎鎸囧畾鐨凢ragment
+ */
+ public void removeFragment(Fragment fragment) {
+ if (fragment != null) {
+ fragmentStack.remove(fragment);
+ }
+ }
+
+
+ /**
+ * 鏄惁鏈塅ragment
+ */
+ 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()鏄疉ctivity鐨勭被鏂规硶锛屼粎浠呴拡瀵笰ctivity锛屽綋璋冪敤finish()鏃讹紝鍙槸灏嗘椿鍔ㄦ帹鍚戝悗鍙帮紝骞舵病鏈夌珛鍗抽噴鏀惧唴瀛橈紝娲诲姩鐨勮祫婧愬苟娌℃湁琚竻鐞嗭紱褰撹皟鐢⊿ystem.exit(0)鏃讹紝閫�鍑哄綋鍓岮ctivity骞堕噴鏀捐祫婧愶紙鍐呭瓨锛夛紝浣嗘槸璇ユ柟娉曚笉鍙互缁撴潫鏁翠釜App濡傛湁澶氫釜Activty鎴栬�呮湁鍏朵粬缁勪欢service绛変笉浼氱粨鏉熴��
+// 鍏跺疄android鐨勬満鍒跺喅瀹氫簡鐢ㄦ埛鏃犳硶瀹屽叏閫�鍑哄簲鐢紝褰撲綘鐨刟pplication鏈�闀挎椂闂存病鏈夎鐢ㄨ繃鐨勬椂鍊欙紝android鑷韩浼氬喅瀹氬皢application鍏抽棴浜嗐��
+ //System.exit(0);
+ } catch (Exception e) {
+ activityStack.clear();
+ e.printStackTrace();
+ }
+ }
+}
\ No newline at end of file
diff --git a/app_base/src/main/java/com/android/app_base/utils/SPUtils.java b/app_base/src/main/java/com/android/app_base/utils/SPUtils.java
new file mode 100644
index 0000000..830891c
--- /dev/null
+++ b/app_base/src/main/java/com/android/app_base/utils/SPUtils.java
@@ -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涓啓鍏tring
+ *
+ * @param key 閿�
+ * @param value 鍊�
+ */
+ public void put(@NonNull final String key, @NonNull final String value) {
+ sp.edit().putString(key, value).apply();
+ }
+
+ /**
+ * SP涓鍙朣tring
+ *
+ * @param key 閿�
+ * @return 瀛樺湪杩斿洖瀵瑰簲鍊硷紝涓嶅瓨鍦ㄨ繑鍥為粯璁ゅ�納@code ""}
+ */
+ public String getString(@NonNull final String key) {
+ return getString(key, "");
+ }
+
+ /**
+ * SP涓鍙朣tring
+ *
+ * @param key 閿�
+ * @param defaultValue 榛樿鍊�
+ * @return 瀛樺湪杩斿洖瀵瑰簲鍊硷紝涓嶅瓨鍦ㄨ繑鍥為粯璁ゅ�納@code defaultValue}
+ */
+ public String getString(@NonNull final String key, @NonNull final String defaultValue) {
+ return sp.getString(key, defaultValue);
+ }
+
+ /**
+ * SP涓啓鍏nt
+ *
+ * @param key 閿�
+ * @param value 鍊�
+ */
+ public void put(@NonNull final String key, final int value) {
+ sp.edit().putInt(key, value).apply();
+ }
+
+ /**
+ * SP涓鍙杋nt
+ *
+ * @param key 閿�
+ * @return 瀛樺湪杩斿洖瀵瑰簲鍊硷紝涓嶅瓨鍦ㄨ繑鍥為粯璁ゅ��-1
+ */
+ public int getInt(@NonNull final String key) {
+ return getInt(key, -1);
+ }
+
+ /**
+ * SP涓鍙杋nt
+ *
+ * @param key 閿�
+ * @param defaultValue 榛樿鍊�
+ * @return 瀛樺湪杩斿洖瀵瑰簲鍊硷紝涓嶅瓨鍦ㄨ繑鍥為粯璁ゅ�納@code defaultValue}
+ */
+ public int getInt(@NonNull final String key, final int defaultValue) {
+ return sp.getInt(key, defaultValue);
+ }
+
+ /**
+ * SP涓啓鍏ong
+ *
+ * @param key 閿�
+ * @param value 鍊�
+ */
+ public void put(@NonNull final String key, final long value) {
+ sp.edit().putLong(key, value).apply();
+ }
+
+ /**
+ * SP涓鍙杔ong
+ *
+ * @param key 閿�
+ * @return 瀛樺湪杩斿洖瀵瑰簲鍊硷紝涓嶅瓨鍦ㄨ繑鍥為粯璁ゅ��-1
+ */
+ public long getLong(@NonNull final String key) {
+ return getLong(key, -1L);
+ }
+
+ /**
+ * SP涓鍙杔ong
+ *
+ * @param key 閿�
+ * @param defaultValue 榛樿鍊�
+ * @return 瀛樺湪杩斿洖瀵瑰簲鍊硷紝涓嶅瓨鍦ㄨ繑鍥為粯璁ゅ�納@code defaultValue}
+ */
+ public long getLong(@NonNull final String key, final long defaultValue) {
+ return sp.getLong(key, defaultValue);
+ }
+
+ /**
+ * SP涓啓鍏loat
+ *
+ * @param key 閿�
+ * @param value 鍊�
+ */
+ public void put(@NonNull final String key, final float value) {
+ sp.edit().putFloat(key, value).apply();
+ }
+
+ /**
+ * SP涓鍙杅loat
+ *
+ * @param key 閿�
+ * @return 瀛樺湪杩斿洖瀵瑰簲鍊硷紝涓嶅瓨鍦ㄨ繑鍥為粯璁ゅ��-1
+ */
+ public float getFloat(@NonNull final String key) {
+ return getFloat(key, -1f);
+ }
+
+ /**
+ * SP涓鍙杅loat
+ *
+ * @param key 閿�
+ * @param defaultValue 榛樿鍊�
+ * @return 瀛樺湪杩斿洖瀵瑰簲鍊硷紝涓嶅瓨鍦ㄨ繑鍥為粯璁ゅ�納@code defaultValue}
+ */
+ public float getFloat(@NonNull final String key, final float defaultValue) {
+ return sp.getFloat(key, defaultValue);
+ }
+
+ /**
+ * SP涓啓鍏oolean
+ *
+ * @param key 閿�
+ * @param value 鍊�
+ */
+ public void put(@NonNull final String key, final boolean value) {
+ sp.edit().putBoolean(key, value).apply();
+ }
+
+ /**
+ * SP涓鍙朾oolean
+ *
+ * @param key 閿�
+ * @return 瀛樺湪杩斿洖瀵瑰簲鍊硷紝涓嶅瓨鍦ㄨ繑鍥為粯璁ゅ�納@code false}
+ */
+ public boolean getBoolean(@NonNull final String key) {
+ return getBoolean(key, false);
+ }
+
+ /**
+ * SP涓鍙朾oolean
+ *
+ * @param key 閿�
+ * @param defaultValue 榛樿鍊�
+ * @return 瀛樺湪杩斿洖瀵瑰簲鍊硷紝涓嶅瓨鍦ㄨ繑鍥為粯璁ゅ�納@code defaultValue}
+ */
+ public boolean getBoolean(@NonNull final String key, final boolean defaultValue) {
+ return sp.getBoolean(key, defaultValue);
+ }
+
+ /**
+ * SP涓啓鍏tring闆嗗悎
+ *
+ * @param key 閿�
+ * @param values 鍊�
+ */
+ public void put(@NonNull final String key, @NonNull final Set<String> values) {
+ sp.edit().putStringSet(key, values).apply();
+ }
+
+ /**
+ * SP涓鍙朣tringSet
+ *
+ * @param key 閿�
+ * @return 瀛樺湪杩斿洖瀵瑰簲鍊硷紝涓嶅瓨鍦ㄨ繑鍥為粯璁ゅ�納@code Collections.<String>emptySet()}
+ */
+ public Set<String> getStringSet(@NonNull final String key) {
+ return getStringSet(key, Collections.<String>emptySet());
+ }
+
+ /**
+ * SP涓鍙朣tringSet
+ *
+ * @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;
+ }
+}
diff --git a/app_base/src/main/java/com/android/app_base/utils/ToastUtils.java b/app_base/src/main/java/com/android/app_base/utils/ToastUtils.java
new file mode 100644
index 0000000..87b9e3b
--- /dev/null
+++ b/app_base/src/main/java/com/android/app_base/utils/ToastUtils.java
@@ -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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/app_base/src/test/java/com/android/app_base/ExampleUnitTest.java b/app_base/src/test/java/com/android/app_base/ExampleUnitTest.java
new file mode 100644
index 0000000..9691579
--- /dev/null
+++ b/app_base/src/test/java/com/android/app_base/ExampleUnitTest.java
@@ -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);
+ }
+}
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..0b7f65c
--- /dev/null
+++ b/build.gradle
@@ -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
+}
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..d5ff165
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,23 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# 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
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..e708b1c
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..7e4c7eb
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -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
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..4f906e0
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=`expr $i + 1`
+ done
+ case $i in
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..107acd3
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..bc2be8b
--- /dev/null
+++ b/settings.gradle
@@ -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'
--
Gitblit v1.9.1