├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── cxz │ │ └── kotlin │ │ └── samples │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── cxz │ │ │ └── kotlin │ │ │ └── samples │ │ │ ├── app │ │ │ └── App.kt │ │ │ ├── bean │ │ │ └── DataRepo.kt │ │ │ ├── constant │ │ │ └── Constant.kt │ │ │ ├── ext │ │ │ └── Ext.kt │ │ │ ├── http │ │ │ ├── MainApi.kt │ │ │ └── MainRetrofit.kt │ │ │ ├── mvp │ │ │ ├── contract │ │ │ │ ├── MainContract.kt │ │ │ │ └── TestContract.kt │ │ │ ├── model │ │ │ │ ├── MainModel.kt │ │ │ │ └── TestModel.kt │ │ │ └── presenter │ │ │ │ ├── MainPresenter.kt │ │ │ │ └── TestPresenter.kt │ │ │ ├── ui │ │ │ ├── activity │ │ │ │ └── MainActivity.kt │ │ │ └── fragment │ │ │ │ └── TestFragment.kt │ │ │ ├── utils │ │ │ ├── DialogUtil.kt │ │ │ ├── PermissionHelper.kt │ │ │ └── PermissionPageUtil.java │ │ │ └── widgets │ │ │ └── PermissionDialog.kt │ └── res │ │ ├── color │ │ ├── s_app_color_blue_2.xml │ │ ├── s_app_color_gray.xml │ │ ├── s_btn_blue_bg.xml │ │ ├── s_btn_blue_border.xml │ │ ├── s_btn_blue_text.xml │ │ └── s_topbar_btn_color.xml │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── bg_white_corner_10.xml │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── dialog_permission.xml │ │ └── fragment_test.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── attr.xml │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── cxz │ └── kotlin │ └── samples │ └── ExampleUnitTest.kt ├── art └── base.png ├── baselibs ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── cxz │ │ └── kotlin │ │ └── baselibs │ │ ├── base │ │ ├── BaseActivity.kt │ │ ├── BaseFragment.kt │ │ ├── BaseMvpActivity.kt │ │ ├── BaseMvpFragment.kt │ │ └── BaseMvpTitleActivity.kt │ │ ├── bean │ │ └── BaseBean.kt │ │ ├── config │ │ └── AppConfig.kt │ │ ├── ext │ │ ├── CommonExt.kt │ │ ├── ImageViewExt.kt │ │ └── RxExt.kt │ │ ├── http │ │ ├── HttpStatus.kt │ │ ├── RetrofitFactory.kt │ │ ├── constant │ │ │ └── HttpConstant.kt │ │ ├── cookies │ │ │ ├── CookieManager.kt │ │ │ ├── OkHttpCookies.kt │ │ │ └── PersistentCookieStore.kt │ │ ├── exception │ │ │ ├── ApiException.kt │ │ │ └── ExceptionHandle.kt │ │ ├── function │ │ │ └── RetryWithDelay.kt │ │ └── interceptor │ │ │ ├── CacheInterceptor.kt │ │ │ ├── CookieInterceptor.kt │ │ │ ├── HeaderInterceptor.kt │ │ │ └── QueryParameterInterceptor.kt │ │ ├── mvp │ │ ├── BaseModel.kt │ │ ├── BasePresenter.kt │ │ ├── IModel.kt │ │ ├── IPresenter.kt │ │ └── IView.kt │ │ ├── rx │ │ ├── BaseObserver.kt │ │ ├── BaseSubscriber.kt │ │ ├── SchedulerUtils.kt │ │ └── scheduler │ │ │ ├── BaseScheduler.kt │ │ │ ├── ComputationMainScheduler.kt │ │ │ ├── IoMainScheduler.kt │ │ │ ├── NewThreadMainScheduler.kt │ │ │ ├── SingleMainScheduler.kt │ │ │ └── TrampolineMainScheduler.kt │ │ ├── utils │ │ ├── AnimatorUtil.kt │ │ ├── AppUtils.kt │ │ ├── CommonUtil.kt │ │ ├── FileProvider7.kt │ │ ├── KeyBoardUtil.kt │ │ ├── NLog.kt │ │ ├── NetWorkUtil.kt │ │ ├── Preference.kt │ │ ├── RomUtil.kt │ │ ├── RxTimerUtil.kt │ │ └── StatusBarUtil.kt │ │ └── widget │ │ ├── CustomToast.kt │ │ ├── LoadingDialog.kt │ │ └── OnNoDoubleClickListener.kt │ └── res │ ├── drawable │ ├── bg_loading_dialog.xml │ └── bg_toast_custom.xml │ ├── layout │ ├── activity_base_title.xml │ ├── base_toolbar.xml │ ├── layout_loading_dialog.xml │ └── toast_custom.xml │ ├── values │ ├── colors.xml │ ├── dimen.xml │ ├── ids.xml │ ├── strings.xml │ └── styles.xml │ └── xml │ ├── file_paths.xml │ └── network_security_config.xml ├── bintray.gradle ├── build.gradle ├── config.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── publish-mavencentral.gradle └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kotlin 版 MVP 基础框架 2 | 3 | - **项目 [https://github.com/iceCola7/WanAndroid](https://github.com/iceCola7/WanAndroid) 已经使用该框架** 4 | - **Java 版 MVP 基础框架:[https://github.com/iceCola7/MVPSamples](https://github.com/iceCola7/MVPSamples)** 5 | - **Kotlin 版 MVP 基础框架:[https://github.com/iceCola7/KotlinMVPSamples](https://github.com/iceCola7/KotlinMVPSamples)** 6 | 7 | ## 简介 8 | 9 | > 快速搭建 Kotlin+MVP+RxJava+Retrofit+EventBus 的框架,方便快速开发新项目、减少开发成本,让所写的代码更加简洁,让开发者只需要关注业务的实现。 10 | 11 | ## 代码结构 12 | 13 | ![](/art/base.png) 14 | 15 | #### 1. View 层的基类封装 16 | 17 | - `BaseActivity` 、 `BaseFragment` 是抽象类,封装了布局文件 `ID` 、初始化 `View` 、初始化数据、开始请求、是否使用 `EventBus` 、状态栏等; 18 | - `BaseMvpActivity` 、 `BaseMvpFragment` 分别继承 `BaseActivity` 和 `BaseFragment` 并实现了 `IView` 接口,将 `MVP` 基础架构封装起来;注:如果想使用 `MVP` 架构就继承 `BaseMvpActivity` 或者 `BaseMvpFragment` ,如果不适用 `MVP` 架构就继承 `BaseActivity` 或者 `BaseFragment` ; 19 | - `BaseMvpTitleActivity` 继承 `BaseMvpActivity` ,简单了封装了 `Toolbar`,可扩展 。 20 | 21 | #### 2. ext 相关的封装(主要用到了 Kotlin 扩展函数) 22 | 23 | - 封装 `loge` 、 `showToast` 、 `showSnackMsg` 、`ss` 、 `sss` 等通用方法,项目中可以直接调用; 24 | - `ss` 、 `sss` ,这两个方法主要是对网络请求的统一封装,使用起来非常方便(**亮点**); 25 | ``` 26 | mModel?.getBanners()?.ss(mModel, mView) { 27 | mView?.showBanners(it.data) 28 | } 29 | addDisposable( 30 | mModel?.getBanners()?.sss(mView) { 31 | mView?.showBanners(it.data) 32 | } 33 | ) 34 | ``` 35 | 36 | > 这里贴上 `ss` 和 `sss` 方法的代码(这两个方法用起来真的太爽了): 37 | 38 | ``` 39 | fun Observable.ss( 40 | model: IModel?, 41 | view: IView?, 42 | isShowLoading: Boolean = true, 43 | onSuccess: (T) -> Unit 44 | ) { 45 | this.compose(SchedulerUtils.ioToMain()) 46 | .retryWhen(RetryWithDelay()) 47 | .subscribe(object : Observer { 48 | override fun onComplete() { 49 | view?.hideLoading() 50 | } 51 | override fun onSubscribe(d: Disposable) { 52 | if (isShowLoading) { 53 | view?.showLoading() 54 | } 55 | model?.addDisposable(d) 56 | if (!NetWorkUtil.isConnected()) { 57 | view?.showDefaultMsg("当前网络不可用,请检查网络设置") 58 | d.dispose() 59 | onComplete() 60 | } 61 | } 62 | override fun onNext(t: T) { 63 | when { 64 | t.errorCode == ErrorStatus.SUCCESS -> onSuccess.invoke(t) 65 | t.errorCode == ErrorStatus.TOKEN_INVAILD -> { 66 | // Token 过期,重新登录 67 | } 68 | else -> view?.showDefaultMsg(t.errorMsg) 69 | } 70 | } 71 | override fun onError(t: Throwable) { 72 | view?.hideLoading() 73 | view?.showError(ExceptionHandle.handleException(t)) 74 | } 75 | }) 76 | } 77 | ``` 78 | ``` 79 | fun Observable.sss( 80 | view: IView?, 81 | isShowLoading: Boolean = true, 82 | onSuccess: (T) -> Unit 83 | ): Disposable { 84 | if (isShowLoading) { 85 | view?.showLoading() 86 | } 87 | return this.compose(SchedulerUtils.ioToMain()) 88 | .retryWhen(RetryWithDelay()) 89 | .subscribe({ 90 | when { 91 | it.errorCode == ErrorStatus.SUCCESS -> onSuccess.invoke(it) 92 | it.errorCode == ErrorStatus.TOKEN_INVAILD -> { 93 | // Token 过期,重新登录 94 | } 95 | else -> view?.showDefaultMsg(it.errorMsg) 96 | } 97 | view?.hideLoading() 98 | }, { 99 | view?.hideLoading() 100 | view?.showError(ExceptionHandle.handleException(it)) 101 | }) 102 | } 103 | ``` 104 | 105 | #### 3. 网络通讯通用类 106 | 107 | - 封装 `RetrofitFactory` 来构建不同 `baseUrl` 的 `RetrofitService` (**注:项目中 baseUrl 很多的情况下,不建议使用,建议重新封装**) ; 108 | - 封装 `cookie` 相关、统一的异常处理、 `CacheInterceptor` 、 `HeaderInterceptor` 、 `SaveCookieInterceptor` 等; 109 | - 封装 请求重连 操作,详情请见类 `RetryWithDelay` 。 110 | 111 | ``` 112 | object MainRetrofit : RetrofitFactory() { 113 | override fun baseUrl(): String = Constant.BASE_URL 114 | override fun getService(): Class = MainApi::class.java 115 | } 116 | interface MainApi { 117 | @GET("/banner/json") 118 | fun getHomeBanner(): Observable>> 119 | } 120 | ``` 121 | 122 | #### 4. MVP 基础架构 123 | 124 | **MVP 即是 Model , View , Presenter 三层,把 Activity 中的 UI 逻辑抽象成 View 接口,把业务逻辑抽象成 Presenter 接口, Model 类还是原来的 Model ,实现了 Model 层和 View 层完全解耦。** 125 | 126 | > 用户触发 V 层事件, V 层把事件通知 P 层, P 层通知 M 层处理这个事件, M 层处理完之后把结果发送给 P 层, P 层再发送给 V 层,最后 V 层做出相应的处理,这是 MVP 架构的一整套流程。 127 | 128 | - M 层:模型层,负责数据的请求、解析、过滤等操作; 129 | - V 层:视图层,负责视图部分展示、视图时间处理, `Activity` 、 `Fragment` 、 `Dialog` 、 `ViewGroup` 等呈现视图的组件都可以承担该角色; 130 | - P 层:模型层和视图层交互的桥梁。 131 | 132 | ##### MVP 架构的优缺点 133 | 134 | **A. 优点** 135 | 136 | - 模块职责划分明显,层次清晰,接口功能清晰; 137 | 138 | - Model层和View层分离,解耦.修改 `View` 而不影响 `Model` ; 139 | 140 | - 功能复用度高,方便.一个 `Presenter` 可以复用于多个 `View` ,而不用更改 `Presenter` 的逻辑; 141 | 142 | - 有利于测试驱动开发,以前的 `Android` 开发是难以进行单元测试; 143 | 144 | - 如果后台接口还未写好,但已知返回数据类型的情况下,完全可以写出此接口完整的功能。 145 | 146 | **B. 缺点** 147 | 148 | - `MVP` 中接口过多; 149 | 150 | - 每一个功能,相比于 `MVC` 要多写好几个文件; 151 | 152 | - 如果某一个界面中需要请求多个服务器接口,这个界面文件中会实现很多的回调接口,导致代码繁杂; 153 | 154 | - 如果更改了数据源和请求中参数,会导致更多的代码修改; 155 | 156 | - 额外的代码复杂度及学习成本。 157 | 158 | #### 5. rx 相关 159 | 160 | - 封装 `SchedulerUtils` 工具类、 `IoMainScheduler` 等; 161 | - `BaseObserver` 和 `BaseSubscriber` 对 `ResourceObserver` 和 `ResourceSubscriber` 通用封装。 162 | 163 | #### 6. 工具类和帮助类 164 | 165 | - 封装 `Preference` 类,主要采用了 `kotlin` 委托属性和 `SharedPreference` 的实例; 166 | - 封装 `StatusBarUtil` (适配状态栏) 、 `KeyBoardUtil` (键盘相关) 、 `NetWorkUtil` (网络相关) 、 `RomUtil` (手机ROM相关) 、 `FileProvider7` (7.0手机文件适配) 等。 167 | 168 | #### 7. 自定义控件 169 | - 封装 `Toast` 、 `LoadingView` 和 `OnNoDoubleClickListener` (防止连续点击)。 170 | 171 | ## 如何使用? 172 | 173 | > 只需要五步就可以实现 MVP 架构。 174 | 175 | ##### 第一步:导入baselibs库 176 | 177 | > `Clone or Download` 后导入 `baselibs` 库,再根据需求自行修改即可。 178 | 179 | ##### 第二步:定义一个Contract 180 | 181 | 需要定义一个 `Contract` 接口,来抽象 `View`、`Presenter`、`Model` 的方法,并且它们需要分别继承 `IView`、`IPresenter`、`IModel` 接口。 182 | > 案例: MainContract 183 | 184 | ``` 185 | interface MainContract { 186 | interface View : IView { 187 | fun showBanners(banners: MutableList) 188 | } 189 | interface Presenter : IPresenter { 190 | fun getBanner2() 191 | } 192 | interface Model : IModel { 193 | fun getBanners(): Observable>> 194 | } 195 | } 196 | ``` 197 | 198 | ##### 第三步:创建一个Model接口的实现类,需要继承 BaseModel 类 并实现Model接口 199 | > 案例: MainModel 200 | 201 | ``` 202 | class MainModel : BaseModel(), MainContract.Model { 203 | override fun getBanners(): Observable>> { 204 | return MainRetrofit.service.getHomeBanner() 205 | } 206 | } 207 | ``` 208 | 209 | ##### 第四步:创建一个Presenter接口的实现类,需要继承 BasePresenter 并实现 MainContract.Presenter 接口 210 | 211 | > 案例: MainPresenter 212 | 213 | ``` 214 | class MainPresenter : BasePresenter(), MainContract.Presenter { 215 | override fun createModel(): MainContract.Model? = MainModel() 216 | override fun getBanner2() { 217 | mModel?.getBanners()?.ss(mModel, mView) { 218 | mView?.showBanners(it.data) 219 | } 220 | } 221 | } 222 | ``` 223 | 224 | ##### 第五步:创建一个View的接口实现类,这是一个Activity或者Fragment,需要继承BaseMvpActivity> 或者 BaseMvpFragment> 并实现 View 接口 225 | > 案例:MainActivity 226 | 227 | ``` 228 | class MainActivity : BaseMvpTitleActivity(), MainContract.View { 229 | override fun attachChildLayoutRes(): Int = R.layout.activity_main 230 | override fun createPresenter(): MainContract.Presenter = MainPresenter() 231 | override fun initView() { 232 | } 233 | override fun initData() { 234 | } 235 | override fun start() { 236 | mPresenter?.getBanner2() 237 | } 238 | override fun showBanners(banners: MutableList) { 239 | tv_result.text = banners.toString() 240 | } 241 | } 242 | ``` 243 | 244 | **至此, `Kotlin` 基础框架已经搭建完成,如有错误之处还请指正。** 245 | 246 | ## 最后 247 | 248 | 完整的项目地址:**[https://github.com/iceCola7/KotlinMVPSamples](https://github.com/iceCola7/KotlinMVPSamples)**。 249 | 250 | ## LICENSE 251 | 252 | ``` 253 | Copyright 2018 iceCola7 254 | 255 | Licensed under the Apache License, Version 2.0 (the "License"); 256 | you may not use this file except in compliance with the License. 257 | You may obtain a copy of the License at 258 | 259 | http://www.apache.org/licenses/LICENSE-2.0 260 | 261 | Unless required by applicable law or agreed to in writing, software 262 | distributed under the License is distributed on an "AS IS" BASIS, 263 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 264 | See the License for the specific language governing permissions and 265 | limitations under the License. 266 | ``` -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | apply plugin: 'kotlin-kapt' 5 | 6 | android { 7 | compileSdkVersion rootProject.ext.android.compileSdkVersion 8 | defaultConfig { 9 | applicationId "com.cxz.kotlin.samples" 10 | minSdkVersion rootProject.ext.android.minSdkVersion 11 | targetSdkVersion rootProject.ext.android.targetSdkVersion 12 | versionCode rootProject.ext.android.versionCode 13 | versionName rootProject.ext.android.versionName 14 | testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' 15 | 16 | multiDexEnabled true 17 | 18 | } 19 | buildTypes { 20 | debug { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 23 | } 24 | 25 | release { 26 | minifyEnabled false 27 | // shrinkResources true 28 | // zipAlignEnabled true 29 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 30 | } 31 | } 32 | 33 | compileOptions { 34 | sourceCompatibility JavaVersion.VERSION_1_8 35 | targetCompatibility JavaVersion.VERSION_1_8 36 | } 37 | 38 | kotlinOptions { 39 | jvmTarget = JavaVersion.VERSION_1_8 40 | } 41 | 42 | androidExtensions { 43 | experimental = true 44 | } 45 | } 46 | 47 | dependencies { 48 | implementation fileTree(include: ['*.jar'], dir: 'libs') 49 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 50 | testImplementation rootProject.ext.testDeps["junit"] 51 | androidTestImplementation rootProject.ext.testDeps["runner"] 52 | androidTestImplementation rootProject.ext.testDeps["espresso-core"] 53 | 54 | // leakCanary 55 | debugImplementation rootProject.ext.testDeps["leakcanary-debug"] 56 | releaseImplementation rootProject.ext.testDeps["leakcanary-release"] 57 | 58 | implementation project(':baselibs') 59 | // implementation 'io.github.iceCola7:kotlin-mvp:1.0.4@aar' 60 | // implementation 'com.cxz:kotlin-mvp:1.0.2' 61 | // qmui 62 | implementation 'com.qmuiteam:qmui:1.2.0' 63 | } 64 | -------------------------------------------------------------------------------- /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. 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/src/androidTest/java/com/cxz/kotlin/samples/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.cxz.kotlin.samples 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.getTargetContext() 22 | assertEquals("com.cxz.kotlin.samples", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/cxz/kotlin/samples/app/App.kt: -------------------------------------------------------------------------------- 1 | package com.cxz.kotlin.samples.app 2 | 3 | import android.app.Activity 4 | import android.app.Application 5 | import android.content.Context 6 | import android.os.Bundle 7 | import androidx.multidex.MultiDex 8 | import com.alibaba.android.arouter.launcher.ARouter 9 | import com.cxz.kotlin.baselibs.config.AppConfig 10 | import com.cxz.kotlin.baselibs.utils.NLog 11 | import com.squareup.leakcanary.LeakCanary 12 | import com.squareup.leakcanary.RefWatcher 13 | 14 | /** 15 | * @author admin 16 | * @date 2018/11/21 17 | * @desc 18 | */ 19 | class App : Application() { 20 | 21 | private val TAG = "App" 22 | 23 | private var refWatcher: RefWatcher? = null 24 | 25 | companion object { 26 | fun getRefWatcher(context: Context): RefWatcher? { 27 | val app = context.applicationContext as App 28 | return app.refWatcher 29 | } 30 | } 31 | 32 | override fun onCreate() { 33 | super.onCreate() 34 | initApp() 35 | initLeakCanary() 36 | initRouter() 37 | } 38 | 39 | private fun initApp() { 40 | AppConfig.init(this) 41 | AppConfig.openDebug() 42 | } 43 | 44 | private fun initLeakCanary() { 45 | refWatcher = if (LeakCanary.isInAnalyzerProcess(this)) 46 | RefWatcher.DISABLED 47 | else LeakCanary.install(this) 48 | 49 | registerActivityLifecycleCallbacks(mActivityLifecycleCallbacks) 50 | } 51 | 52 | private fun initRouter() { 53 | ARouter.openLog() 54 | ARouter.openDebug() 55 | ARouter.init(this) 56 | } 57 | 58 | override fun attachBaseContext(base: Context?) { 59 | super.attachBaseContext(base) 60 | MultiDex.install(this) 61 | } 62 | 63 | private val mActivityLifecycleCallbacks = object : ActivityLifecycleCallbacks { 64 | override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { 65 | NLog.d(TAG, "onCreated: " + activity.componentName.className) 66 | } 67 | 68 | override fun onActivityStarted(activity: Activity) { 69 | NLog.d(TAG, "onStart: " + activity.componentName.className) 70 | } 71 | 72 | override fun onActivityResumed(activity: Activity) { 73 | NLog.d(TAG, "onResume: " + activity.componentName.className) 74 | } 75 | 76 | override fun onActivityPaused(activity: Activity) { 77 | NLog.d(TAG, "onPause: " + activity.componentName.className) 78 | } 79 | 80 | override fun onActivityStopped(activity: Activity) { 81 | NLog.d(TAG, "onStop: " + activity.componentName.className) 82 | } 83 | 84 | override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) { 85 | NLog.d(TAG, "onSaveInstanceState: " + activity.componentName.className) 86 | } 87 | 88 | override fun onActivityDestroyed(activity: Activity) { 89 | NLog.d(TAG, "onDestroy: " + activity.componentName.className) 90 | } 91 | } 92 | 93 | } -------------------------------------------------------------------------------- /app/src/main/java/com/cxz/kotlin/samples/bean/DataRepo.kt: -------------------------------------------------------------------------------- 1 | package com.cxz.kotlin.samples.bean 2 | 3 | import com.cxz.kotlin.baselibs.bean.BaseBean 4 | import com.squareup.moshi.Json 5 | import com.squareup.moshi.JsonClass 6 | 7 | /** 8 | * @author admin 9 | * @date 2018/11/21 10 | * @desc 11 | */ 12 | 13 | //data class HttpResult( 14 | // @Json(name = "data") val data: T, 15 | // @Json(name = "errorCode") val errorCode: Int, 16 | // @Json(name = "errorMsg") val errorMsg: String 17 | //) 18 | 19 | data class HttpResult( 20 | @Json(name = "data") val data: T 21 | ) : BaseBean() 22 | 23 | //轮播图 24 | data class Banner( 25 | @Json(name = "desc") val desc: String, 26 | @Json(name = "id") val id: Int, 27 | @Json(name = "imagePath") val imagePath: String, 28 | @Json(name = "isVisible") val isVisible: Int, 29 | @Json(name = "order") val order: Int, 30 | @Json(name = "title") val title: String, 31 | @Json(name = "type") val type: Int, 32 | @Json(name = "url") val url: String 33 | ) 34 | 35 | // 登录数据 36 | data class LoginData( 37 | @Json(name = "chapterTops") val chapterTops: MutableList, 38 | @Json(name = "collectIds") val collectIds: MutableList, 39 | @Json(name = "email") val email: String, 40 | @Json(name = "icon") val icon: String, 41 | @Json(name = "id") val id: Int, 42 | @Json(name = "password") val password: String, 43 | @Json(name = "token") val token: String, 44 | @Json(name = "type") val type: Int, 45 | @Json(name = "username") val username: String 46 | ) 47 | 48 | data class CollectionResponseBody( 49 | @Json(name = "curPage") val curPage: Int, 50 | @Json(name = "datas") val datas: List, 51 | @Json(name = "offset") val offset: Int, 52 | @Json(name = "over") val over: Boolean, 53 | @Json(name = "pageCount") val pageCount: Int, 54 | @Json(name = "size") val size: Int, 55 | @Json(name = "total") val total: Int 56 | ) 57 | 58 | data class CollectionArticle( 59 | @Json(name = "author") val author: String, 60 | @Json(name = "chapterId") val chapterId: Int, 61 | @Json(name = "chapterName") val chapterName: String, 62 | @Json(name = "courseId") val courseId: Int, 63 | @Json(name = "desc") val desc: String, 64 | @Json(name = "envelopePic") val envelopePic: String, 65 | @Json(name = "id") val id: Int, 66 | @Json(name = "link") val link: String, 67 | @Json(name = "niceDate") val niceDate: String, 68 | @Json(name = "origin") val origin: String, 69 | @Json(name = "originId") val originId: Int, 70 | @Json(name = "publishTime") val publishTime: Long, 71 | @Json(name = "title") val title: String, 72 | @Json(name = "userId") val userId: Int, 73 | @Json(name = "visible") val visible: Int, 74 | @Json(name = "zan") val zan: Int 75 | ) 76 | -------------------------------------------------------------------------------- /app/src/main/java/com/cxz/kotlin/samples/constant/Constant.kt: -------------------------------------------------------------------------------- 1 | package com.cxz.kotlin.samples.constant 2 | 3 | /** 4 | * @author admin 5 | * @date 2018/11/21 6 | * @desc 7 | */ 8 | 9 | object Constant{ 10 | 11 | const val BASE_URL = "https://www.wanandroid.com" 12 | 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/cxz/kotlin/samples/ext/Ext.kt: -------------------------------------------------------------------------------- 1 | package com.cxz.kotlin.samples.ext 2 | 3 | /** 4 | * @author admin 5 | * @date 2018/11/21 6 | * @desc 7 | */ 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/cxz/kotlin/samples/http/MainApi.kt: -------------------------------------------------------------------------------- 1 | package com.cxz.kotlin.samples.http 2 | 3 | import com.cxz.kotlin.samples.bean.Banner 4 | import com.cxz.kotlin.samples.bean.CollectionArticle 5 | import com.cxz.kotlin.samples.bean.CollectionResponseBody 6 | import com.cxz.kotlin.samples.bean.HttpResult 7 | import io.reactivex.rxjava3.core.Observable 8 | import retrofit2.http.* 9 | 10 | interface MainApi { 11 | 12 | @GET("banner/json") 13 | fun getHomeBanner(): Observable>> 14 | 15 | /** 16 | * 登录 17 | */ 18 | @POST("user/login") 19 | @FormUrlEncoded 20 | fun login(@Field("username") username: String, @Field("password") password: String) 21 | : Observable> 22 | 23 | /** 24 | * 退出登录 25 | */ 26 | @GET("user/logout/json") 27 | fun logout(): Observable> 28 | 29 | /** 30 | * 轮播列表数据 31 | */ 32 | @GET("banner/json") 33 | fun getBannerList(): Observable>> 34 | 35 | /** 36 | * 收藏列表数据 37 | */ 38 | @GET("lg/collect/list/{page}/json") 39 | fun getCollectList(@Path("page") page: Int) 40 | : Observable>> 41 | 42 | } -------------------------------------------------------------------------------- /app/src/main/java/com/cxz/kotlin/samples/http/MainRetrofit.kt: -------------------------------------------------------------------------------- 1 | package com.cxz.kotlin.samples.http 2 | 3 | import com.cxz.kotlin.baselibs.http.RetrofitFactory 4 | import com.cxz.kotlin.samples.constant.Constant 5 | 6 | /** 7 | * @author chenxz 8 | * @date 2018/11/21 9 | * @desc 10 | */ 11 | object MainRetrofit : RetrofitFactory() { 12 | 13 | override fun baseUrl(): String = Constant.BASE_URL 14 | 15 | override fun getService(): Class = MainApi::class.java 16 | 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/cxz/kotlin/samples/mvp/contract/MainContract.kt: -------------------------------------------------------------------------------- 1 | package com.cxz.kotlin.samples.mvp.contract 2 | 3 | import com.cxz.kotlin.baselibs.mvp.IModel 4 | import com.cxz.kotlin.baselibs.mvp.IPresenter 5 | import com.cxz.kotlin.baselibs.mvp.IView 6 | import com.cxz.kotlin.samples.bean.Banner 7 | import com.cxz.kotlin.samples.bean.CollectionArticle 8 | import com.cxz.kotlin.samples.bean.CollectionResponseBody 9 | import com.cxz.kotlin.samples.bean.HttpResult 10 | import io.reactivex.rxjava3.core.Observable 11 | 12 | /** 13 | * @author admin 14 | * @date 2018/11/20 15 | * @desc 16 | */ 17 | interface MainContract { 18 | 19 | interface View : IView { 20 | fun showBanners(banners: MutableList) 21 | 22 | fun loginSuccess() 23 | 24 | fun showBannerList(bannerList: MutableList) 25 | 26 | fun showCollectList(collectionResponseBody: CollectionResponseBody) 27 | 28 | fun logoutSuccess() 29 | } 30 | 31 | interface Presenter : IPresenter { 32 | fun getBanner() 33 | fun getBanner2() 34 | fun getBanner3() 35 | 36 | fun login(username: String, password: String) 37 | fun getBannerList() 38 | fun getCollectList(page: Int) 39 | fun logout() 40 | } 41 | 42 | interface Model : IModel { 43 | fun getBanners(): Observable>> 44 | 45 | fun login(username: String, password: String): Observable> 46 | fun getBannerList(): Observable>> 47 | fun getCollectList(page: Int): Observable>> 48 | fun logout(): Observable> 49 | 50 | } 51 | 52 | } -------------------------------------------------------------------------------- /app/src/main/java/com/cxz/kotlin/samples/mvp/contract/TestContract.kt: -------------------------------------------------------------------------------- 1 | package com.cxz.kotlin.samples.mvp.contract 2 | 3 | import com.cxz.kotlin.baselibs.mvp.IModel 4 | import com.cxz.kotlin.baselibs.mvp.IPresenter 5 | import com.cxz.kotlin.baselibs.mvp.IView 6 | 7 | /** 8 | * @author chenxz 9 | * @date 2018/12/1 10 | * @desc 11 | */ 12 | interface TestContract { 13 | 14 | interface View : IView { 15 | 16 | } 17 | 18 | interface Presenter : IPresenter { 19 | 20 | } 21 | 22 | interface Model : IModel { 23 | 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /app/src/main/java/com/cxz/kotlin/samples/mvp/model/MainModel.kt: -------------------------------------------------------------------------------- 1 | package com.cxz.kotlin.samples.mvp.model 2 | 3 | import com.cxz.kotlin.baselibs.mvp.BaseModel 4 | import com.cxz.kotlin.samples.bean.Banner 5 | import com.cxz.kotlin.samples.bean.CollectionArticle 6 | import com.cxz.kotlin.samples.bean.CollectionResponseBody 7 | import com.cxz.kotlin.samples.bean.HttpResult 8 | import com.cxz.kotlin.samples.http.MainRetrofit 9 | import com.cxz.kotlin.samples.mvp.contract.MainContract 10 | import io.reactivex.rxjava3.core.Observable 11 | 12 | /** 13 | * @author admin 14 | * @date 2018/11/20 15 | * @desc 16 | */ 17 | class MainModel : BaseModel(), MainContract.Model { 18 | 19 | override fun getBanners(): Observable>> { 20 | return MainRetrofit.service.getHomeBanner() 21 | } 22 | 23 | override fun login(username: String, password: String): Observable> { 24 | return MainRetrofit.service.login(username, password) 25 | } 26 | 27 | override fun getBannerList(): Observable>> { 28 | return MainRetrofit.service.getBannerList() 29 | } 30 | 31 | override fun getCollectList(page: Int): Observable>> { 32 | return MainRetrofit.service.getCollectList(page) 33 | } 34 | 35 | override fun logout(): Observable> { 36 | return MainRetrofit.service.logout() 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /app/src/main/java/com/cxz/kotlin/samples/mvp/model/TestModel.kt: -------------------------------------------------------------------------------- 1 | package com.cxz.kotlin.samples.mvp.model 2 | 3 | import com.cxz.kotlin.baselibs.mvp.BaseModel 4 | import com.cxz.kotlin.samples.mvp.contract.TestContract 5 | 6 | /** 7 | * @author chenxz 8 | * @date 2018/12/1 9 | * @desc 10 | */ 11 | class TestModel : BaseModel(), TestContract.Model { 12 | 13 | } -------------------------------------------------------------------------------- /app/src/main/java/com/cxz/kotlin/samples/mvp/presenter/MainPresenter.kt: -------------------------------------------------------------------------------- 1 | package com.cxz.kotlin.samples.mvp.presenter 2 | 3 | import com.cxz.kotlin.baselibs.ext.ss 4 | import com.cxz.kotlin.baselibs.ext.sss 5 | import com.cxz.kotlin.baselibs.http.HttpStatus 6 | import com.cxz.kotlin.baselibs.http.exception.ExceptionHandle 7 | import com.cxz.kotlin.baselibs.mvp.BasePresenter 8 | import com.cxz.kotlin.baselibs.rx.SchedulerUtils 9 | import com.cxz.kotlin.samples.mvp.contract.MainContract 10 | import com.cxz.kotlin.samples.mvp.model.MainModel 11 | 12 | /** 13 | * @author admin 14 | * @date 2018/11/20 15 | * @desc 16 | */ 17 | class MainPresenter : BasePresenter(), MainContract.Presenter { 18 | 19 | override fun createModel(): MainContract.Model? = MainModel() 20 | 21 | override fun getBanner() { 22 | mView?.showLoading() 23 | addDisposable(mModel?.getBanners() 24 | ?.compose(SchedulerUtils.ioToMain()) 25 | ?.subscribe({ 26 | mView?.hideLoading() 27 | when { 28 | it.errorCode == HttpStatus.SUCCESS -> mView?.showBanners(it.data) 29 | it.errorCode == HttpStatus.TOKEN_INVALID -> { 30 | // Token 过期,重新登录 31 | } 32 | else -> mView?.showError(it.errorMsg) 33 | } 34 | }, { 35 | mView?.hideLoading() 36 | mView?.showDefaultMsg(ExceptionHandle.handleException(it)) 37 | }) 38 | ) 39 | } 40 | 41 | override fun getBanner2() { 42 | mModel?.getBanners()?.ss(mModel, mView, onSuccess = { 43 | mView?.showBanners(it.data) 44 | }) 45 | } 46 | 47 | override fun getBanner3() { 48 | addDisposable(mModel?.getBanners()?.sss(mView, onSuccess = { 49 | mView?.showBanners(it.data) 50 | })) 51 | } 52 | 53 | override fun login(username: String, password: String) { 54 | mModel?.login(username, password)?.ss(mModel, mView, onSuccess = { 55 | mView?.loginSuccess() 56 | }) 57 | } 58 | 59 | override fun getBannerList() { 60 | mModel?.getBannerList()?.ss(mModel, mView, onSuccess = { 61 | mView?.showBannerList(it.data) 62 | }) 63 | } 64 | 65 | override fun getCollectList(page: Int) { 66 | mModel?.getCollectList(page)?.ss(mModel, mView, onSuccess = { 67 | mView?.showCollectList(it.data) 68 | }) 69 | } 70 | 71 | override fun logout() { 72 | mModel?.logout()?.ss(mModel, mView, onSuccess = { 73 | mView?.logoutSuccess() 74 | }) 75 | } 76 | 77 | } -------------------------------------------------------------------------------- /app/src/main/java/com/cxz/kotlin/samples/mvp/presenter/TestPresenter.kt: -------------------------------------------------------------------------------- 1 | package com.cxz.kotlin.samples.mvp.presenter 2 | 3 | import com.cxz.kotlin.baselibs.mvp.BasePresenter 4 | import com.cxz.kotlin.samples.mvp.contract.TestContract 5 | import com.cxz.kotlin.samples.mvp.model.TestModel 6 | 7 | /** 8 | * @author chenxz 9 | * @date 2018/12/1 10 | * @desc 11 | */ 12 | class TestPresenter : BasePresenter(), TestContract.Presenter { 13 | 14 | override fun createModel(): TestContract.Model? = TestModel() 15 | 16 | } -------------------------------------------------------------------------------- /app/src/main/java/com/cxz/kotlin/samples/ui/activity/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.cxz.kotlin.samples.ui.activity 2 | 3 | import android.text.TextUtils 4 | import android.view.View 5 | import com.bumptech.glide.Glide 6 | import com.cxz.kotlin.baselibs.base.BaseMvpTitleActivity 7 | import com.cxz.kotlin.baselibs.ext.setSingleClickListener 8 | import com.cxz.kotlin.samples.R 9 | import com.cxz.kotlin.samples.bean.Banner 10 | import com.cxz.kotlin.samples.bean.CollectionArticle 11 | import com.cxz.kotlin.samples.bean.CollectionResponseBody 12 | import com.cxz.kotlin.samples.mvp.contract.MainContract 13 | import com.cxz.kotlin.samples.mvp.presenter.MainPresenter 14 | import com.cxz.kotlin.samples.utils.DialogUtil 15 | import com.cxz.kotlin.samples.utils.PermissionHelper 16 | import kotlinx.android.synthetic.main.activity_main.* 17 | 18 | class MainActivity : BaseMvpTitleActivity(), 19 | MainContract.View { 20 | 21 | private val mDialog by lazy { 22 | DialogUtil.getWaitDialog(this, "正在加载") 23 | } 24 | 25 | override fun attachChildLayoutRes(): Int = R.layout.activity_main 26 | 27 | override fun createPresenter(): MainContract.Presenter = MainPresenter() 28 | 29 | override fun hasBackIcon(): Boolean = true 30 | 31 | override fun showLoading() { 32 | mDialog.show() 33 | } 34 | 35 | override fun hideLoading() { 36 | mDialog.dismiss() 37 | } 38 | 39 | override fun initView() { 40 | setStatusBarColor(resources.getColor(R.color.colorPrimary)) 41 | setStatusBarIcon(false) 42 | super.initView() 43 | setBaseTitleColor(android.R.color.white) 44 | setBaseTitle("Main") 45 | } 46 | 47 | override fun initData() { 48 | btn_login.setSingleClickListener { 49 | val username = et_username.text.toString() 50 | val password = et_password.text.toString() 51 | if (TextUtils.isEmpty(username)) { 52 | showDefaultMsg("账号不能为空") 53 | return@setSingleClickListener 54 | } 55 | if (TextUtils.isEmpty(password)) { 56 | showDefaultMsg("密码不能为空") 57 | return@setSingleClickListener 58 | } 59 | mPresenter?.login(username, password) 60 | } 61 | btn_logout.setSingleClickListener { 62 | mPresenter?.logout() 63 | } 64 | btn_get_banner.setSingleClickListener { 65 | tv_result.text = "" 66 | imageView.visibility = View.VISIBLE 67 | mPresenter?.getBannerList() 68 | } 69 | btn_collect.setSingleClickListener { 70 | tv_result.text = "" 71 | imageView.visibility = View.GONE 72 | mPresenter?.getCollectList(0) 73 | } 74 | btn_permission.setSingleClickListener { 75 | PermissionHelper.requestCameraPermission(this) { 76 | showDefaultMsg("相机权限申请成功") 77 | } 78 | // rxPermissions.request(Manifest.permission.READ_EXTERNAL_STORAGE) 79 | // .subscribe { 80 | // if (it) { 81 | // showDefaultMsg("已允许") 82 | // } else { 83 | // showDefaultMsg("未允许") 84 | // } 85 | // } 86 | } 87 | } 88 | 89 | override fun start() { 90 | } 91 | 92 | override fun showBanners(banners: MutableList) { 93 | tv_result.text = banners.toString() 94 | } 95 | 96 | override fun loginSuccess() { 97 | showDefaultMsg("登录成功") 98 | } 99 | 100 | override fun showBannerList(bannerList: MutableList) { 101 | if (bannerList.size > 0) { 102 | tv_result.text = bannerList[0].title 103 | Glide.with(this).load(bannerList[0].imagePath).into(imageView) 104 | } 105 | } 106 | 107 | override fun showCollectList(collectionResponseBody: CollectionResponseBody) { 108 | if (collectionResponseBody.datas.isNotEmpty()) { 109 | tv_result.text = collectionResponseBody.datas[0].title 110 | } 111 | } 112 | 113 | override fun logoutSuccess() { 114 | showDefaultMsg("已退出登录") 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /app/src/main/java/com/cxz/kotlin/samples/ui/fragment/TestFragment.kt: -------------------------------------------------------------------------------- 1 | package com.cxz.kotlin.samples.ui.fragment 2 | 3 | import com.cxz.kotlin.baselibs.base.BaseMvpFragment 4 | import com.cxz.kotlin.samples.R 5 | import com.cxz.kotlin.samples.mvp.contract.TestContract 6 | import com.cxz.kotlin.samples.mvp.presenter.TestPresenter 7 | 8 | /** 9 | * @author chenxz 10 | * @date 2018/11/30 11 | * @desc 12 | */ 13 | class TestFragment : BaseMvpFragment(), TestContract.View { 14 | 15 | override fun createPresenter(): TestContract.Presenter = TestPresenter() 16 | 17 | override fun attachLayoutRes(): Int = R.layout.fragment_test 18 | 19 | override fun lazyLoad() { 20 | } 21 | } -------------------------------------------------------------------------------- /app/src/main/java/com/cxz/kotlin/samples/utils/DialogUtil.kt: -------------------------------------------------------------------------------- 1 | package com.cxz.kotlin.samples.utils 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Context 5 | import com.qmuiteam.qmui.widget.dialog.QMUITipDialog 6 | import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers 7 | import io.reactivex.rxjava3.core.Observable 8 | import io.reactivex.rxjava3.schedulers.Schedulers 9 | import java.util.concurrent.TimeUnit 10 | 11 | /** 12 | * @author admin 13 | * @date 2018/11/22 14 | * @desc 15 | */ 16 | object DialogUtil { 17 | 18 | /** 19 | * 获取提示 Dialog 20 | * 21 | * @param context 22 | * @param message 23 | * @return 24 | */ 25 | fun getTipDialog(context: Context, message: String): QMUITipDialog { 26 | return QMUITipDialog.Builder(context) 27 | .setTipWord(message) 28 | .create() 29 | } 30 | 31 | /** 32 | * 展示提示,并在 1.5s 后自动关闭 33 | * 34 | * @param context 35 | * @param message 36 | * @return 37 | */ 38 | @SuppressLint("CheckResult") 39 | fun showTipDialog(context: Context, message: String): QMUITipDialog { 40 | val tipDialog = getTipDialog(context, message) 41 | tipDialog.show() 42 | Observable.timer(1500, TimeUnit.MILLISECONDS) 43 | .subscribeOn(Schedulers.io()) 44 | .observeOn(AndroidSchedulers.mainThread()) 45 | .subscribe({ tipDialog.dismiss() }, { }) 46 | return tipDialog 47 | } 48 | 49 | /** 50 | * 获取加载中的 Dialog 51 | * 52 | * @param context 53 | * @param message 54 | * @return 55 | */ 56 | fun getWaitDialog(context: Context, message: String): QMUITipDialog { 57 | return QMUITipDialog.Builder(context) 58 | .setIconType(QMUITipDialog.Builder.ICON_TYPE_LOADING) 59 | .setTipWord(message) 60 | .create() 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /app/src/main/java/com/cxz/kotlin/samples/utils/PermissionHelper.kt: -------------------------------------------------------------------------------- 1 | package com.cxz.kotlin.samples.utils 2 | 3 | import android.Manifest 4 | import android.app.AppOpsManager 5 | import android.content.Context 6 | import android.location.LocationManager 7 | import android.os.Binder 8 | import android.os.Build 9 | import androidx.core.app.AppOpsManagerCompat 10 | import androidx.core.app.AppOpsManagerCompat.noteOp 11 | import androidx.fragment.app.FragmentActivity 12 | import com.cxz.kotlin.samples.widgets.PermissionDialog 13 | import com.tbruyelle.rxpermissions3.RxPermissions 14 | 15 | /** 16 | * @author admin 17 | * @date 2019/10/24 18 | * @desc 权限申请 19 | */ 20 | object PermissionHelper { 21 | 22 | /** 23 | * 申请相机权限 24 | */ 25 | fun requestCameraPermission(activity: FragmentActivity, requestSuccess: (() -> Unit)? = null) { 26 | RxPermissions(activity) 27 | .requestEachCombined(Manifest.permission.CAMERA) 28 | .subscribe { 29 | if (it.granted) { 30 | requestSuccess?.invoke() 31 | } else if (it.shouldShowRequestPermissionRationale) { 32 | showPermissionDialog( 33 | activity, 34 | "为了保证您正常使用此功能,需要获取您的相机使用权限,请允许。", 35 | "去允许", 36 | true, 37 | requestSuccess 38 | ) 39 | } else { 40 | showPermissionDialog( 41 | activity, 42 | "未取得您的相机使用权限,此功能无法使用。请前往应用权限设置打开权限。", 43 | "去打开", 44 | false, 45 | requestSuccess 46 | ) 47 | } 48 | } 49 | } 50 | 51 | /** 52 | * 展示申请相机权限的对话框 53 | */ 54 | private fun showPermissionDialog( 55 | activity: FragmentActivity, 56 | content: String, 57 | rightText: String, 58 | showPermission: Boolean = true, 59 | requestSuccess: (() -> Unit)? = null 60 | ) { 61 | PermissionDialog.newBuilder() 62 | .setTitle("温馨提示") 63 | .setContent(content) 64 | .setRightText(rightText) 65 | .build() 66 | ?.setOnConfirmClickListener { 67 | if (showPermission) { 68 | requestCameraPermission(activity, requestSuccess) 69 | } else { 70 | PermissionPageUtil.gotoPermission(activity) 71 | } 72 | } 73 | ?.show(activity.supportFragmentManager, "permission_dialog") 74 | } 75 | 76 | /** 77 | * 检查系统定位服务权限 78 | */ 79 | fun checkLocServicePermission(activity: FragmentActivity, callback: (() -> Unit)? = null) { 80 | if (!PermissionHelper.isLocServiceEnable(activity)) { // 检测是否开启定位服务 81 | PermissionDialog.newBuilder() 82 | .setTitle("温馨提示") 83 | .setContent("开启定位服务,获取精准定位") 84 | .setRightText("去开启") 85 | .build() 86 | ?.setOnConfirmClickListener { 87 | PermissionPageUtil.gotoLocServicePage(activity) 88 | } 89 | ?.show(activity.supportFragmentManager, "location_dialog") 90 | } else { // 检测用户是否将当前应用的定位权限拒绝 91 | if (callback != null) { 92 | callback?.invoke() 93 | } else { 94 | //其中2代表AppOpsManager.OP_GPS,如果要判断悬浮框权限,第二个参数需换成24即AppOpsManager。OP_SYSTEM_ALERT_WINDOW及,第三个参数需要换成AppOpsManager.OPSTR_SYSTEM_ALERT_WINDOW 95 | val checkResult = checkOp(activity, 2, AppOpsManager.OPSTR_FINE_LOCATION) 96 | val checkResult2 = checkOp(activity, 1, AppOpsManager.OPSTR_FINE_LOCATION) 97 | if (AppOpsManagerCompat.MODE_IGNORED == checkResult || AppOpsManagerCompat.MODE_IGNORED == checkResult2) { 98 | // DlgUtils.showLocIgnoredDialog(this) 99 | } 100 | } 101 | } 102 | } 103 | 104 | /** 105 | * 手机是否开启位置服务,如果没有开启那么所有app将不能使用定位功能 106 | */ 107 | fun isLocServiceEnable(context: Context): Boolean { 108 | val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager 109 | val gps = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) 110 | val network = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER) 111 | return gps || network 112 | } 113 | 114 | /** 115 | * 检查权限列表 116 | * 其中第二个参数2代表AppOpsManager.OP_GPS, 117 | * 如果要判断悬浮框权限,第二个参数需换成24即AppOpsManager.OP_SYSTEM_ALERT_WINDOW及,第三个参数需要换成AppOpsManager.OPSTR_SYSTEM_ALERT_WINDOW 118 | * 119 | * @param context 120 | * @param op 这个值被hide了,去AppOpsManager类源码找,如位置权限 AppOpsManager.OP_GPS==2 121 | * @param opString 如判断定位权限 AppOpsManager.OPSTR_FINE_LOCATION 122 | * @return @see 如果返回值 AppOpsManagerCompat.MODE_IGNORED 表示被禁用了 123 | */ 124 | fun checkOp(context: Context, op: Int, opString: String): Int { 125 | val version = Build.VERSION.SDK_INT 126 | if (version >= 19) { 127 | val obj = context.getSystemService(Context.APP_OPS_SERVICE) 128 | // Object object = context.getSystemService("appops"); 129 | val c = obj.javaClass 130 | try { 131 | val cArg = arrayOfNulls>(3) 132 | cArg[0] = Int::class.javaPrimitiveType 133 | cArg[1] = Int::class.javaPrimitiveType 134 | cArg[2] = String::class.java 135 | val lMethod = c.getDeclaredMethod("checkOp", *cArg) 136 | return lMethod.invoke(obj, op, Binder.getCallingUid(), context.packageName) as Int 137 | } catch (e: Exception) { 138 | e.printStackTrace() 139 | if (Build.VERSION.SDK_INT >= 23) { 140 | return noteOp( 141 | context, 142 | opString, 143 | context.applicationInfo.uid, 144 | context.packageName 145 | ) 146 | } 147 | } 148 | } 149 | return -1 150 | } 151 | 152 | } -------------------------------------------------------------------------------- /app/src/main/java/com/cxz/kotlin/samples/utils/PermissionPageUtil.java: -------------------------------------------------------------------------------- 1 | package com.cxz.kotlin.samples.utils; 2 | 3 | import android.content.ActivityNotFoundException; 4 | import android.content.ComponentName; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.net.Uri; 8 | import android.os.Build; 9 | import android.provider.Settings; 10 | import android.text.TextUtils; 11 | 12 | import com.cxz.kotlin.samples.BuildConfig; 13 | 14 | /** 15 | * @author chenxz 16 | * @date 2019/10/23 17 | * @desc 跳转到手机权限设置页面 18 | */ 19 | public class PermissionPageUtil { 20 | 21 | public static void gotoPermission(Context context) { 22 | String brand = Build.BRAND;//手机厂商 23 | if (TextUtils.equals(brand.toLowerCase(), "redmi") || TextUtils.equals(brand.toLowerCase(), "xiaomi")) { 24 | PermissionPageUtil.gotoMiuiPermission(context); // 小米 25 | } else if (TextUtils.equals(brand.toLowerCase(), "meizu")) { 26 | PermissionPageUtil.gotoMeizuPermission(context); 27 | } else if (TextUtils.equals(brand.toLowerCase(), "huawei") || TextUtils.equals(brand.toLowerCase(), "honor")) { 28 | PermissionPageUtil.gotoHuaweiPermission(context); 29 | } else { 30 | context.startActivity(PermissionPageUtil.getAppDetailSettingIntent(context)); 31 | } 32 | } 33 | 34 | /** 35 | * 跳转到miui的权限管理页面 36 | */ 37 | private static void gotoMiuiPermission(Context context) { 38 | try { // MIUI 8 39 | Intent localIntent = new Intent("miui.intent.action.APP_PERM_EDITOR"); 40 | localIntent.setClassName("com.miui.securitycenter", "com.miui.permcenter.permissions.PermissionsEditorActivity"); 41 | localIntent.putExtra("extra_pkgname", context.getPackageName()); 42 | context.startActivity(localIntent); 43 | } catch (Exception e) { 44 | try { // MIUI 5/6/7 45 | Intent localIntent = new Intent("miui.intent.action.APP_PERM_EDITOR"); 46 | localIntent.setClassName("com.miui.securitycenter", "com.miui.permcenter.permissions.AppPermissionsEditorActivity"); 47 | localIntent.putExtra("extra_pkgname", context.getPackageName()); 48 | context.startActivity(localIntent); 49 | } catch (Exception e1) { // 否则跳转到应用详情 50 | context.startActivity(getAppDetailSettingIntent(context)); 51 | } 52 | } 53 | } 54 | 55 | /** 56 | * 跳转到魅族的权限管理系统 57 | */ 58 | private static void gotoMeizuPermission(Context context) { 59 | try { 60 | Intent intent = new Intent("com.meizu.safe.security.SHOW_APPSEC"); 61 | intent.addCategory(Intent.CATEGORY_DEFAULT); 62 | intent.putExtra("packageName", BuildConfig.APPLICATION_ID); 63 | context.startActivity(intent); 64 | } catch (Exception e) { 65 | e.printStackTrace(); 66 | context.startActivity(getAppDetailSettingIntent(context)); 67 | } 68 | } 69 | 70 | /** 71 | * 华为的权限管理页面 72 | */ 73 | private static void gotoHuaweiPermission(Context context) { 74 | try { 75 | Intent intent = new Intent(); 76 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 77 | // 华为权限管理 78 | ComponentName comp = new ComponentName("com.huawei.systemmanager", "com.huawei.permissionmanager.ui.MainActivity"); 79 | intent.setComponent(comp); 80 | context.startActivity(intent); 81 | } catch (Exception e) { 82 | e.printStackTrace(); 83 | context.startActivity(getAppDetailSettingIntent(context)); 84 | } 85 | 86 | } 87 | 88 | /** 89 | * 获取应用详情页面intent(如果找不到要跳转的界面,也可以先把用户引导到系统设置页面) 90 | */ 91 | private static Intent getAppDetailSettingIntent(Context context) { 92 | Intent localIntent = new Intent(); 93 | localIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 94 | localIntent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS"); 95 | localIntent.setData(Uri.fromParts("package", context.getPackageName(), null)); 96 | return localIntent; 97 | } 98 | 99 | /** 100 | * 跳转到系统定位服务页面 101 | */ 102 | public static void gotoLocServicePage(Context context) { 103 | Intent intent = new Intent(); 104 | intent.setAction(Settings.ACTION_LOCATION_SOURCE_SETTINGS); 105 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 106 | try { 107 | context.startActivity(intent); 108 | } catch (ActivityNotFoundException ex) { 109 | intent.setAction(Settings.ACTION_SETTINGS); 110 | try { 111 | context.startActivity(intent); 112 | } catch (Exception e) { 113 | e.printStackTrace(); 114 | } 115 | } 116 | } 117 | 118 | } 119 | -------------------------------------------------------------------------------- /app/src/main/java/com/cxz/kotlin/samples/widgets/PermissionDialog.kt: -------------------------------------------------------------------------------- 1 | package com.cxz.kotlin.samples.widgets 2 | 3 | import android.content.DialogInterface 4 | import android.graphics.Color 5 | import android.graphics.drawable.ColorDrawable 6 | import android.os.Bundle 7 | import androidx.fragment.app.DialogFragment 8 | import android.view.* 9 | import com.cxz.kotlin.baselibs.widget.OnNoDoubleClickListener 10 | import com.cxz.kotlin.samples.R 11 | import kotlinx.android.synthetic.main.dialog_permission.view.* 12 | 13 | /** 14 | * @author admin 15 | * @date 2019/10/23 16 | * @desc 17 | */ 18 | class PermissionDialog : DialogFragment() { 19 | 20 | companion object { 21 | val TITLE = "title" 22 | val CONTENT = "content" 23 | val RIGHT_TEXT = "right_text" 24 | val CANCEL_ABLE = "cancel_able" 25 | 26 | fun newInstance(builder: Builder): PermissionDialog { 27 | val bundle = Bundle() 28 | val fragment = PermissionDialog() 29 | bundle.putString(TITLE, builder.title) 30 | bundle.putString(CONTENT, builder.content) 31 | bundle.putString(RIGHT_TEXT, builder.rightText) 32 | fragment.arguments = bundle 33 | fragment.isCancelable = builder.cancelAble 34 | return fragment 35 | } 36 | 37 | fun newBuilder(): Builder { 38 | return Builder() 39 | } 40 | } 41 | 42 | private var onConfirmClickListener: (() -> Unit)? = null 43 | private var onCancelClickListener: (() -> Unit)? = null 44 | 45 | fun setOnConfirmClickListener(click: (() -> Unit)): PermissionDialog { 46 | this.onConfirmClickListener = click 47 | return this 48 | } 49 | 50 | fun setOnCancelClickListener(click: (() -> Unit)): PermissionDialog { 51 | this.onCancelClickListener = click 52 | return this 53 | } 54 | 55 | override fun onCreateView( 56 | inflater: LayoutInflater, 57 | container: ViewGroup?, 58 | savedInstanceState: Bundle? 59 | ): View? { 60 | dialog?.requestWindowFeature(Window.FEATURE_NO_TITLE) 61 | dialog?.window!!.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) 62 | dialog?.setCanceledOnTouchOutside(false) 63 | dialog?.setOnKeyListener(DialogInterface.OnKeyListener { dialog, keyCode, event -> 64 | if (keyCode == KeyEvent.KEYCODE_BACK) { 65 | return@OnKeyListener true 66 | } 67 | false 68 | }) 69 | 70 | val rootView = inflater.inflate(R.layout.dialog_permission, container, false) 71 | initView(rootView) 72 | return rootView 73 | } 74 | 75 | private fun initView(view: View) { 76 | 77 | val title = this.arguments?.getString(TITLE) 78 | if (title.isNullOrEmpty().not()) { 79 | view.tv_title.visibility = View.VISIBLE 80 | view.tv_title.text = title 81 | } 82 | val content = this.arguments?.getString(CONTENT) 83 | if (content.isNullOrEmpty().not()) { 84 | view.tv_content.visibility = View.VISIBLE 85 | view.tv_content.text = content 86 | } 87 | val rightText = this.arguments?.getString(RIGHT_TEXT) 88 | if (rightText.isNullOrEmpty().not()) { 89 | view.tv_confirm.text = rightText 90 | } 91 | 92 | view.tv_confirm.setOnClickListener(object : OnNoDoubleClickListener() { 93 | override fun onNoDoubleClick(v: View?) { 94 | onConfirmClickListener?.invoke() 95 | dismiss() 96 | } 97 | }) 98 | view.tv_cancel.setOnClickListener(object : OnNoDoubleClickListener() { 99 | override fun onNoDoubleClick(v: View?) { 100 | onCancelClickListener?.invoke() 101 | dismiss() 102 | } 103 | }) 104 | } 105 | 106 | class Builder { 107 | private var mConfirmDialog: PermissionDialog? = null 108 | var title: String = "" 109 | var content: String = "" 110 | var rightText: String = "" 111 | var cancelAble: Boolean = false 112 | 113 | fun setTitle(title: String): Builder { 114 | this.title = title 115 | return this 116 | } 117 | 118 | fun setContent(content: String): Builder { 119 | this.content = content 120 | return this 121 | } 122 | 123 | fun setRightText(rightText: String): Builder { 124 | this.rightText = rightText 125 | return this 126 | } 127 | 128 | fun setCancelAble(cancelAble: Boolean): Builder { 129 | this.cancelAble = cancelAble 130 | return this 131 | } 132 | 133 | fun build(): PermissionDialog? { 134 | if (this.mConfirmDialog == null) { 135 | this.mConfirmDialog = newInstance(this) 136 | } 137 | return this.mConfirmDialog 138 | } 139 | } 140 | 141 | 142 | } 143 | -------------------------------------------------------------------------------- /app/src/main/res/color/s_app_color_blue_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/color/s_app_color_gray.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/color/s_btn_blue_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/color/s_btn_blue_border.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/color/s_btn_blue_text.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/color/s_topbar_btn_color.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_white_corner_10.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 44 | 46 | 48 | 50 | 52 | 54 | 56 | 58 | 60 | 62 | 64 | 66 | 68 | 70 | 72 | 74 | 75 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 13 | 14 | 21 | 22 | 26 | 27 |