├── .circleci
└── config.yml
├── .editorconfig
├── .github
└── workflows
│ ├── build.yml
│ ├── docs.yml
│ └── issues-stale.yml
├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── app
├── .gitignore
├── build.gradle.kts
├── proguard-rules.pro
├── release
│ ├── app-release.apk
│ └── output-metadata.json
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── king
│ │ └── mvvmframe
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── ic_launcher-web.png
│ ├── java
│ │ └── com
│ │ │ └── king
│ │ │ └── mvvmframe
│ │ │ ├── App.kt
│ │ │ ├── app
│ │ │ ├── MainActivity.kt
│ │ │ ├── adapter
│ │ │ │ ├── BindingAdapter.kt
│ │ │ │ ├── BindingHolder.kt
│ │ │ │ └── SearchHistoryAdapter.kt
│ │ │ ├── base
│ │ │ │ └── BaseViewModel.kt
│ │ │ ├── city
│ │ │ │ ├── CityActivity.kt
│ │ │ │ └── CityViewModel.kt
│ │ │ ├── dictionary
│ │ │ │ ├── DictionaryActivity.kt
│ │ │ │ └── DictionaryViewModel.kt
│ │ │ └── oil
│ │ │ │ ├── OilPriceActivity.kt
│ │ │ │ └── OilPriceViewModel.kt
│ │ │ ├── config
│ │ │ └── AppConfigModule.kt
│ │ │ ├── constant
│ │ │ └── Constants.kt
│ │ │ ├── data
│ │ │ ├── database
│ │ │ │ ├── AppDatabase.kt
│ │ │ │ └── dao
│ │ │ │ │ └── SearchHistoryDao.kt
│ │ │ ├── model
│ │ │ │ ├── City.kt
│ │ │ │ ├── DictionaryInfo.java
│ │ │ │ ├── OilPrice.kt
│ │ │ │ ├── Result.kt
│ │ │ │ └── SearchHistory.kt
│ │ │ ├── repository
│ │ │ │ ├── CityRepository.kt
│ │ │ │ ├── DictionaryRepository.kt
│ │ │ │ └── OilPriceRepository.kt
│ │ │ └── service
│ │ │ │ ├── CityService.kt
│ │ │ │ ├── DictionaryService.kt
│ │ │ │ └── OilPriceService.kt
│ │ │ └── util
│ │ │ └── RandomUtil.kt
│ └── res
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable-xxhdpi
│ │ ├── btn_delete_normal.png
│ │ └── btn_delete_pressed.png
│ │ ├── drawable
│ │ ├── btn_delete_selector.xml
│ │ ├── dialog_bg.xml
│ │ ├── ic_launcher_background.xml
│ │ ├── list_divider.xml
│ │ ├── list_divider_8.xml
│ │ ├── tag_bg_normal.xml
│ │ ├── tag_bg_pressed.xml
│ │ └── tag_bg_selector.xml
│ │ ├── layout
│ │ ├── city_activity.xml
│ │ ├── dictionary_activity.xml
│ │ ├── main_activity.xml
│ │ ├── oil_price_activity.xml
│ │ ├── rv_city_item.xml
│ │ ├── rv_dictionary_item.xml
│ │ ├── rv_oil_price_item.xml
│ │ └── search_history_item.xml
│ │ ├── menu
│ │ └── search_view_menu.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_round.png
│ │ ├── values
│ │ ├── colors.xml
│ │ ├── ic_launcher_background.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ │ └── xml
│ │ └── network_security_config.xml
│ └── test
│ └── java
│ └── com
│ └── king
│ └── mvvmframe
│ └── ExampleUnitTest.kt
├── art
└── mvvm_architecture.jpg
├── build.gradle.kts
├── build_docs.sh
├── gradle.properties
├── gradle
├── libs.versions.toml
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── jitpack.yml
├── mkdocs.yml
├── mvvmframe-plugin
├── .gitignore
├── build.gradle.kts
├── gradle.properties
└── src
│ └── main
│ └── java
│ └── com
│ └── king
│ └── frame
│ └── mvvmframe
│ └── plugin
│ ├── MvvmFrameExtension.kt
│ ├── MvvmFramePlugin.kt
│ └── internal
│ ├── DependencyExt.kt
│ ├── PluginId.kt
│ └── Version.kt
├── mvvmframe
├── .gitignore
├── build.gradle.kts
├── gradle.properties
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── king
│ │ └── frame
│ │ └── mvvm
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── king
│ │ │ └── frame
│ │ │ └── mvvmframe
│ │ │ ├── base
│ │ │ ├── BaseActivity.kt
│ │ │ ├── BaseAndroidViewModel.kt
│ │ │ ├── BaseApplication.kt
│ │ │ ├── BaseDialogFragment.kt
│ │ │ ├── BaseFragment.kt
│ │ │ ├── BaseProgressDialog.kt
│ │ │ ├── BaseViewModel.kt
│ │ │ ├── ContentActivity.kt
│ │ │ ├── Ext.kt
│ │ │ ├── IView.kt
│ │ │ └── controller
│ │ │ │ ├── CombinedController.kt
│ │ │ │ ├── Controller.kt
│ │ │ │ ├── DefaultCombinedController.kt
│ │ │ │ ├── DefaultDialogController.kt
│ │ │ │ ├── DefaultLoadingController.kt
│ │ │ │ ├── DefaultToastController.kt
│ │ │ │ ├── DialogController.kt
│ │ │ │ ├── LoadingController.kt
│ │ │ │ └── ToastController.kt
│ │ │ ├── config
│ │ │ ├── AppliesOptions.kt
│ │ │ ├── Config.kt
│ │ │ ├── FrameConfigModule.kt
│ │ │ └── ManifestParser.kt
│ │ │ ├── data
│ │ │ ├── DataRepository.kt
│ │ │ ├── Repository.kt
│ │ │ └── datasource
│ │ │ │ ├── DataSource.kt
│ │ │ │ └── DefaultDataSource.kt
│ │ │ ├── di
│ │ │ └── module
│ │ │ │ ├── ConfigModule.kt
│ │ │ │ ├── DataSourceModule.kt
│ │ │ │ ├── HttpModule.kt
│ │ │ │ └── RepositoryModule.kt
│ │ │ └── util
│ │ │ └── JumpDebounce.kt
│ └── res
│ │ ├── anim
│ │ ├── mvvmframe_dialog_in.xml
│ │ └── mvvmframe_dialog_out.xml
│ │ ├── drawable
│ │ ├── mvvmframe_loading.xml
│ │ └── mvvmframe_progress.xml
│ │ ├── layout
│ │ ├── mvvmframe_content_activity.xml
│ │ ├── mvvmframe_loading.xml
│ │ └── mvvmframe_progress_dialog.xml
│ │ └── values
│ │ ├── colors.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── king
│ └── frame
│ └── mvvm
│ └── ExampleUnitTest.kt
└── settings.gradle.kts
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2.1
2 |
3 | orbs:
4 | android: circleci/android@2.4.0
5 |
6 | jobs:
7 | build:
8 | docker:
9 | - image: cimg/android:2023.08
10 | steps:
11 | - checkout
12 | - run:
13 | command: ./gradlew build
14 |
15 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 4
6 | charset = utf-8
7 | trim_trailing_whitespace = true
8 | insert_final_newline = true
9 |
10 | [*.{yml,yaml}]
11 | indent_size = 2
12 |
13 | [*.{kt, kts}]
14 | ij_kotlin_imports_layout = *
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: build
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | pull_request:
7 | branches: [ master ]
8 |
9 | env:
10 | JAVA_VERSION: 17
11 |
12 | jobs:
13 | build:
14 | runs-on: ubuntu-latest
15 |
16 | steps:
17 | - name: Checkout
18 | uses: actions/checkout@v4
19 | with:
20 | fetch-depth: 0
21 |
22 | - name: Configure JDK
23 | uses: actions/setup-java@v4
24 | with:
25 | distribution: 'zulu'
26 | java-version: ${{ env.JAVA_VERSION }}
27 | - name: Build with Gradle
28 | run: ./gradlew build
29 |
--------------------------------------------------------------------------------
/.github/workflows/docs.yml:
--------------------------------------------------------------------------------
1 | name: docs
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 |
8 | env:
9 | JAVA_VERSION: 17
10 | PYTHON_VERSION: 3.x
11 | GRADLE_OPTS: "-Dorg.gradle.jvmargs=-Xmx4g -Dorg.gradle.daemon=false -Dkotlin.incremental=false"
12 |
13 | permissions:
14 | contents: write
15 | id-token: write
16 | pages: write
17 |
18 | jobs:
19 | docs:
20 | environment:
21 | name: github-pages
22 | url: ${{ steps.deployment.outputs.page_url }}
23 | runs-on: ubuntu-latest
24 | if: github.ref == 'refs/heads/master'
25 |
26 | steps:
27 | - name: Checkout
28 | uses: actions/checkout@v4
29 | with:
30 | fetch-depth: 0
31 |
32 | - name: Configure JDK
33 | uses: actions/setup-java@v4
34 | with:
35 | distribution: 'zulu'
36 | java-version: ${{ env.JAVA_VERSION }}
37 |
38 | - name: Install Python
39 | uses: actions/setup-python@v5
40 | with:
41 | python-version: ${{ env.PYTHON_VERSION }}
42 |
43 | - name: Install MkDocs Material
44 | run: pip install mkdocs-material
45 |
46 | - name: Generate Docs
47 | run: ./build_docs.sh
48 |
49 | - name: Upload to GitHub Pages
50 | uses: actions/upload-pages-artifact@v3
51 | with:
52 | path: site
53 |
54 | - name: Deploy to GitHub Pages
55 | id: deployment
56 | uses: actions/deploy-pages@v4
57 |
--------------------------------------------------------------------------------
/.github/workflows/issues-stale.yml:
--------------------------------------------------------------------------------
1 | name: 'Close stale issues and PRs'
2 | on:
3 | schedule:
4 | - cron: '0 16 * * *'
5 |
6 | jobs:
7 | stale:
8 | runs-on: ubuntu-latest
9 | permissions:
10 | issues: write
11 | pull-requests: write
12 | steps:
13 | - uses: actions/stale@v5
14 | with:
15 | stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.'
16 | days-before-stale: 30
17 | days-before-close: 5
18 | exempt-all-pr-milestones: true
19 | exempt-issue-labels: 'help wanted'
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea
5 | .DS_Store
6 | /build
7 | /captures
8 | .externalNativeBuild
9 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: android
2 | dist: trusty
3 | jdk: oraclejdk8
4 | sudo: false
5 |
6 | env:
7 | global:
8 | - ANDROID_API_LEVEL=30
9 | - ANDROID_BUILD_TOOLS_VERSION=30.0.3
10 | - TRAVIS_SECURE_ENV_VARS=true
11 |
12 | before_install:
13 | - chmod +x gradlew
14 | - mkdir "$ANDROID_HOME/licenses" || true
15 | # Hack to accept Android licenses
16 | - yes | sdkmanager "platforms;android-$ANDROID_API_LEVEL"
17 |
18 |
19 | android:
20 | components:
21 | # The BuildTools version used by your project
22 | - tools
23 | - platform-tools
24 | - build-tools-$ANDROID_BUILD_TOOLS_VERSION
25 | # The SDK version used to compile your project
26 | - android-$ANDROID_API_LEVEL
27 | - extra-android-m2repository
28 | - extra-google-android-support
29 |
30 | script:
31 | - ./gradlew clean
32 | - ./gradlew assembleRelease
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 版本日志
2 |
3 | #### v3.1.0:2025-3-23
4 | * 优化插件配置
5 | * 优化一些细节
6 | * 更新gradle至v8.9
7 | * 更新kotlin至v1.9.24
8 | * 更新appcompat至v1.7.0
9 | * 更新core-ktx至v1.13.1
10 | * 更新fragment-ktx至v1.8.6
11 | * 更新lifecycle-ktx至v2.8.7
12 | * 更新hilt至v2.52
13 | * 更新retrofit至v2.11.0
14 | * 更新gson至v2.12.1
15 |
16 | #### v3.0.0:2024-3-3
17 | * 统一改为使用`kotlin`进行了重构
18 | * 移除所有`LiveData`相关代码改用`Flow`
19 | * 支持插件配置
20 | * 更新编译SDK至34
21 | * 更新Gradle至v8.0
22 | * 新增core-ktx依赖(v1.12.0)
23 | * 新增fragment-ktx依赖(v1.6.2)
24 | * 新增lifecycle-ktx相关依赖(v2.7.0)
25 | * 更新Okhttp至v4.12.0
26 | * 更新Hilt至v2.51
27 | * 更新Gson至v2.10.1
28 | * 更新Room至v2.6.1
29 | * 更新retrofit-helper至v1.1.0
30 |
31 | #### v2.2.1:2022-4-21
32 | * 更新Okhttp至v4.9.3
33 | * 更新Hilt至v2.41
34 | * 更新Gson至v2.9.0
35 |
36 | #### v2.2.0:2021-11-18
37 | * minSdk要求从 16+ 改为 21+
38 | * 更新编译SDK至30
39 | * 更新Gradle至v6.7.1
40 | * 更新Okhttp至v4.9.2
41 | * 更新Hilt至v2.40.1
42 | * 更新Gson至v2.8.9
43 | * 更新Timber至v5.0.1
44 |
45 | #### v2.1.1:2021-6-29
46 | * 更新Hilt至v2.37
47 | * 更新Gson至v2.8.7
48 | * 优化细节
49 |
50 | #### v2.1.0:2021-4-28 (从v2.1.0开始不再发布至JCenter)
51 | * 更新Hilt至v2.35
52 | * 移除androidx.hilt:hilt-lifecycle-viewmodel [移除原因请查看Dagger v2.34更新说明](https://github.com/google/dagger/releases)
53 | * 更新Lifecycle至v2.3.1
54 | * 更新Room至v2.3.0
55 | * 更新RetrofitHelper至v1.0.1
56 | * 发布至Maven Central
57 |
58 | #### v2.0.0:2021-1-15
59 | * 使用Hilt简化Dagger依赖注入用法
60 |
61 | #### v1.1.4:2020-12-14
62 | * 优化细节
63 | * 更新Dagger至v2.30.1
64 |
65 | #### v1.1.3:2020-6-1
66 | * 支持配置多个BaseUrl,且支持动态改变(详情查看 [RetrofitHelper](https://github.com/jenly1314/RetrofitHelper))
67 | * 对外暴露更多配置,(详情查看 FrameConfigModule)
68 | * 优化细节
69 | * 更新Retrofit至v2.9.0
70 |
71 | #### v1.1.2:2020-4-5
72 | * 优化细节
73 | * 更新Gradle至v5.6.4
74 | * 更新Lifecycle至v2.2.0
75 | * 更新Room至v2.2.5
76 | * 更新Dagger至v2.27
77 | * 更新Retrofit至v2.8.1
78 |
79 | #### v1.1.1:2019-11-4
80 | * 优化部分细节
81 | * 更新编译SDK至29
82 | * 更新Gradle至v5.4.1
83 | * 更新Lifecycle至v2.2.0-rc01
84 | * 更新Room至v2.2.1
85 | * 更新Dagger至v2.25.2
86 | * 更新Retrofit至v2.6.2
87 | * 更新Gson至v2.8.6
88 |
89 | #### v1.1.0:2019-7-22
90 | * 更新Dagger至v2.23.2
91 | * 更新Gradle至v5.1.1
92 | * 完全迁移至AndroidX版本
93 |
94 | #### v1.0.2:2019-7-22
95 | * 更新Dagger至v2.19
96 | * 为迁移至AndroidX做准备(下一版本将直接发布AndroidX版)
97 |
98 | #### v1.0.1:2019-7-9
99 | * Retrofit更新至v2.6.0
100 |
101 | #### v1.0.0:2018-12-12
102 | * MVVMFrame初始版本
103 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2017 Jenly Yu
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.android.application)
3 | alias(libs.plugins.kotlin.android)
4 | alias(libs.plugins.kapt)
5 | alias(libs.plugins.ksp)
6 | alias(libs.plugins.mvvmframe)
7 | }
8 |
9 | android {
10 | namespace = "com.king.mvvmframe"
11 | compileSdk = libs.versions.compileSdk.get().toInt()
12 |
13 | defaultConfig {
14 |
15 | applicationId = "com.king.mvvmframe"
16 | minSdk = libs.versions.minSdk.get().toInt()
17 | targetSdk = libs.versions.targetSdk.get().toInt()
18 | versionCode = properties["VERSION_CODE"].toString().toInt()
19 | versionName = properties["VERSION_NAME"].toString()
20 |
21 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
22 |
23 | vectorDrawables {
24 | useSupportLibrary = true
25 | }
26 | }
27 |
28 | buildTypes {
29 | release {
30 | isMinifyEnabled = false
31 | proguardFiles(
32 | getDefaultProguardFile("proguard-android.txt"),
33 | "proguard-rules.pro"
34 | )
35 | }
36 | }
37 |
38 | buildFeatures{
39 | dataBinding = true
40 | buildConfig = true
41 | }
42 |
43 | compileOptions {
44 | sourceCompatibility = JavaVersion.VERSION_17
45 | targetCompatibility = JavaVersion.VERSION_17
46 | }
47 | kotlinOptions {
48 | jvmTarget = JavaVersion.VERSION_17.toString()
49 | }
50 | }
51 |
52 | dependencies {
53 |
54 | // test
55 | testImplementation(libs.junit)
56 | androidTestImplementation(libs.androidx.junit)
57 | androidTestImplementation(libs.androidx.espresso)
58 |
59 | implementation(libs.material)
60 | implementation(libs.androidx.appcompat)
61 | implementation(libs.swiperefreshlayout)
62 | implementation(libs.recyclerview)
63 | implementation(libs.constraintlayout)
64 |
65 | implementation(libs.base.util)
66 | implementation(libs.base.adapter)
67 |
68 | implementation(libs.android.ktx)
69 |
70 | implementation(libs.flowlayout)
71 |
72 | implementation(libs.logx)
73 |
74 | //leakCanary
75 | debugImplementation(libs.leakcanary)
76 |
77 | ksp(libs.room.compiler)
78 |
79 | // MVVMFrame
80 | implementation(project(":mvvmframe"))
81 |
82 | }
83 |
84 | mvvmFrame {
85 | //..
86 | }
87 |
88 | kapt {
89 | correctErrorTypes = true
90 | }
91 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.kts.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/app/release/app-release.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenly1314/MVVMFrame/e3d343fa4adf1fc5b073f9e36c5e5942be8352b5/app/release/app-release.apk
--------------------------------------------------------------------------------
/app/release/output-metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 3,
3 | "artifactType": {
4 | "type": "APK",
5 | "kind": "Directory"
6 | },
7 | "applicationId": "com.king.mvvmframe",
8 | "variantName": "release",
9 | "elements": [
10 | {
11 | "type": "SINGLE",
12 | "filters": [],
13 | "attributes": [],
14 | "versionCode": 18,
15 | "versionName": "3.1.0",
16 | "outputFile": "app-release.apk"
17 | }
18 | ],
19 | "elementType": "File",
20 | "baselineProfiles": [
21 | {
22 | "minApi": 28,
23 | "maxApi": 30,
24 | "baselineProfiles": [
25 | "baselineProfiles/1/app-release.dm"
26 | ]
27 | },
28 | {
29 | "minApi": 31,
30 | "maxApi": 2147483647,
31 | "baselineProfiles": [
32 | "baselineProfiles/0/app-release.dm"
33 | ]
34 | }
35 | ],
36 | "minSdkVersionForDexing": 21
37 | }
38 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/king/mvvmframe/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.king.mvvmframe
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.king.mvvmframe", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
25 | * Follow me 26 | */ 27 | @HiltAndroidApp 28 | class App : BaseApplication() { 29 | 30 | 31 | override fun onCreate() { 32 | super.onCreate() 33 | initLog() 34 | //------------------------------ 35 | // 如果你没有使用FrameConfigModule中的第一中方式初始化BaseUrl,也可以通过第二种方式来设置BaseUrl(二选其一即可) 36 | // RetrofitHelper.getInstance().setBaseUrl(Constants.BASE_URL) 37 | // 设置动态BaseUrl 38 | RetrofitHelper.getInstance().apply { 39 | putDomain(Constants.DOMAIN_DICTIONARY, Constants.DICTIONARY_BASE_URL) 40 | putDomain(Constants.DOMAIN_JENLY, Constants.JENLY_BASE_URL) 41 | } 42 | } 43 | 44 | /** 45 | * 初始化 46 | */ 47 | private fun initLog() { 48 | // 初始化日志打印 49 | Timber.plant(object : Timber.DebugTree() { 50 | override fun log(priority: Int, tag: String?, message: String, t: Throwable?) { 51 | LogX.offset(4).log(priority, message) 52 | } 53 | }) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/java/com/king/mvvmframe/app/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.king.mvvmframe.app 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import androidx.databinding.ViewDataBinding 6 | import com.king.android.ktx.activity.startActivity 7 | import com.king.frame.mvvmframe.base.BaseActivity 8 | import com.king.frame.mvvmframe.base.BaseViewModel 9 | import com.king.mvvmframe.R 10 | import com.king.mvvmframe.app.city.CityActivity 11 | import com.king.mvvmframe.app.dictionary.DictionaryActivity 12 | import com.king.mvvmframe.app.oil.OilPriceActivity 13 | import dagger.hilt.android.AndroidEntryPoint 14 | 15 | /** 16 | * MVVMFrame 框架基于 Google 官方的 JetPack 构建,在使用 MVVMFrame 时,需遵循一些规范: 17 | * 18 | * Hilt大幅简化了 Dagger2 的用法,使得我们不用通过 @Component 注解去编写桥接层的逻辑,但是也因此限定了注入功能只能从几个 Android 固定的入口点开始, 19 | * Hilt一共支持6个入口点,分别是: 20 | * Application 21 | * Activity 22 | * Fragment 23 | * View 24 | * Service 25 | * BroadcastReceiver 26 | * 27 | * 其中,只有Application这个入口点是使用@HiltAndroidApp注解来声明,示例如下 28 | * 29 | * Example: Application 30 | * 31 | * ``` 32 | * @HiltAndroidApp 33 | * class YourApplication : Application() { 34 | * 35 | * } 36 | * ``` 37 | * 其他的入口点,都是用@AndroidEntryPoint注解来声明,示例如下 38 | * 39 | * Example: Activity 40 | * ``` 41 | * @AndroidEntryPoint 42 | * class YourActivity : BaseActivity() { 43 | * 44 | * } 45 | * ``` 46 | * Example: Fragment 47 | * ``` 48 | * @AndroidEntryPoint 49 | * class YourFragment : BaseFragment() { 50 | * 51 | * } 52 | * ``` 53 | * 主要示例请查看 [OilPriceActivity] 和 [OilPriceViewModel] 54 | * 55 | * @author Jenly 56 | *
57 | * Follow me
58 | */
59 | @AndroidEntryPoint
60 | class MainActivity : BaseActivity
13 | * Follow me
14 | */
15 | class BindingAdapter
11 | * Follow me
12 | */
13 | class BindingHolder
16 | * Follow me
17 | */
18 | class SearchHistoryAdapter(list: MutableList
23 | * Follow me
24 | */
25 | @HiltViewModel
26 | open class BaseViewModel @Inject constructor(application: Application) : BaseAndroidViewModel(application) {
27 |
28 | /**
29 | * 是否成功
30 | */
31 | fun isSuccess(result: Result<*>, showError: Boolean = true): Boolean {
32 | if (result.isSuccess()) {
33 | return true
34 | }
35 | if (showError) {
36 | sendMessage(result.message)
37 | }
38 | return false
39 | }
40 |
41 | /**
42 | * 启动一个协程
43 | */
44 | fun launch(
45 | showLoading: Boolean = true,
46 | context: CoroutineContext = EmptyCoroutineContext,
47 | error: suspend (Throwable) -> Unit = {
48 | Timber.w(it)
49 | if (SystemUtils.isNetWorkActive(getApplication())) {
50 | when (it) {
51 | is SocketTimeoutException -> sendMessage(R.string.result_connect_timeout_error)
52 | is ConnectException -> sendMessage(R.string.result_connect_failed_error)
53 | else -> sendMessage(R.string.result_error)
54 | }
55 | } else {
56 | sendMessage(R.string.result_network_unavailable_error)
57 | }
58 | },
59 | block: suspend () -> Unit,
60 | ) = viewModelScope.launch(context = context) {
61 | try {
62 | if (showLoading) {
63 | showLoading()
64 | }
65 | block()
66 | } catch (e: Throwable) {
67 | error(e)
68 | } finally {
69 | if (showLoading) {
70 | hideLoading()
71 | }
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/app/src/main/java/com/king/mvvmframe/app/city/CityActivity.kt:
--------------------------------------------------------------------------------
1 | package com.king.mvvmframe.app.city
2 |
3 | import android.os.Bundle
4 | import androidx.lifecycle.flowWithLifecycle
5 | import androidx.lifecycle.lifecycleScope
6 | import androidx.recyclerview.widget.LinearLayoutManager
7 | import com.king.base.adapter.divider.DividerItemDecoration
8 | import com.king.frame.mvvmframe.base.BaseActivity
9 | import com.king.mvvmframe.R
10 | import com.king.mvvmframe.app.adapter.BindingAdapter
11 | import com.king.mvvmframe.data.model.City
12 | import com.king.mvvmframe.databinding.CityActivityBinding
13 | import dagger.hilt.android.AndroidEntryPoint
14 | import kotlinx.coroutines.launch
15 |
16 | /**
17 | * 示例
18 | *
19 | * @author Jenly
20 | *
21 | * Follow me
22 | */
23 | @AndroidEntryPoint
24 | class CityActivity : BaseActivity
17 | * Follow me
18 | */
19 | @HiltViewModel
20 | class CityViewModel @Inject constructor(
21 | private val cityRepository: CityRepository,
22 | application: Application
23 | ) : BaseViewModel(application) {
24 |
25 | private val _cityFlow = MutableStateFlow
30 | * Follow me
31 | */
32 | @AndroidEntryPoint
33 | class DictionaryActivity : BaseActivity
18 | * Follow me
19 | */
20 | @HiltViewModel
21 | class DictionaryViewModel @Inject constructor(
22 | private val dictionaryRepository: DictionaryRepository,
23 | application: Application
24 | ) : BaseViewModel(application) {
25 |
26 | private val _dictionaryFlow = MutableStateFlow
21 | * Follow me
22 | */
23 | @AndroidEntryPoint
24 | class OilPriceActivity : BaseActivity
17 | * Follow me
18 | */
19 | @HiltViewModel
20 | class OilPriceViewModel @Inject constructor(
21 | private val oilPriceRepository: OilPriceRepository,
22 | application: Application
23 | ) : BaseViewModel(application) {
24 |
25 |
26 | private val _oilPriceFlow = MutableStateFlow
26 | * Follow me
27 | */
28 | class AppConfigModule : FrameConfigModule() {
29 | override fun applyOptions(context: Context, builder: ConfigModule.Builder) {
30 | /**
31 | * > 目前通过设置 BaseUrl 的入口主要有两种:
32 | * >> 1.一种是通过在 Manifest 中配置 meta-data 的来自定义 FrameConfigModule,在里面 通过 [ConfigModule.Builder.baseUrl]来配置 BaseUrl。(一次设置,全局配置)
33 | * >
34 | * >> 2.一种就是通过RetrofitHelper [RetrofitHelper.setBaseUrl] 或 [RetrofitHelper.setBaseUrl] 来配置 BaseUrl。(可多次设置,动态全局配置,有前提条件)
35 | * >
36 | * > 以上两种配置 BaseUrl 的方式都可以达到目的。但是你可以根据不同的场景选择不同的配置方式。
37 | * >
38 | * > 主要场景与选择如下:
39 | * >
40 | * >> 一般场景:对于只使用单个不变的 BaseUrl的
41 | * >>> 场景1:如果本库的默认已满足你的需求,无需额外自定义配置的。
42 | * > 选择:建议你直接使用 [RetrofitHelper.setBaseUrl] 或 [RetrofitHelper.setBaseUrl] 来初始化 BaseUrl,切记在框架配置初始化 BaseUrl之前,建议在你自定义的 [Application.onCreate]中初始化。
43 | * >
44 | * >>> 场景2:如果本库的默认配置不满足你的需求,你需要自定义一些配置的。(比如需要使用 RxJava相关)
45 | * > 选择:建议你在自定义配置中通过 [ConfigModule.Builder.baseUrl] 来初始化 BaseUrl。
46 | * >
47 | * >> 二般场景:对于只使用单个 BaseUrl 但是,BaseUrl中途会变动的。
48 | * >>> 场景3:和一般场景一样,也能分两种,所以选择也和一般场景也可以是一样的。
49 | * > 选择:两种选择都行,但当 BaseUrl需要中途变动时,还需将 [RetrofitHelper.setDynamicDomain] 设置为 `true` 才能支持动态改变 BaseUrl。
50 | * >
51 | * >> 特殊场景:对于支持多个 BaseUrl 且支持动态可变的。
52 | * >>> 选择:这个场景的选择,主要涉及到另外的方法,请查看 [RetrofitHelper.putDomain] 和 [RetrofitHelper.putDomain]相关详情
53 | * >
54 | */
55 | // 通过第一种方式初始化BaseUrl
56 | builder.baseUrl(Constants.BASE_URL) // TODO 配置Retrofit中的baseUrl
57 |
58 | builder.retrofitOptions(object : RetrofitOptions {
59 | override fun applyOptions(builder: Retrofit.Builder) {
60 | // TODO 配置Retrofit
61 |
62 | }
63 | })
64 | .okHttpClientOptions(object : OkHttpClientOptions {
65 | override fun applyOptions(builder: OkHttpClient.Builder) {
66 | // TODO 配置OkHttpClient
67 | }
68 | })
69 | .gsonOptions(object : GsonOptions {
70 | override fun applyOptions(builder: GsonBuilder) {
71 | // TODO 配置Gson
72 | }
73 | })
74 | .roomDatabaseOptions(object : RoomDatabaseOptions {
75 | override fun applyOptions(builder: RoomDatabase.Builder
6 | * Follow me
7 | */
8 | object Constants {
9 |
10 | const val BASE_URL = "http://apis.juhe.cn"
11 |
12 | const val DICTIONARY_BASE_URL = "http://v.juhe.cn"
13 |
14 | const val JENLY_BASE_URL = "https://jenly1314.gitlab.io"
15 |
16 | const val TAG = "Jenly"
17 |
18 | const val OIL_PRICE_KEY = "402e469d939079d5bb758fc5ca65967d"
19 | const val DICTIONARY_KEY = "e38e852fe9307822a50e66763afdc593"
20 |
21 | const val COLOR_RGB_MIN = 20
22 | const val COLOR_RGB_MAX = 230
23 |
24 | const val DOMAIN_DICTIONARY = "dictionary"
25 | const val DOMAIN_JENLY = "jenly1314"
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/java/com/king/mvvmframe/data/database/AppDatabase.kt:
--------------------------------------------------------------------------------
1 | package com.king.mvvmframe.data.database
2 |
3 | import androidx.room.Database
4 | import androidx.room.RoomDatabase
5 | import com.king.mvvmframe.data.model.SearchHistory
6 | import com.king.mvvmframe.data.database.dao.SearchHistoryDao
7 |
8 | /**
9 | * 数据库
10 | *
11 | * @author Jenly
12 | *
13 | * Follow me
14 | */
15 | @Database(entities = [SearchHistory::class], version = 1, exportSchema = false)
16 | abstract class AppDatabase : RoomDatabase() {
17 | /**
18 | * SearchHistoryDao
19 | */
20 | abstract val searchHistoryDao: SearchHistoryDao
21 | }
22 |
--------------------------------------------------------------------------------
/app/src/main/java/com/king/mvvmframe/data/database/dao/SearchHistoryDao.kt:
--------------------------------------------------------------------------------
1 | package com.king.mvvmframe.data.database.dao
2 |
3 | import androidx.room.Dao
4 | import androidx.room.Insert
5 | import androidx.room.OnConflictStrategy
6 | import androidx.room.Query
7 | import com.king.mvvmframe.data.model.SearchHistory
8 | import kotlinx.coroutines.flow.Flow
9 |
10 | /**
11 | * 搜索历史
12 | *
13 | * @author Jenly
14 | *
15 | * Follow me
16 | */
17 | @Dao
18 | interface SearchHistoryDao {
19 | /**
20 | * 插入一条历史数据
21 | * @param searchHistory
22 | */
23 | @Insert(onConflict = OnConflictStrategy.REPLACE)
24 | suspend fun insert(searchHistory: SearchHistory)
25 |
26 | /**
27 | * 删除所有历史数据
28 | */
29 | @Query("DELETE FROM SearchHistory")
30 | suspend fun deleteAll()
31 |
32 | /**
33 | * 获取历史数据对应的[Flow]
34 | *
35 | * @param size 获取历史记录的条数
36 | */
37 | @Query("SELECT * FROM SearchHistory ORDER BY timestamp DESC LIMIT :size")
38 | fun getHistoryFlow(size: Int): Flow
8 | * Follow me
9 | */
10 | data class City(
11 | val id: String,
12 | val name: String,
13 | )
14 |
--------------------------------------------------------------------------------
/app/src/main/java/com/king/mvvmframe/data/model/DictionaryInfo.java:
--------------------------------------------------------------------------------
1 | package com.king.mvvmframe.data.model;
2 |
3 | import java.util.List;
4 |
5 | /**
6 | * @author Jenly
7 | */
8 | public class DictionaryInfo {
9 |
10 | /**
11 | * zi : 聚
12 | * py : ju
13 | * wubi : bcti
14 | * pinyin : jù
15 | * bushou : 耳
16 | * bihua : 14
17 | * jijie : ["聚","jù","会合,集合:聚合。聚会。聚积。聚集。聚谈。聚拢。聚齐。聚餐。欢聚一堂。物以类聚。凝聚。聚沙成塔。","散","","笔画数:14;","部首:耳;","笔顺编号:12211154323334"]
18 | * xiangjie : ["聚","jù","【名】","(形声。小篆,下面是三个人,表示人多;上面的\u201c取\u201d,作声符。本义:村落)","同本义〖village〗","聚,会也。\u2014\u2014《说文》。按,邑落曰聚,今曰邨,曰镇,北方曰集皆是。","一年而所居成聚。\u2014\u2014《史记·五帝纪》","聚曰序。\u2014\u2014《史记·平帝纪》","所止聚落化其德。\u2014\u2014《后汉书·王扶传》。注:\u201c小于乡曰聚。\u201d","乡聚里中人哀而救之。\u2014\u2014刘向《说苑》","禹无十户之聚,以王诸侯。\u2014\u2014枚乘《上书谏吴王》","又如:聚落(村落里邑,人群聚居的地方)","众;集团;一伙〖group〗","我是以有辅氏之聚。\u2014\u2014《左传·成公十三年》","陈人恃其聚。\u2014\u2014《左传·哀公十七年》","又如:聚观(群聚观看);聚口(犹齐声)","地名〖Jucity〗。在今山西省绛县东南","","聚","jù","【动】","会合;聚集〖assemble;flocktogether;gather;gettogether〗","而发于众心之所聚。\u2014\u2014《管子·君臣上》","聚室而谋日。\u2014\u2014《列子·汤问》","以王命聚之。\u2014\u2014唐·柳宗元《捕蛇者说》","鸣锣聚众。\u2014\u2014《广东军务记》","聚至百有余乡。","又如:聚散浮生(指人生聚散无定。浮生:人活着的时候是虚浮无定的);聚麀之诮(比喻和不三不四的女人鬼混在一起,遭人耻笑。麀:母鹿,泛指母兽);聚寇(聚集起来的盗寇)","积蓄,累积〖accumulate〗","聚菽粟。\u2014\u2014《墨子·尚贤中》","我今将畜积并聚之于仓廪。\u2014\u2014《荀子·王制》","又如:聚沙成塔;聚米(堆积米粒做成模型以说明军事形势,运筹决策;米堆。形容矮小);聚货(聚集货物)","征集〖collect〗","太医以王命聚之。\u2014\u2014柳宗元《捕蛇者说》","使民众聚居〖assemble;dwellingroups〗","大叔完聚。(完;修好城墙。)\u2014\u2014《左传》","","聚宝盆","jùbǎopén","〖treasurebowl〗民间传说中装有金银珠宝而且取之不尽的盆儿,比喻资源丰富的地方","聚变","jùbiàn","〖fusion〗某些轻元素结合时形成较重核并导致大量能量释放的原子核的结合","聚餐","jùcān","〖luncheon〗通常在公共食堂为集体而供应的一种比较正规的便餐(如遇到俱乐部的聚会或业务上的会议)","聚餐","jùcān","〖dinetogether〗聚在一起在公共食堂吃比较正规的便餐","聚赌","jùdǔ","〖operate〗多人聚集在一起赌博","聚光","jùguāng","〖condensation〗∶光的聚焦式准直","〖spotlight〗∶对一个物体的小面积进行照明的强光束","聚光灯","jùguāngdēng","〖spotlight〗类似小探照灯的一种设备,有一个可调反光镜,通常装有白炽灯或弧光灯,用于在室内将一窄束强光射在一个选定的小区域内(如舞台或待照相的对象等)","聚合","jùhé","〖gettogether〗∶分散的聚集到一起","〖polymerize〗∶化学结合〖小分子〗成为大分子,尤其非常大的分子;使受到聚合作用","聚会","jùhuì","〖gettogether〗聚集会合","几个老同学聚会","聚伙","jùhuǒ","〖gatheracrowd〗多人合成一伙","聚伙抢劫","聚积","jùjī","〖buildup〗∶聚蓄;积蓄","聚积革命力量","〖accumulate〗∶累积,经年累月,一点一滴地累积","他由于工作努力和投资适当,聚积了大量财富","聚集","jùjí","〖gather;assemble;collect〗集合","机场上聚集着数千人","聚歼","jùjiān","〖roundupandannihilate〗把敌人包围起来全部消灭","聚焦","jùjiāo","〖focus〗使光线或电子束等集中于一点","聚居","jùjū","〖inhabitaregion〗集中居住","少数民族聚居的地方","聚敛","jùliǎn","〖amasswealthbyheavytaxation〗课重税来搜刮(民财)","季氏富于周公,而求也为之聚敛而附益之。\u2014\u2014《论语·先进》","聚拢","jùlǒng","〖gathertogether〗会聚合拢起来","聚齐","jùqí","〖calltogether〗〖在约定地点〗全体集合","参观的人八时在展览馆对面聚齐","聚散","jùsàn","〖meetingandparting〗会聚与分散","人生聚散无常","聚谈","jùtán","〖gettogetherandtalkover〗聚在一起交谈","好久不得如此聚谈快意了!","聚头","jùtóu","〖gettogether;meet〗聚首;会面;人碰在一起","不是冤家不聚头","聚晤","jùwù","〖meet〗会晤","聚晤一堂,谈笑风生","聚议","jùyì","〖gettogetheranddiscuss〗聚集在一起商议","聚议大事","聚义","jùyì","〖gettogetheranduprise〗旧指为正义事业而聚集在一起","聚义英雄","聚饮","jùyǐn","〖gettogetheranddrink〗聚集在一起喝酒取乐","聚众","jùzhòng","〖mob;gatheracrowd〗聚集群众;把许多人聚集在一起","聚众闹事"]
19 | */
20 |
21 | private String zi;
22 | private String py;
23 | private String wubi;
24 | private String pinyin;
25 | private String bushou;
26 | private String bihua;
27 | private List
10 | * Follow me
11 | */
12 | data class OilPrice(
13 | var city: String,
14 | @SerializedName("92h")
15 | val oil92: String,
16 | @SerializedName("95h")
17 | val oil95: String,
18 | @SerializedName("98h")
19 | val oil98: String,
20 | @SerializedName("0h")
21 | val oil0: String,
22 | ) {
23 | fun getOil92Str() = "¥ $oil92"
24 |
25 | fun getOil95Str() = "¥ $oil95"
26 |
27 | fun getOil98Str() = "¥ $oil98"
28 |
29 | fun getOil0Str() = "¥ $oil0"
30 | }
31 |
--------------------------------------------------------------------------------
/app/src/main/java/com/king/mvvmframe/data/model/Result.kt:
--------------------------------------------------------------------------------
1 | package com.king.mvvmframe.data.model
2 |
3 | import com.google.gson.annotations.SerializedName
4 |
5 | /**
6 | * 结果
7 | *
8 | * @author Jenly
9 | *
10 | * Follow me
11 | */
12 | data class Result
12 | * Follow me
13 | */
14 | @Entity(indices = [Index(value = ["word"], unique = true)])
15 | data class SearchHistory(
16 | @PrimaryKey(autoGenerate = true)
17 | val id: Long = 0,
18 | val word: String,
19 | val timestamp: Long,
20 | )
21 |
--------------------------------------------------------------------------------
/app/src/main/java/com/king/mvvmframe/data/repository/CityRepository.kt:
--------------------------------------------------------------------------------
1 | package com.king.mvvmframe.data.repository
2 |
3 | import com.king.frame.mvvmframe.data.datasource.DataSource
4 | import com.king.mvvmframe.data.model.City
5 | import com.king.mvvmframe.data.service.CityService
6 | import javax.inject.Inject
7 | import javax.inject.Singleton
8 |
9 | /**
10 | * 城市数据仓库
11 | *
12 | * @author Jenly
13 | *
14 | * Follow me
15 | */
16 | @Singleton
17 | class CityRepository @Inject constructor(private val dataSource: DataSource) {
18 |
19 | private val cityService: CityService by lazy {
20 | dataSource.getRetrofitService(CityService::class.java)
21 | }
22 |
23 | /**
24 | * 获取热门城市列表
25 | */
26 | suspend fun getHotCities(): List
20 | * Follow me
21 | */
22 | @Singleton
23 | class DictionaryRepository @Inject constructor(private val dataSource: DataSource) {
24 |
25 | private val dictionaryService: DictionaryService by lazy {
26 | dataSource.getRetrofitService(DictionaryService::class.java)
27 | }
28 |
29 | private val searchHistoryDao by lazy {
30 | dataSource.getRoomDatabase(AppDatabase::class.java).searchHistoryDao
31 | }
32 |
33 | /**
34 | * 获取热门城市列表
35 | */
36 | suspend fun getDictionaryInfo(
37 | word: String,
38 | key: String = Constants.DICTIONARY_KEY
39 | ): Result
16 | * Follow me
17 | */
18 | @Singleton
19 | class OilPriceRepository @Inject constructor(private val dataSource: DataSource) {
20 |
21 | private val oilPriceService: OilPriceService by lazy {
22 | dataSource.getRetrofitService(OilPriceService::class.java)
23 | }
24 |
25 | /**
26 | * 获取查询国内油价
27 | */
28 | suspend fun getOilPriceInfo(key: String = Constants.OIL_PRICE_KEY): Result
14 | * Follow me
15 | */
16 | interface CityService {
17 |
18 | /**
19 | * 动态改变 BaseUrl 示例
20 | * @return
21 | */
22 | @DomainName(Constants.DOMAIN_JENLY)
23 | @Timeout(connectTimeout = 10, readTimeout = 15, writeTimeout = 10)
24 | @GET("api/city/hotCities.json")
25 | suspend fun getHotCities(): List
15 | * Follow me
16 | */
17 | interface DictionaryService {
18 |
19 | /**
20 | * 获取字典信息- 新华字典
21 | * @param key
22 | * @return
23 | */
24 | @DomainName(Constants.DOMAIN_DICTIONARY)
25 | @GET("xhzd/query")
26 | suspend fun getDictionaryInfo(
27 | @Query("word") word: String,
28 | @Query("key") key: String,
29 | ): Result
13 | * Follow me
14 | */
15 | interface OilPriceService {
16 |
17 | /**
18 | * 查询国内油价
19 | */
20 | @GET("gnyj/query")
21 | suspend fun getOilPriceInfo(@Query("key") key: String): Result
11 | * Follow me
12 | */
13 | object RandomUtil {
14 |
15 | private val random by lazy {
16 | Random()
17 | }
18 |
19 | @ColorInt
20 | fun randomColor(
21 | @IntRange(from = 0, to = 255) min: Int,
22 | @IntRange(from = 0, to = 255) max: Int
23 | ): Int {
24 | return Color.rgb(random(min, max), random(min, max), random(min, max))
25 | }
26 |
27 | fun random(min: Int, max: Int): Int {
28 | return random.nextInt(max - min + 1) + min
29 | }
30 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
10 | * Follow me
11 | */
12 | open class MvvmFrameExtension {
13 | /**
14 | * room版本
15 | */
16 | var roomVersion = Version.ROOM_VERSION
17 |
18 | /**
19 | * 是否启用依赖:androidx.room:room-runtime
20 | */
21 | var enabledRoomRuntime = true
22 |
23 | /**
24 | * 是否启用依赖:androidx.room:room-compiler
25 | */
26 | var enabledRoomCompiler = true
27 | }
28 |
--------------------------------------------------------------------------------
/mvvmframe-plugin/src/main/java/com/king/frame/mvvmframe/plugin/MvvmFramePlugin.kt:
--------------------------------------------------------------------------------
1 | package com.king.frame.mvvmframe.plugin
2 |
3 | import com.king.frame.mvvmframe.plugin.internal.PluginId
4 | import com.king.frame.mvvmframe.plugin.internal.Version
5 | import com.king.frame.mvvmframe.plugin.internal.hasDependency
6 | import com.king.frame.mvvmframe.plugin.internal.implementation
7 | import com.king.frame.mvvmframe.plugin.internal.kapt
8 | import org.gradle.api.Plugin
9 | import org.gradle.api.Project
10 |
11 | /**
12 | * MVVMFrame插件
13 | *
14 | * @author Jenly
15 | *
16 | * Follow me
17 | */
18 | class MvvmFramePlugin : Plugin
11 | * Follow me
12 | */
13 | internal fun Project.hasDependency(group: String, name: String): Boolean {
14 | return configurations.any { configuration ->
15 | configuration.dependencies.any { dependency ->
16 | dependency.group == group && dependency.name == name
17 | }
18 | }
19 | }
20 |
21 | /**
22 | * implementation
23 | */
24 | internal fun DependencyHandler.implementation(
25 | group: String,
26 | artifact: String,
27 | version: String
28 | ) {
29 | add(IMPLEMENTATION, group, artifact, version)
30 | }
31 |
32 | /**
33 | * kapt
34 | */
35 | internal fun DependencyHandler.kapt(
36 | group: String,
37 | artifact: String,
38 | version: String
39 | ) {
40 | add(KAPT, group, artifact, version)
41 | }
42 |
43 | /**
44 | * 添加依赖
45 | */
46 | internal fun DependencyHandler.add(
47 | configurationName: String,
48 | group: String,
49 | artifact: String,
50 | version: String
51 | ) {
52 | add(configurationName, "$group:$artifact:$version")
53 | }
54 |
55 | private const val IMPLEMENTATION = "implementation"
56 | private const val KAPT = "kapt"
57 |
--------------------------------------------------------------------------------
/mvvmframe-plugin/src/main/java/com/king/frame/mvvmframe/plugin/internal/PluginId.kt:
--------------------------------------------------------------------------------
1 | package com.king.frame.mvvmframe.plugin.internal
2 |
3 | /**
4 | * 插件
5 | *
6 | * @author Jenly
7 | *
8 | * Follow me
9 | */
10 | internal object PluginId {
11 | const val KOTLIN_ANDROID = "org.jetbrains.kotlin.android"
12 | const val KOTLIN_KAPT = "org.jetbrains.kotlin.kapt"
13 | const val HILT_ANDROID = "com.google.dagger.hilt.android"
14 | }
15 |
--------------------------------------------------------------------------------
/mvvmframe-plugin/src/main/java/com/king/frame/mvvmframe/plugin/internal/Version.kt:
--------------------------------------------------------------------------------
1 | package com.king.frame.mvvmframe.plugin.internal
2 |
3 | /**
4 | * 版本
5 | *
6 | * @author Jenly
7 | *
8 | * Follow me
9 | */
10 | internal object Version {
11 | const val HILT_VERSION = "2.52"
12 | const val ROOM_VERSION = "2.6.1"
13 | }
14 |
--------------------------------------------------------------------------------
/mvvmframe/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/mvvmframe/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.android.library)
3 | alias(libs.plugins.kotlin.android)
4 | alias(libs.plugins.kapt)
5 | alias(libs.plugins.ksp)
6 | alias(libs.plugins.dagger.hilt.android)
7 | alias(libs.plugins.dokka)
8 | alias(libs.plugins.maven.publish)
9 | }
10 |
11 | android {
12 | namespace = "com.king.frame.mvvmframe"
13 | compileSdk = libs.versions.compileSdk.get().toInt()
14 |
15 | defaultConfig {
16 | minSdk = libs.versions.minSdk.get().toInt()
17 |
18 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
19 | consumerProguardFiles("consumer-rules.pro")
20 | }
21 |
22 | buildTypes {
23 | release {
24 | isMinifyEnabled = false
25 | proguardFiles(
26 | getDefaultProguardFile("proguard-android.txt"),
27 | "proguard-rules.pro"
28 | )
29 | }
30 | }
31 |
32 | buildFeatures{
33 | dataBinding = true
34 | }
35 |
36 | compileOptions {
37 | sourceCompatibility = JavaVersion.VERSION_17
38 | targetCompatibility = JavaVersion.VERSION_17
39 | }
40 | kotlinOptions {
41 | jvmTarget = JavaVersion.VERSION_17.toString()
42 | }
43 |
44 | }
45 |
46 | dependencies {
47 |
48 | // test
49 | testImplementation(libs.junit)
50 | androidTestImplementation(libs.androidx.junit)
51 | androidTestImplementation(libs.androidx.espresso)
52 |
53 | // appcompat
54 | compileOnly(libs.androidx.appcompat)
55 |
56 | api(libs.androidx.core.ktx)
57 | api(libs.androidx.fragment.ktx)
58 |
59 | // okhttp
60 | api(libs.okhttp)
61 | api(libs.okhttp.logging.interceptor)
62 |
63 | // retrofit
64 | api(libs.retrofit)
65 | api(libs.retrofit.converter.gson)
66 | api(libs.gson)
67 |
68 | // retrofit-helper
69 | api(libs.retrofit.helper)
70 |
71 | // lifecycle
72 | api(libs.lifecycle.ktx)
73 | api(libs.lifecycle.viewmodel.ktx)
74 |
75 | // room
76 | api(libs.room.runtime)
77 | api(libs.room.ktx)
78 | ksp(libs.room.compiler)
79 |
80 | // hilt
81 | api(libs.dagger.hilt.android)
82 | kapt(libs.dagger.hilt.compiler)
83 |
84 | // log
85 | api(libs.timber)
86 | // toast
87 | implementation(libs.toaster)
88 | }
89 |
90 | kapt {
91 | correctErrorTypes = true
92 | }
93 |
--------------------------------------------------------------------------------
/mvvmframe/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_NAME=MVVMFrame
2 | POM_ARTIFACT_ID=mvvmframe
3 | POM_PACKAGING=aar
--------------------------------------------------------------------------------
/mvvmframe/src/androidTest/java/com/king/frame/mvvm/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.king.frame.mvvm
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.king.frame.mvvm.test", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/mvvmframe/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
37 | * Follow me
38 | */
39 | abstract class BaseActivity
24 | * Follow me
25 | */
26 | @HiltViewModel
27 | open class BaseAndroidViewModel @Inject constructor(private val application: Application) :
28 | BaseViewModel() {
29 |
30 | /**
31 | * 获取[Application]
32 | */
33 | @Suppress("UNCHECKED_CAST")
34 | fun
24 | * Follow me
25 | */
26 | open class BaseApplication : Application() {
27 |
28 | override fun onCreate() {
29 | super.onCreate()
30 | initAppConfig(this)
31 | }
32 |
33 | companion object {
34 | /**
35 | * 初始化App配置
36 | */
37 | @JvmStatic
38 | fun initAppConfig(application: Application) {
39 | // 初始化Toaster
40 | Toaster.init(application)
41 | val offsetY = application.resources.displayMetrics.heightPixels / 6
42 | Toaster.setGravity(Gravity.BOTTOM, 0, offsetY)
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/mvvmframe/src/main/java/com/king/frame/mvvmframe/base/BaseDialogFragment.kt:
--------------------------------------------------------------------------------
1 | package com.king.frame.mvvmframe.base
2 |
3 | import android.app.Dialog
4 | import android.content.Intent
5 | import android.os.Bundle
6 | import android.view.LayoutInflater
7 | import android.view.View
8 | import android.view.ViewGroup
9 | import android.view.Window
10 | import androidx.annotation.IdRes
11 | import androidx.databinding.DataBindingUtil
12 | import androidx.databinding.ViewDataBinding
13 | import androidx.fragment.app.DialogFragment
14 | import androidx.lifecycle.ViewModel
15 | import androidx.lifecycle.ViewModelProvider
16 | import androidx.lifecycle.flowWithLifecycle
17 | import androidx.lifecycle.lifecycleScope
18 | import com.king.frame.mvvmframe.base.BaseViewModel.Companion.getVMClass
19 | import com.king.frame.mvvmframe.base.controller.CombinedController
20 | import com.king.frame.mvvmframe.base.controller.DefaultCombinedController
21 | import com.king.frame.mvvmframe.util.JumpDebounce
22 | import kotlinx.coroutines.launch
23 |
24 | /**
25 | * MVVMFrame 框架基于 Google 官方的 JetPack 构建,在使用 MVVMFrame 时,需遵循一些规范:
26 | *
27 | * 如果您继承使用了 BaseDialogFragment 或其子类,你需要参照如下方式添加 @AndroidEntryPoint 注解标记,来进行注入
28 | *
29 | * ```
30 | * // 示例
31 | * @AndroidEntryPoint
32 | * class YourFragment: BaseDialogFragment() {
33 | *
34 | * }
35 | * ```
36 | *
37 | * @author Jenly
38 | *
39 | * Follow me
40 | */
41 | @Suppress("unused")
42 | abstract class BaseDialogFragment
39 | * Follow me
40 | */
41 | @Suppress("unused")
42 | abstract class BaseFragment
13 | * Follow me
14 | */
15 | open class BaseProgressDialog : Dialog {
16 | constructor(context: Context) : this(context, R.style.mvvmframe_progress_dialog)
17 | constructor(context: Context, themeResId: Int) : super(context, themeResId) {
18 | window?.attributes?.gravity = Gravity.CENTER
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/mvvmframe/src/main/java/com/king/frame/mvvmframe/base/BaseViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.king.frame.mvvmframe.base
2 |
3 | import androidx.lifecycle.ViewModel
4 | import androidx.lifecycle.viewModelScope
5 | import com.king.frame.mvvmframe.base.controller.ToastController
6 | import dagger.hilt.android.lifecycle.HiltViewModel
7 | import java.lang.reflect.ParameterizedType
8 | import kotlinx.coroutines.flow.Flow
9 | import kotlinx.coroutines.flow.MutableSharedFlow
10 | import kotlinx.coroutines.flow.MutableStateFlow
11 | import kotlinx.coroutines.flow.asSharedFlow
12 | import kotlinx.coroutines.flow.asStateFlow
13 | import kotlinx.coroutines.launch
14 | import javax.inject.Inject
15 |
16 | /**
17 | * MVVMFrame 框架基于 Google 官方的 JetPack 构建,在使用 MVVMFrame 时,需遵循一些规范:
18 | *
19 | * 如果您继承使用了 BaseViewModel 或其子类,你需要参照如下方式添加 @HiltViewModel 和 @Inject 注解标记,来进行注入
20 | *
21 | * ```
22 | * // 示例
23 | * @HiltViewModel
24 | * class YourViewModel @Inject constructor(): BaseViewModel() {
25 | *
26 | * }
27 | * ```
28 | *
29 | * @author Jenly
30 | *
31 | * Follow me
32 | */
33 | @HiltViewModel
34 | open class BaseViewModel @Inject constructor() : ViewModel() {
35 |
36 | /**
37 | * 加载状态
38 | */
39 | private val _loadingState: MutableStateFlow
29 | * Follow me
30 | */
31 | @Suppress("unused")
32 | abstract class ContentActivity : BaseActivity
11 | * Follow me
12 | */
13 |
14 | /**
15 | * 通过id获取对应的String资源
16 | */
17 | internal fun Context.stringResIdToString(id: Int): String {
18 | return resources.stringResIdToString(id)
19 | }
20 |
21 | /**
22 | * 通过id获取对应的String资源
23 | */
24 | internal fun Resources.stringResIdToString(id: Int): String {
25 | return try {
26 | this.getString(id)
27 | } catch (e: Resources.NotFoundException) {
28 | // 当找不到ID对应的资源时,则直接返回ID本身
29 | return id.toString()
30 | }
31 | }
--------------------------------------------------------------------------------
/mvvmframe/src/main/java/com/king/frame/mvvmframe/base/IView.kt:
--------------------------------------------------------------------------------
1 | package com.king.frame.mvvmframe.base
2 |
3 | import android.os.Bundle
4 | import androidx.annotation.LayoutRes
5 | import androidx.lifecycle.ViewModel
6 |
7 | /**
8 | * 视图层定义一些共性需要实现的函数
9 | *
10 | * @author Jenly
11 | *
12 | * Follow me
13 | */
14 | interface IView
8 | * Follow me
9 | */
10 | interface CombinedController : DialogController, LoadingController, ToastController
11 |
--------------------------------------------------------------------------------
/mvvmframe/src/main/java/com/king/frame/mvvmframe/base/controller/Controller.kt:
--------------------------------------------------------------------------------
1 | package com.king.frame.mvvmframe.base.controller
2 |
3 | import androidx.lifecycle.Lifecycle
4 | import androidx.lifecycle.LifecycleEventObserver
5 | import androidx.lifecycle.LifecycleOwner
6 |
7 | /**
8 | * 控制器定义
9 | *
10 | * @author Jenly
11 | *
12 | * Follow me
13 | */
14 | interface Controller : LifecycleEventObserver {
15 |
16 | override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
17 |
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/mvvmframe/src/main/java/com/king/frame/mvvmframe/base/controller/DefaultCombinedController.kt:
--------------------------------------------------------------------------------
1 | package com.king.frame.mvvmframe.base.controller
2 |
3 | import android.content.Context
4 | import androidx.lifecycle.Lifecycle
5 | import androidx.lifecycle.LifecycleOwner
6 |
7 | /**
8 | * 组合控制器
9 | *
10 | * @author Jenly
11 | *
12 | * Follow me
13 | */
14 | class DefaultCombinedController(
15 | private val context: Context,
16 | private val dialogController: DialogController = DefaultDialogController(context),
17 | private val loadingController: LoadingController = DefaultLoadingController(context),
18 | private val toastController: ToastController = DefaultToastController(context),
19 | ) : CombinedController, DialogController by dialogController,
20 | LoadingController by loadingController,
21 | ToastController by toastController {
22 |
23 | override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
24 | dialogController.onStateChanged(source, event)
25 | loadingController.onStateChanged(source, event)
26 | toastController.onStateChanged(source, event)
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/mvvmframe/src/main/java/com/king/frame/mvvmframe/base/controller/DefaultDialogController.kt:
--------------------------------------------------------------------------------
1 | package com.king.frame.mvvmframe.base.controller
2 |
3 | import android.app.Dialog
4 | import android.content.Context
5 | import android.content.DialogInterface
6 | import android.view.KeyEvent
7 | import android.view.View
8 | import android.view.Window
9 | import androidx.annotation.StyleRes
10 | import androidx.lifecycle.Lifecycle
11 | import androidx.lifecycle.LifecycleOwner
12 | import com.king.frame.mvvmframe.base.BaseProgressDialog
13 |
14 | /**
15 | * 对话框控制器
16 | *
17 | * @author Jenly
18 | *
19 | * Follow me
20 | */
21 | class DefaultDialogController(private val context: Context) : DialogController {
22 |
23 | private var dialog: Dialog? = null
24 |
25 | private var progressDialog: Dialog? = null
26 |
27 | /**
28 | * 显示进度对话框
29 | */
30 | override fun showProgressDialog(
31 | layoutId: Int,
32 | cancel: Boolean,
33 | ) {
34 | dismissProgressDialog()
35 | progressDialog = BaseProgressDialog(context).also {
36 | it.setContentView(layoutId)
37 | it.setCanceledOnTouchOutside(cancel)
38 | it.show()
39 | }
40 | }
41 |
42 | /**
43 | * 关闭进度对话框
44 | */
45 | override fun dismissProgressDialog() {
46 | dismissDialog(progressDialog)
47 | }
48 |
49 | /**
50 | * 显示对话框
51 | * @param contentView 弹框内容视图
52 | * @param styleId Dialog样式
53 | * @param gravity Dialog的对齐方式
54 | * @param widthRatio 宽度比例,根据屏幕宽度计算得来
55 | * @param x x轴偏移量,需与 gravity 结合使用
56 | * @param y y轴偏移量,需与 gravity 结合使用
57 | * @param horizontalMargin 水平方向边距
58 | * @param verticalMargin 垂直方向边距
59 | * @param horizontalWeight 水平方向权重
60 | * @param verticalWeight 垂直方向权重
61 | * @param backCancel 返回键是否可取消(默认为true,false则拦截back键)
62 | */
63 | override fun showDialog(
64 | contentView: View,
65 | @StyleRes styleId: Int,
66 | gravity: Int,
67 | widthRatio: Float,
68 | x: Int,
69 | y: Int,
70 | horizontalMargin: Float,
71 | verticalMargin: Float,
72 | horizontalWeight: Float,
73 | verticalWeight: Float,
74 | backCancel: Boolean,
75 | ) {
76 | dismissDialog()
77 | dialog = Dialog(context, styleId).also {
78 | it.setContentView(contentView)
79 | it.setCanceledOnTouchOutside(false)
80 | it.setOnKeyListener(DialogInterface.OnKeyListener { _, keyCode, _ ->
81 | if (keyCode == KeyEvent.KEYCODE_BACK) {
82 | if (backCancel) {
83 | dismissDialog()
84 | }
85 | return@OnKeyListener true
86 | }
87 | false
88 | })
89 | setWindow(
90 | window = it.window,
91 | gravity = gravity,
92 | widthRatio = widthRatio,
93 | x = x,
94 | y = y,
95 | horizontalMargin = horizontalMargin,
96 | verticalMargin = verticalMargin,
97 | horizontalWeight = horizontalWeight,
98 | verticalWeight = verticalWeight
99 | )
100 | it.show()
101 | }
102 |
103 | }
104 |
105 | /**
106 | * 设置 Window 布局相关参数
107 | * @param window [Window]
108 | * @param gravity Dialog的对齐方式
109 | * @param widthRatio 宽度比例,根据屏幕宽度计算得来
110 | * @param x x轴偏移量,需与 gravity 结合使用
111 | * @param y y轴偏移量,需与 gravity 结合使用
112 | * @param horizontalMargin 水平方向边距
113 | * @param verticalMargin 垂直方向边距
114 | * @param horizontalWeight 水平方向权重
115 | * @param verticalWeight 垂直方向权重
116 | */
117 | override fun setWindow(
118 | window: Window?,
119 | gravity: Int,
120 | widthRatio: Float,
121 | x: Int,
122 | y: Int,
123 | horizontalMargin: Float,
124 | verticalMargin: Float,
125 | horizontalWeight: Float,
126 | verticalWeight: Float,
127 | ) {
128 | window?.attributes?.let {
129 | it.width = (context.resources.displayMetrics.widthPixels * widthRatio).toInt()
130 | it.gravity = gravity
131 | it.x = x
132 | it.y = y
133 | it.horizontalMargin = horizontalMargin
134 | it.verticalMargin = verticalMargin
135 | it.horizontalWeight = horizontalWeight
136 | it.verticalWeight = verticalWeight
137 | window.attributes = it
138 | }
139 | }
140 |
141 | override fun dismissDialog() {
142 | dismissDialog(dialog)
143 | }
144 |
145 | /**
146 | * 关闭对话框
147 | */
148 | override fun dismissDialog(dialog: Dialog?) {
149 | dialog?.dismiss()
150 | }
151 |
152 | override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
153 | super.onStateChanged(source, event)
154 | when (event) {
155 | Lifecycle.Event.ON_DESTROY -> {
156 | dismissProgressDialog()
157 | dismissDialog()
158 | }
159 |
160 | else -> Unit
161 | }
162 | }
163 | }
164 |
--------------------------------------------------------------------------------
/mvvmframe/src/main/java/com/king/frame/mvvmframe/base/controller/DefaultLoadingController.kt:
--------------------------------------------------------------------------------
1 | package com.king.frame.mvvmframe.base.controller
2 |
3 | import android.app.Dialog
4 | import android.content.Context
5 | import androidx.lifecycle.Lifecycle
6 | import androidx.lifecycle.LifecycleOwner
7 | import com.king.frame.mvvmframe.R
8 | import com.king.frame.mvvmframe.base.BaseProgressDialog
9 |
10 | /**
11 | * 加载控制器
12 | *
13 | * @author Jenly
14 | *
15 | * Follow me
16 | */
17 | class DefaultLoadingController(private val context: Context) : LoadingController {
18 |
19 | private var loadingDialog: Dialog? = null
20 |
21 | override fun showLoading() {
22 | hideLoading()
23 | loadingDialog = BaseProgressDialog(context).also {
24 | it.setContentView(R.layout.mvvmframe_loading)
25 | it.setCanceledOnTouchOutside(false)
26 | it.show()
27 | }
28 | }
29 |
30 | override fun hideLoading() {
31 | loadingDialog?.dismiss()
32 | }
33 |
34 | override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
35 | super.onStateChanged(source, event)
36 | when (event) {
37 | Lifecycle.Event.ON_DESTROY -> {
38 | hideLoading()
39 | }
40 |
41 | else -> Unit
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/mvvmframe/src/main/java/com/king/frame/mvvmframe/base/controller/DefaultToastController.kt:
--------------------------------------------------------------------------------
1 | package com.king.frame.mvvmframe.base.controller
2 |
3 | import android.content.Context
4 | import android.widget.Toast
5 | import androidx.lifecycle.Lifecycle
6 | import androidx.lifecycle.LifecycleOwner
7 | import com.hjq.toast.Toaster
8 | import com.king.frame.mvvmframe.base.stringResIdToString
9 |
10 | /**
11 | * 默认的Toast控制器
12 | *
13 | * @author Jenly
14 | *
15 | * Follow me
16 | */
17 | class DefaultToastController(private val context: Context) : ToastController {
18 |
19 | private var toast: Toast? = null
20 | private var isDestroy = false
21 |
22 | override fun showToast(text: CharSequence) {
23 | if (Toaster.isInit()) {
24 | Toaster.show(text)
25 | } else {
26 | show(text)
27 | }
28 | }
29 |
30 | override fun showToast(resId: Int) {
31 | showToast(context.applicationContext.stringResIdToString(resId))
32 | }
33 |
34 | private fun show(text: CharSequence) {
35 | if (isDestroy) {
36 | return
37 | }
38 | toast?.cancel()
39 | toast = Toast.makeText(context, text, Toast.LENGTH_SHORT)
40 | toast?.show()
41 | }
42 |
43 | override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
44 | super.onStateChanged(source, event)
45 | when (event) {
46 | Lifecycle.Event.ON_DESTROY -> {
47 | isDestroy = true
48 | toast?.cancel()
49 | toast = null
50 | }
51 |
52 | else -> Unit
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/mvvmframe/src/main/java/com/king/frame/mvvmframe/base/controller/DialogController.kt:
--------------------------------------------------------------------------------
1 | package com.king.frame.mvvmframe.base.controller
2 |
3 | import android.app.Dialog
4 | import android.view.Gravity
5 | import android.view.View
6 | import android.view.Window
7 | import androidx.annotation.StyleRes
8 | import com.king.frame.mvvmframe.R
9 |
10 | /**
11 | * 对话框控制器;默认实现请查看[DefaultDialogController]
12 | *
13 | * @author Jenly
14 | *
15 | * Follow me
16 | */
17 | interface DialogController : Controller {
18 |
19 | /**
20 | * 显示进度对话框
21 | */
22 | fun showProgressDialog(
23 | layoutId: Int = R.layout.mvvmframe_progress_dialog,
24 | cancel: Boolean = false,
25 | )
26 |
27 | /**
28 | * 关闭进度对话框
29 | */
30 | fun dismissProgressDialog()
31 |
32 | /**
33 | * 显示对话框
34 | * @param contentView 弹框内容视图
35 | * @param styleId Dialog样式
36 | * @param gravity Dialog的对齐方式
37 | * @param widthRatio 宽度比例,根据屏幕宽度计算得来
38 | * @param x x轴偏移量,需与 gravity 结合使用
39 | * @param y y轴偏移量,需与 gravity 结合使用
40 | * @param horizontalMargin 水平方向边距
41 | * @param verticalMargin 垂直方向边距
42 | * @param horizontalWeight 水平方向权重
43 | * @param verticalWeight 垂直方向权重
44 | * @param backCancel 返回键是否可取消(默认为true,false则拦截back键)
45 | */
46 | fun showDialog(
47 | contentView: View,
48 | @StyleRes styleId: Int = R.style.mvvmframe_dialog,
49 | gravity: Int = Gravity.NO_GRAVITY,
50 | widthRatio: Float = 0.85f,
51 | x: Int = 0,
52 | y: Int = 0,
53 | horizontalMargin: Float = 0f,
54 | verticalMargin: Float = 0f,
55 | horizontalWeight: Float = 0f,
56 | verticalWeight: Float = 0f,
57 | backCancel: Boolean = true
58 | )
59 |
60 | /**
61 | * 设置 Window 布局相关参数
62 | * @param window [Window]
63 | * @param gravity Dialog的对齐方式
64 | * @param widthRatio 宽度比例,根据屏幕宽度计算得来
65 | * @param x x轴偏移量,需与 gravity 结合使用
66 | * @param y y轴偏移量,需与 gravity 结合使用
67 | * @param horizontalMargin 水平方向边距
68 | * @param verticalMargin 垂直方向边距
69 | * @param horizontalWeight 水平方向权重
70 | * @param verticalWeight 垂直方向权重
71 | */
72 | fun setWindow(
73 | window: Window?,
74 | gravity: Int = Gravity.NO_GRAVITY,
75 | widthRatio: Float = 0.85f,
76 | x: Int = 0,
77 | y: Int = 0,
78 | horizontalMargin: Float = 0f,
79 | verticalMargin: Float = 0f,
80 | horizontalWeight: Float = 0f,
81 | verticalWeight: Float = 0f
82 | )
83 |
84 | /**
85 | * 关闭对话框
86 | */
87 | fun dismissDialog()
88 |
89 | /**
90 | * 关闭对话框
91 | */
92 | fun dismissDialog(dialog: Dialog?)
93 |
94 | }
95 |
--------------------------------------------------------------------------------
/mvvmframe/src/main/java/com/king/frame/mvvmframe/base/controller/LoadingController.kt:
--------------------------------------------------------------------------------
1 | package com.king.frame.mvvmframe.base.controller
2 |
3 | /**
4 | * 加载控制器;默认实现请查看[DefaultLoadingController]
5 | *
6 | * @author Jenly
7 | *
8 | * Follow me
9 | */
10 | interface LoadingController : Controller {
11 |
12 | /**
13 | * 显示加载中
14 | */
15 | fun showLoading()
16 |
17 | /**
18 | * 隐藏加载中
19 | */
20 | fun hideLoading()
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/mvvmframe/src/main/java/com/king/frame/mvvmframe/base/controller/ToastController.kt:
--------------------------------------------------------------------------------
1 | package com.king.frame.mvvmframe.base.controller
2 |
3 | import android.widget.Toast
4 | import androidx.annotation.StringRes
5 |
6 | /**
7 | * Toast控制器;默认实现请查看[DefaultToastController]
8 | *
9 | * @author Jenly
10 | *
11 | * Follow me
12 | */
13 | interface ToastController : Controller {
14 |
15 | /**
16 | * 通过[Toast]显示提示信息
17 | */
18 | fun showToast(text: CharSequence)
19 |
20 | /**
21 | * 通过[Toast]显示提示信息
22 | */
23 | fun showToast(@StringRes resId: Int)
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/mvvmframe/src/main/java/com/king/frame/mvvmframe/config/AppliesOptions.kt:
--------------------------------------------------------------------------------
1 | package com.king.frame.mvvmframe.config
2 |
3 | import android.content.Context
4 | import androidx.room.RoomDatabase
5 | import com.google.gson.Gson
6 | import com.google.gson.GsonBuilder
7 | import com.king.frame.mvvmframe.di.module.ConfigModule
8 | import okhttp3.OkHttpClient
9 | import retrofit2.Retrofit
10 |
11 | /**
12 | * MVVMFrame全局配置
13 | *
14 | * @author Jenly
15 | *
16 | * Follow me
17 | */
18 | interface AppliesOptions {
19 | /**
20 | * 为框架提供一些配置参数入口
21 | * @param context
22 | * @param builder
23 | */
24 | fun applyOptions(context: Context, builder: ConfigModule.Builder)
25 |
26 | /**
27 | * 为框架中的[Retrofit]提供配置参数入口
28 | */
29 | interface RetrofitOptions {
30 | fun applyOptions(builder: Retrofit.Builder)
31 | }
32 |
33 | /**
34 | * 为框架中的[OkHttpClient]提供配置参数入口
35 | */
36 | interface OkHttpClientOptions {
37 | fun applyOptions(builder: OkHttpClient.Builder)
38 | }
39 |
40 | /**
41 | * 为框架中的[Gson]提供配置参数入口
42 | */
43 | interface GsonOptions {
44 | fun applyOptions(builder: GsonBuilder)
45 | }
46 |
47 | /**
48 | * 为框架中的[Config]提供配置参数入口
49 | */
50 | interface ConfigOptions {
51 | fun applyOptions(builder: Config.Builder)
52 | }
53 |
54 | /**
55 | * 为框架中的[RoomDatabase]提供配置参数入口
56 | */
57 | interface RoomDatabaseOptions {
58 | fun applyOptions(builder: RoomDatabase.Builder
15 | * Follow me
16 | */
17 | class Config(builder: Builder) {
18 |
19 | internal val httpLoggingLevel: Level
20 | internal val isAddGsonConverterFactory: Boolean
21 | internal val retrofitServiceMaxCacheSize: Int
22 | internal val roomDatabaseMaxCacheSize: Int
23 |
24 | init {
25 | isAddGsonConverterFactory = builder.isAddGsonConverterFactory
26 | httpLoggingLevel = builder.httpLoggingLevel
27 | retrofitServiceMaxCacheSize = builder.retrofitServiceMaxCacheSize
28 | roomDatabaseMaxCacheSize = builder.roomDatabaseMaxCacheSize
29 | }
30 |
31 | /**
32 | * [Config.Builder]
33 | */
34 | @Suppress("unused")
35 | class Builder {
36 |
37 | internal var isAddGsonConverterFactory: Boolean = true
38 | internal var httpLoggingLevel: Level = Level.NONE
39 | internal var retrofitServiceMaxCacheSize = DEFAULT_RETROFIT_SERVICE_MAX_SIZE
40 | internal var roomDatabaseMaxCacheSize = DEFAULT_ROOM_DATABASE_MAX_SIZE
41 |
42 | /**
43 | * 配置是否添加 [GsonConverterFactory];默认为:true
44 | */
45 | fun addGsonConverterFactory(addGsonConverterFactory: Boolean): Builder {
46 | this.isAddGsonConverterFactory = addGsonConverterFactory
47 | return this
48 | }
49 |
50 | /**
51 | * 配置 http日志级别 [HttpLoggingInterceptor];默认为:[Level.BASIC]
52 | */
53 | fun httpLoggingLevel(level: Level): Builder {
54 | this.httpLoggingLevel = level
55 | return this
56 | }
57 |
58 | /**
59 | * [DataSource]中通过[DataSource.getRetrofitService]获取 service 实例的最大可缓存数量,
60 | * 采用[LruCache]缓存策略;默认值为:[DEFAULT_RETROFIT_SERVICE_MAX_SIZE]
61 | */
62 | fun retrofitServiceMaxCacheSize(size: Int): Builder {
63 | this.retrofitServiceMaxCacheSize = size
64 | return this
65 | }
66 |
67 | /**
68 | * [DataSource]中 通过 [DataSource.getRoomDatabase] 获取 [RoomDatabase] 实例的最大缓存数量,
69 | * 采用[LruCache]缓存策略;默认值为:[DEFAULT_ROOM_DATABASE_MAX_SIZE]
70 | */
71 | fun roomDatabaseMaxCacheSize(size: Int): Builder {
72 | this.roomDatabaseMaxCacheSize = size
73 | return this
74 | }
75 |
76 | /**
77 | * 构建[Config]
78 | */
79 | fun build(): Config {
80 | return Config(this)
81 | }
82 |
83 | override fun toString(): String {
84 | return "Builder(isAddGsonConverterFactory=$isAddGsonConverterFactory, " +
85 | "httpLoggingLevel=$httpLoggingLevel, " +
86 | "retrofitServiceMaxCacheSize=$retrofitServiceMaxCacheSize, " +
87 | "roomDatabaseMaxCacheSize=$roomDatabaseMaxCacheSize)"
88 | }
89 |
90 | }
91 |
92 | companion object {
93 |
94 | internal const val DEFAULT_RETROFIT_SERVICE_MAX_SIZE = 60
95 |
96 | internal const val DEFAULT_ROOM_DATABASE_MAX_SIZE = 10
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/mvvmframe/src/main/java/com/king/frame/mvvmframe/config/FrameConfigModule.kt:
--------------------------------------------------------------------------------
1 | package com.king.frame.mvvmframe.config
2 |
3 | /**
4 | * MVVMFrame全局配置
5 | *
6 | * @author Jenly
7 | *
8 | * Follow me
9 | */
10 | abstract class FrameConfigModule : AppliesOptions {
11 | /**
12 | * 是否启用解析配置
13 | * @return 默认返回{@code true}
14 | */
15 | open fun isManifestParsingEnabled(): Boolean {
16 | return true
17 | }
18 | }
--------------------------------------------------------------------------------
/mvvmframe/src/main/java/com/king/frame/mvvmframe/config/ManifestParser.kt:
--------------------------------------------------------------------------------
1 | package com.king.frame.mvvmframe.config
2 |
3 | import android.content.Context
4 | import android.content.pm.PackageManager
5 | import timber.log.Timber
6 |
7 | /**
8 | * Manifest解析器;实现参考 [ManifestParse](https://github.com/bumptech/glide/blob/f7d860412f061e059aa84a42f2563a01ac8c303b/library/src/main/java/com/bumptech/glide/module/ManifestParser.java)
9 | *
10 | * @author Jenly
11 | *
12 | * Follow me
13 | *
14 | */
15 | class ManifestParser(private val context: Context) {
16 |
17 | /**
18 | * 解析
19 | */
20 | @Suppress("DEPRECATION")
21 | fun parse(): List
29 | * Follow me
30 | */
31 | @Deprecated(
32 | "This Class is deprecated. Use DataSourceImpl instead",
33 | replaceWith = ReplaceWith(
34 | expression = "DataSourceImpl",
35 | imports = ["com.king.frame.mvvmframe.data.datasource.DataSourceImpl"]
36 | )
37 | )
38 | @Singleton
39 | open class DataRepository @Inject constructor() : Repository {
40 |
41 | @Inject
42 | internal lateinit var dataSource: DefaultDataSource
43 |
44 | /**
45 | * 传入API接口类的Class,通过[Retrofit.create] 获得对应的Class
46 | *
47 | * @param service [Retrofit] 对应的API接口定义
48 | * @param T 泛型[T]为具体的接口对象类型
49 | * @return [Retrofit.create]
50 | */
51 | override fun
26 | * Follow me
27 | *
28 | */
29 | @Deprecated(
30 | "This Class is deprecated. Use DataSource instead",
31 | replaceWith = ReplaceWith(
32 | expression = "DataSource",
33 | imports = ["com.king.frame.mvvmframe.data.datasource.DataSource"]
34 | )
35 | )
36 | interface Repository {
37 |
38 | /**
39 | * 传入API接口类的Class,通过[Retrofit.create] 获得对应的Class
40 | *
41 | * @param service [Retrofit] 对应的API接口定义
42 | * @param T 泛型[T]为具体的接口对象类型
43 | * @return [Retrofit.create]
44 | */
45 | fun
13 | * Follow me
14 | */
15 | interface DataSource {
16 |
17 | /**
18 | * 传入API接口类的Class,通过[Retrofit.create] 获得对应的Class
19 | *
20 | * @param service [Retrofit] 对应的API接口定义
21 | * @param T 泛型[T]为具体的接口对象类型
22 | * @return [Retrofit.create]
23 | */
24 | fun
21 | * Follow me
22 | */
23 | @Singleton
24 | open class DefaultDataSource @Inject constructor(
25 | @ApplicationContext private val context: Context,
26 | private val retrofit: Retrofit,
27 | private val config: Config,
28 | private val roomDatabaseOptions: RoomDatabaseOptions?,
29 | ) : DataSource {
30 |
31 | /**
32 | * 缓存 Retrofit Service
33 | */
34 | private val retrofitServiceCache: LruCache
27 | * Follow me
28 | */
29 | @InstallIn(SingletonComponent::class)
30 | @Module
31 | object ConfigModule {
32 | @Singleton
33 | @Provides
34 | fun provideBaseUrl(builder: Builder): HttpUrl {
35 | var baseUrl = builder.baseUrl
36 | if (baseUrl == null) { // 如果 mBaseUrl 为空表示没有在自定义配置 FrameConfigModule 中配过 BaseUrl
37 | // 尝试去 RetrofitHelper 中取一次 BaseUrl,这里相当于多支持一种配置 BaseUrl 的方式
38 | baseUrl = RetrofitHelper.getInstance().baseUrl
39 | }
40 | // 再次检测 mBaseUrl 是否为空,如果依旧为空,表示两种配置方式都没有配置过,则直接抛出异常
41 | Objects.requireNonNull(baseUrl, "baseUrl == null")
42 | return baseUrl!!
43 | }
44 |
45 | @Singleton
46 | @Provides
47 | fun provideRetrofitOptions(builder: Builder): RetrofitOptions? {
48 | return builder.retrofitOptions
49 | }
50 |
51 | @Singleton
52 | @Provides
53 | fun provideOkHttpClientOptions(builder: Builder): OkHttpClientOptions? {
54 | return builder.okHttpClientOptions
55 | }
56 |
57 | @Singleton
58 | @Provides
59 | fun provideGsonOptions(builder: Builder): GsonOptions? {
60 | return builder.gsonOptions
61 | }
62 |
63 | @Singleton
64 | @Provides
65 | fun provideConfigOptions(builder: Builder): ConfigOptions? {
66 | return builder.configOptions
67 | }
68 |
69 | @Singleton
70 | @Provides
71 | fun provideRoomDatabaseOptions(builder: Builder): RoomDatabaseOptions? {
72 | return builder.roomDatabaseOptions
73 | }
74 |
75 | @Singleton
76 | @Provides
77 | fun provideConfigModuleBuilder(@ApplicationContext context: Context): Builder {
78 | val builder = Builder()
79 | // 解析配置
80 | val modules = ManifestParser(context).parse()
81 | // 遍历配置
82 | for (configModule in modules) {
83 | // 如果启用则申请配置参数
84 | if (configModule.isManifestParsingEnabled()) {
85 | configModule.applyOptions(context, builder)
86 | }
87 | }
88 | return builder
89 | }
90 |
91 | class Builder {
92 | internal var baseUrl: HttpUrl? = null
93 | internal var retrofitOptions: RetrofitOptions? = null
94 | internal var okHttpClientOptions: OkHttpClientOptions? = null
95 | internal var gsonOptions: GsonOptions? = null
96 | internal var configOptions: ConfigOptions? = null
97 | internal var roomDatabaseOptions: RoomDatabaseOptions? = null
98 | fun baseUrl(baseUrl: String): Builder {
99 | this.baseUrl = baseUrl.toHttpUrl()
100 | return this
101 | }
102 |
103 | fun baseUrl(baseUrl: HttpUrl): Builder {
104 | this.baseUrl = baseUrl
105 | return this
106 | }
107 |
108 | fun retrofitOptions(retrofitOptions: RetrofitOptions): Builder {
109 | this.retrofitOptions = retrofitOptions
110 | return this
111 | }
112 |
113 | fun okHttpClientOptions(okHttpClientOptions: OkHttpClientOptions): Builder {
114 | this.okHttpClientOptions = okHttpClientOptions
115 | return this
116 | }
117 |
118 | fun gsonOptions(gsonOptions: GsonOptions): Builder {
119 | this.gsonOptions = gsonOptions
120 | return this
121 | }
122 |
123 | fun configOptions(configOptions: ConfigOptions): Builder {
124 | this.configOptions = configOptions
125 | return this
126 | }
127 |
128 | fun roomDatabaseOptions(roomDatabaseOptions: RoomDatabaseOptions): Builder {
129 | this.roomDatabaseOptions = roomDatabaseOptions
130 | return this
131 | }
132 | }
133 |
134 | }
135 |
--------------------------------------------------------------------------------
/mvvmframe/src/main/java/com/king/frame/mvvmframe/di/module/DataSourceModule.kt:
--------------------------------------------------------------------------------
1 | package com.king.frame.mvvmframe.di.module
2 |
3 | import com.king.frame.mvvmframe.data.datasource.DataSource
4 | import com.king.frame.mvvmframe.data.datasource.DefaultDataSource
5 | import dagger.Binds
6 | import dagger.Module
7 | import dagger.hilt.InstallIn
8 | import dagger.hilt.components.SingletonComponent
9 |
10 | /**
11 | * 数据源 注入
12 | *
13 | * @author Jenly
14 | *
15 | * Follow me
16 | */
17 | @InstallIn(SingletonComponent::class)
18 | @Module
19 | abstract class DataSourceModule {
20 |
21 | /**
22 | * 绑定DataSource
23 | */
24 | @Binds
25 | abstract fun bindDataSource(dataSource: DefaultDataSource): DataSource
26 | }
27 |
--------------------------------------------------------------------------------
/mvvmframe/src/main/java/com/king/frame/mvvmframe/di/module/HttpModule.kt:
--------------------------------------------------------------------------------
1 | package com.king.frame.mvvmframe.di.module
2 |
3 | import com.google.gson.Gson
4 | import com.google.gson.GsonBuilder
5 | import com.king.frame.mvvmframe.config.AppliesOptions
6 | import com.king.frame.mvvmframe.config.AppliesOptions.GsonOptions
7 | import com.king.frame.mvvmframe.config.AppliesOptions.OkHttpClientOptions
8 | import com.king.frame.mvvmframe.config.AppliesOptions.RetrofitOptions
9 | import com.king.frame.mvvmframe.config.Config
10 | import com.king.retrofit.retrofithelper.RetrofitHelper
11 | import dagger.Module
12 | import dagger.Provides
13 | import dagger.hilt.InstallIn
14 | import dagger.hilt.components.SingletonComponent
15 | import okhttp3.HttpUrl
16 | import okhttp3.OkHttpClient
17 | import okhttp3.logging.HttpLoggingInterceptor
18 | import retrofit2.Retrofit
19 | import retrofit2.converter.gson.GsonConverterFactory
20 | import timber.log.Timber
21 | import javax.inject.Singleton
22 |
23 | /**
24 | * Http相关注入
25 | *
26 | * @author Jenly
27 | *
28 | * Follow me
29 | */
30 | @InstallIn(SingletonComponent::class)
31 | @Module
32 | object HttpModule {
33 |
34 | @Singleton
35 | @Provides
36 | fun provideRetrofit(builder: Retrofit.Builder, options: RetrofitOptions?): Retrofit {
37 | options?.applyOptions(builder)
38 | return builder.build()
39 | }
40 |
41 | @Singleton
42 | @Provides
43 | fun provideOkHttpClient(
44 | builder: OkHttpClient.Builder,
45 | config: Config,
46 | options: OkHttpClientOptions?
47 | ): OkHttpClient {
48 | options?.applyOptions(builder)
49 | val loggingInterceptor = HttpLoggingInterceptor().apply {
50 | level = config.httpLoggingLevel
51 | }
52 | builder.addInterceptor(loggingInterceptor)
53 | return builder.build()
54 | }
55 |
56 | @Singleton
57 | @Provides
58 | fun provideGson(builder: GsonBuilder, options: GsonOptions?): Gson {
59 | options?.applyOptions(builder)
60 | return builder.create()
61 | }
62 |
63 | @Singleton
64 | @Provides
65 | fun provideConfig(
66 | builder: Config.Builder,
67 | options: AppliesOptions.ConfigOptions?
68 | ): Config {
69 | options?.applyOptions(builder)
70 | return builder.build()
71 | }
72 |
73 | @Singleton
74 | @Provides
75 | fun provideRetrofitBuilder(
76 | httpUrl: HttpUrl,
77 | client: OkHttpClient,
78 | gson: Gson,
79 | config: Config
80 | ): Retrofit.Builder {
81 | val builder = Retrofit.Builder()
82 | builder.baseUrl(httpUrl)
83 | .client(client)
84 | if (config.isAddGsonConverterFactory) {
85 | builder.addConverterFactory(GsonConverterFactory.create(gson))
86 | }
87 | return builder
88 | }
89 |
90 | @Singleton
91 | @Provides
92 | fun provideClientBuilder(): OkHttpClient.Builder {
93 | return RetrofitHelper.getInstance().createClientBuilder()
94 | }
95 |
96 | @Singleton
97 | @Provides
98 | fun provideGsonBuilder(): GsonBuilder {
99 | return GsonBuilder()
100 | }
101 |
102 | @Singleton
103 | @Provides
104 | fun provideConfigBuilder(): Config.Builder {
105 | return Config.Builder()
106 | }
107 |
108 | private const val TAG = "OkHttp"
109 | }
110 |
--------------------------------------------------------------------------------
/mvvmframe/src/main/java/com/king/frame/mvvmframe/di/module/RepositoryModule.kt:
--------------------------------------------------------------------------------
1 | package com.king.frame.mvvmframe.di.module
2 |
3 | import com.king.frame.mvvmframe.data.DataRepository
4 | import com.king.frame.mvvmframe.data.Repository
5 | import dagger.Binds
6 | import dagger.Module
7 | import dagger.hilt.InstallIn
8 | import dagger.hilt.components.SingletonComponent
9 |
10 | /**
11 | * 仓库注入
12 | *
13 | * @author Jenly
14 | *
15 | * Follow me
16 | */
17 | @Deprecated(
18 | "This Class is deprecated. Use DataModule instead",
19 | replaceWith = ReplaceWith(
20 | expression = "DataModule",
21 | imports = ["com.king.frame.mvvmframe.di.module.DataModule"]
22 | )
23 | )
24 | @InstallIn(SingletonComponent::class)
25 | @Module
26 | abstract class RepositoryModule {
27 |
28 | /**
29 | * 绑定Repository
30 | */
31 | @Binds
32 | abstract fun bindRepository(dataRepository: DataRepository): Repository
33 | }
34 |
--------------------------------------------------------------------------------
/mvvmframe/src/main/java/com/king/frame/mvvmframe/util/JumpDebounce.kt:
--------------------------------------------------------------------------------
1 | package com.king.frame.mvvmframe.util
2 |
3 | import android.content.Intent
4 | import android.os.SystemClock
5 | import timber.log.Timber
6 |
7 | /**
8 | * 跳转防抖
9 | *
10 | * @author Jenly
11 | *
12 | * Follow me
13 | */
14 | internal class JumpDebounce {
15 | private var lastTag: String? = null
16 | private var lastJumpTime: Long = 0L
17 |
18 | /**
19 | * 是否忽略跳转
20 | */
21 | fun isIgnoreJump(intent: Intent, intervalTime: Int = DEBOUNCE_INTERVAL_TIME): Boolean {
22 | val jumpTag = if (intent.component != null) {
23 | intent.component!!.className
24 | } else if (intent.action != null) {
25 | intent.action
26 | } else {
27 | null
28 | }
29 | if (jumpTag.isNullOrEmpty()) {
30 | return false
31 | }
32 | if (jumpTag == lastTag && lastJumpTime > SystemClock.elapsedRealtime() - intervalTime) {
33 | Timber.d("Ignore Intent:$jumpTag")
34 | return true
35 | }
36 | lastTag = jumpTag
37 | lastJumpTime = SystemClock.elapsedRealtime()
38 | return false
39 | }
40 |
41 | companion object {
42 | const val DEBOUNCE_INTERVAL_TIME = 500
43 | }
44 | }
--------------------------------------------------------------------------------
/mvvmframe/src/main/res/anim/mvvmframe_dialog_in.xml:
--------------------------------------------------------------------------------
1 |
2 | >(emptyList())
26 | val cityFlow = _cityFlow.asSharedFlow()
27 |
28 | /**
29 | * 获取热门城市
30 | */
31 | fun getHotCities() {
32 | launch {
33 | cityRepository.getHotCities().also {
34 | _cityFlow.emit(it)
35 | }
36 | }
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/app/src/main/java/com/king/mvvmframe/app/dictionary/DictionaryActivity.kt:
--------------------------------------------------------------------------------
1 | package com.king.mvvmframe.app.dictionary
2 |
3 | import android.annotation.SuppressLint
4 | import android.os.Bundle
5 | import android.text.InputFilter
6 | import android.text.InputFilter.LengthFilter
7 | import android.view.Menu
8 | import android.view.View
9 | import androidx.appcompat.widget.SearchView
10 | import androidx.appcompat.widget.SearchView.SearchAutoComplete
11 | import androidx.lifecycle.flowWithLifecycle
12 | import androidx.lifecycle.lifecycleScope
13 | import androidx.recyclerview.widget.LinearLayoutManager
14 | import com.king.base.adapter.divider.DividerItemDecoration
15 | import com.king.base.util.StringUtils
16 | import com.king.frame.mvvmframe.base.BaseActivity
17 | import com.king.mvvmframe.R
18 | import com.king.mvvmframe.app.adapter.BindingAdapter
19 | import com.king.mvvmframe.app.adapter.SearchHistoryAdapter
20 | import com.king.mvvmframe.data.model.SearchHistory
21 | import com.king.mvvmframe.databinding.DictionaryActivityBinding
22 | import dagger.hilt.android.AndroidEntryPoint
23 | import kotlinx.coroutines.launch
24 |
25 | /**
26 | * 示例
27 | *
28 | * @author Jenly
29 | *
>(emptyList())
27 | val oilPriceFlow = _oilPriceFlow.asSharedFlow()
28 |
29 | /**
30 | * 获取油价信息
31 | */
32 | fun getOilPriceInfo() {
33 | launch {
34 | val result = oilPriceRepository.getOilPriceInfo()
35 | if (isSuccess(result)) {
36 | result.data?.also {
37 | _oilPriceFlow.emit(it)
38 | }
39 | }
40 | }
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/app/src/main/java/com/king/mvvmframe/config/AppConfigModule.kt:
--------------------------------------------------------------------------------
1 | package com.king.mvvmframe.config
2 |
3 | import android.content.Context
4 | import androidx.room.RoomDatabase
5 | import com.google.gson.GsonBuilder
6 | import com.king.frame.mvvmframe.config.AppliesOptions
7 | import com.king.frame.mvvmframe.config.AppliesOptions.GsonOptions
8 | import com.king.frame.mvvmframe.config.AppliesOptions.OkHttpClientOptions
9 | import com.king.frame.mvvmframe.config.AppliesOptions.RetrofitOptions
10 | import com.king.frame.mvvmframe.config.AppliesOptions.RoomDatabaseOptions
11 | import com.king.frame.mvvmframe.config.Config
12 | import com.king.frame.mvvmframe.config.FrameConfigModule
13 | import com.king.frame.mvvmframe.di.module.ConfigModule
14 | import com.king.mvvmframe.BuildConfig
15 | import com.king.mvvmframe.constant.Constants
16 | import com.king.retrofit.retrofithelper.RetrofitHelper
17 | import okhttp3.OkHttpClient
18 | import okhttp3.logging.HttpLoggingInterceptor
19 | import retrofit2.Retrofit
20 |
21 | /**
22 | * 全局配置
23 | *
24 | * @author Jenly
25 | *
?>
39 | }
40 |
--------------------------------------------------------------------------------
/app/src/main/java/com/king/mvvmframe/data/model/City.kt:
--------------------------------------------------------------------------------
1 | package com.king.mvvmframe.data.model
2 |
3 | /**
4 | * 城市
5 | *
6 | * @author Jenly
7 | *
> {
53 | return searchHistoryDao.getHistoryFlow(size).filterNotNull()
54 | }
55 |
56 | /**
57 | * 清空搜索历史
58 | */
59 | suspend fun clearSearchHistory() {
60 | searchHistoryDao.deleteAll()
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/app/src/main/java/com/king/mvvmframe/data/repository/OilPriceRepository.kt:
--------------------------------------------------------------------------------
1 | package com.king.mvvmframe.data.repository
2 |
3 | import com.king.frame.mvvmframe.data.datasource.DataSource
4 | import com.king.mvvmframe.constant.Constants
5 | import com.king.mvvmframe.data.model.OilPrice
6 | import com.king.mvvmframe.data.model.Result
7 | import com.king.mvvmframe.data.service.OilPriceService
8 | import javax.inject.Inject
9 | import javax.inject.Singleton
10 |
11 | /**
12 | * 油价价格数据仓库
13 | *
14 | * @author Jenly
15 | *
> {
29 | return oilPriceService.getOilPriceInfo(key)
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/app/src/main/java/com/king/mvvmframe/data/service/CityService.kt:
--------------------------------------------------------------------------------
1 | package com.king.mvvmframe.data.service
2 |
3 | import com.king.mvvmframe.constant.Constants
4 | import com.king.mvvmframe.data.model.City
5 | import com.king.retrofit.retrofithelper.annotation.DomainName
6 | import com.king.retrofit.retrofithelper.annotation.Timeout
7 | import retrofit2.http.GET
8 |
9 | /**
10 | * 城市API
11 | *
12 | * @author Jenly
13 | *
>
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/app/src/main/java/com/king/mvvmframe/util/RandomUtil.kt:
--------------------------------------------------------------------------------
1 | package com.king.mvvmframe.util
2 |
3 | import android.graphics.Color
4 | import androidx.annotation.ColorInt
5 | import androidx.annotation.IntRange
6 | import java.util.Random
7 |
8 | /**
9 | * @author Jenly
10 | *