From 21e7ec53813bc4d48b1c02435b7fabba8709b2e3 Mon Sep 17 00:00:00 2001 From: Aleyn Date: Sun, 31 Jul 2022 22:33:53 +0800 Subject: [PATCH] =?UTF-8?q?fate:=E6=B7=BB=E5=8A=A0FlowAdapter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/codeStyles/Project.xml | 16 -- .idea/compiler.xml | 2 +- .idea/gradle.xml | 6 +- .idea/misc.xml | 14 +- .idea/runConfigurations.xml | 12 - app/build.gradle | 62 ---- app/build.gradle.kts | 67 +++++ app/proguard-rules.pro | 2 +- app/src/main/AndroidManifest.xml | 8 +- .../java/com/pcl/mvvm/app/MyApplication.kt | 18 +- .../java/com/pcl/mvvm/app/base/BaseResult.kt | 2 +- .../main/java/com/pcl/mvvm/common/Constant.kt | 2 +- .../java/com/pcl/mvvm/data/HomeRepository.kt | 56 ++-- .../java/com/pcl/mvvm/data/db/LinDatabase.kt | 35 --- .../db/converters/ArticlesTypeConverters.kt | 23 -- .../java/com/pcl/mvvm/data/db/dao/HomeDao.kt | 38 --- .../mvvm/data/db/migration/MIGRATION_1_2.kt | 17 -- .../com/pcl/mvvm/data/http/HomeNetWork.kt | 6 +- .../com/pcl/mvvm/network/api/HomeService.kt | 19 +- .../pcl/mvvm/network/entity/ArticlesBean.kt | 24 +- .../com/pcl/mvvm/network/entity/BannerBean.kt | 7 +- .../pcl/mvvm/network/entity/HomeListBean.kt | 8 - .../main/java/com/pcl/mvvm/ui/MainActivity.kt | 36 ++- .../com/pcl/mvvm/ui/detail/DetailActivity.kt | 8 +- .../java/com/pcl/mvvm/ui/home/HomeFragment.kt | 45 +-- .../com/pcl/mvvm/ui/home/HomeListAdapter.kt | 2 +- .../com/pcl/mvvm/ui/home/HomeViewModel.kt | 44 ++- .../java/com/pcl/mvvm/ui/me/MeFragment.kt | 12 +- .../java/com/pcl/mvvm/ui/me/MeViewModel.kt | 19 +- .../pcl/mvvm/ui/project/ProjectViewModel.kt | 57 ++-- .../java/com/pcl/mvvm/utils/InjectorUtil.kt | 6 +- .../java/com/pcl/mvvm/utils/RetrofitClient.kt | 12 +- app/src/main/res/layout/activity_main.xml | 9 +- app/src/main/res/layout/home_fragment.xml | 6 +- app/src/main/res/menu/navigation_items.xml | 15 + build.gradle | 36 --- build.gradle.kts | 10 + buildSrc/build.gradle.kts | 2 +- .../src/main/kotlin/com/aleyn/BuildConfig.kt | 9 +- buildSrc/src/main/kotlin/com/aleyn/Deps.kt | 85 ++---- gradle/wrapper/gradle-wrapper.properties | 2 +- mvvmlin/build.gradle | 65 ----- mvvmlin/build.gradle.kts | 58 ++++ mvvmlin/proguard-rules.pro | 2 +- .../aleyn/mvvm/adapter/FlowAdapterFactory.kt | 46 +++ .../com/aleyn/mvvm/adapter/FlowCallAdapter.kt | 79 ++++++ .../java/com/aleyn/mvvm/app/GlobalConfig.kt | 17 -- .../main/java/com/aleyn/mvvm/app/MVVMLin.kt | 24 +- .../java/com/aleyn/mvvm/base/BaseActivity.kt | 26 +- .../com/aleyn/mvvm/base/BaseApplication.kt | 4 +- .../java/com/aleyn/mvvm/base/BaseFragment.kt | 45 ++- .../java/com/aleyn/mvvm/base/BaseModel.kt | 32 +-- .../java/com/aleyn/mvvm/base/BaseViewModel.kt | 122 +------- .../java/com/aleyn/mvvm/base/IBaseResponse.kt | 2 +- .../java/com/aleyn/mvvm/base/IViewModel.kt | 13 + .../java/com/aleyn/mvvm/base/NoViewModel.kt | 2 +- .../com/aleyn/mvvm/base/ViewModelFactory.kt | 32 --- .../com/aleyn/mvvm/binding/ImageAdapter.kt | 2 +- .../aleyn/mvvm/binding/TabLayoutAdapter.kt | 2 +- .../main/java/com/aleyn/mvvm/event/Message.kt | 2 +- .../main/java/com/aleyn/mvvm/extend/NetKtx.kt | 69 +++++ .../main/java/com/aleyn/mvvm/network/ERROR.kt | 22 +- .../com/aleyn/mvvm/network/ExceptionHandle.kt | 48 ++-- .../aleyn/mvvm/network/ResponseThrowable.kt | 6 +- .../network/interceptor/BaseInterceptor.kt | 22 -- .../network/interceptor/DefaultPrinter.kt | 240 ---------------- .../mvvm/network/interceptor/FormatPrinter.kt | 60 ---- .../network/interceptor/LoggingInterceptor.kt | 266 ------------------ .../main/java/com/aleyn/mvvm/utils/Extend.kt | 22 -- settings.gradle | 1 - settings.gradle.kts | 20 ++ 71 files changed, 733 insertions(+), 1477 deletions(-) delete mode 100644 .idea/runConfigurations.xml delete mode 100644 app/build.gradle create mode 100644 app/build.gradle.kts delete mode 100644 app/src/main/java/com/pcl/mvvm/data/db/LinDatabase.kt delete mode 100644 app/src/main/java/com/pcl/mvvm/data/db/converters/ArticlesTypeConverters.kt delete mode 100644 app/src/main/java/com/pcl/mvvm/data/db/dao/HomeDao.kt delete mode 100644 app/src/main/java/com/pcl/mvvm/data/db/migration/MIGRATION_1_2.kt create mode 100644 app/src/main/res/menu/navigation_items.xml delete mode 100644 build.gradle create mode 100644 build.gradle.kts delete mode 100644 mvvmlin/build.gradle create mode 100644 mvvmlin/build.gradle.kts create mode 100644 mvvmlin/src/main/java/com/aleyn/mvvm/adapter/FlowAdapterFactory.kt create mode 100644 mvvmlin/src/main/java/com/aleyn/mvvm/adapter/FlowCallAdapter.kt delete mode 100644 mvvmlin/src/main/java/com/aleyn/mvvm/app/GlobalConfig.kt create mode 100644 mvvmlin/src/main/java/com/aleyn/mvvm/base/IViewModel.kt delete mode 100644 mvvmlin/src/main/java/com/aleyn/mvvm/base/ViewModelFactory.kt create mode 100644 mvvmlin/src/main/java/com/aleyn/mvvm/extend/NetKtx.kt delete mode 100644 mvvmlin/src/main/java/com/aleyn/mvvm/network/interceptor/BaseInterceptor.kt delete mode 100644 mvvmlin/src/main/java/com/aleyn/mvvm/network/interceptor/DefaultPrinter.kt delete mode 100644 mvvmlin/src/main/java/com/aleyn/mvvm/network/interceptor/FormatPrinter.kt delete mode 100644 mvvmlin/src/main/java/com/aleyn/mvvm/network/interceptor/LoggingInterceptor.kt delete mode 100644 mvvmlin/src/main/java/com/aleyn/mvvm/utils/Extend.kt delete mode 100644 settings.gradle create mode 100644 settings.gradle.kts diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index ce1fc4f..ce889bd 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -1,22 +1,6 @@ - - diff --git a/.idea/compiler.xml b/.idea/compiler.xml index 61a9130..fb7f4a8 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml index 46057ad..87a52b9 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -4,10 +4,10 @@ diff --git a/.idea/misc.xml b/.idea/misc.xml index d5d35ec..3fe0482 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,6 +1,18 @@ - + + + + diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml deleted file mode 100644 index 7f68460..0000000 --- a/.idea/runConfigurations.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle deleted file mode 100644 index 1568973..0000000 --- a/app/build.gradle +++ /dev/null @@ -1,62 +0,0 @@ -import com.aleyn.BuildConfig -import com.aleyn.Depend -import com.aleyn.Room - -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply plugin: 'kotlin-kapt' - -android { - compileSdkVersion BuildConfig.compileSdkVersion - defaultConfig { - applicationId BuildConfig.applicationId - minSdkVersion BuildConfig.minSdkVersion - targetSdkVersion BuildConfig.targetSdkVersion - versionCode BuildConfig.versionCode - versionName BuildConfig.versionName - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } - dataBinding { - enabled = true - } - viewBinding { - enabled = true - } - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - - kotlinOptions { - jvmTarget = "1.8" - } -} - -dependencies { - implementation fileTree(dir: 'libs', include: ['*.jar']) - testImplementation Depend.junit - androidTestImplementation Depend.androidTestJunit - androidTestImplementation Depend.espressoCore - //MVVMLin - implementation project(path: ':mvvmlin') - //第三方 - implementation Depend.banner - implementation Depend.bottomTab - implementation Depend.BRVAH - implementation Depend.bdclta - implementation Depend.swiperefresh - implementation Depend.bdcltaRv - - //Room - api Room.runtime - kapt Room.compiler - api Room.ktx - testImplementation Room.testing - -} diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..1f2daf1 --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,67 @@ +import com.aleyn.BuildConfig +import com.aleyn.Depend +import com.aleyn.AndroidX +import com.aleyn.Retrofit + +plugins { + id("com.android.application") + kotlin("android") + kotlin("kapt") +} + +android { + compileSdk = BuildConfig.compileSdkVersion + + defaultConfig { + applicationId = BuildConfig.applicationId + minSdk = BuildConfig.minSdkVersion + targetSdk = BuildConfig.targetSdkVersion + versionCode = BuildConfig.versionCode + versionName = BuildConfig.versionName + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + + buildFeatures { + dataBinding = true + viewBinding = true + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } +} + +dependencies { + implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar")))) + implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar")))) + AndroidX.values.forEach { implementation(it) } + testImplementation(Depend.junit) + androidTestImplementation(Depend.androidTestJunit) + androidTestImplementation(Depend.espressoCore) + //MVVMLin + implementation(project("path" to ":mvvmlin")) + //第三方 + implementation(Depend.banner) + implementation(Depend.BRVAH) + implementation(Depend.refreshKernel) + implementation(Depend.refreshHeader) + implementation(Depend.bdclta) + implementation(Depend.bdcltaRv) + implementation(Depend.netCache) + implementation(Depend.netCache) + Retrofit.values.forEach { implementation(it) } +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index f1b4245..0deded0 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -1,6 +1,6 @@ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. +# proguardFiles setting in build.gradle.kts # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index de209bb..566df88 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -17,8 +17,12 @@ android:supportsRtl="true" android:theme="@style/AppTheme" tools:ignore="GoogleAppIndexingWarning"> - - + + diff --git a/app/src/main/java/com/pcl/mvvm/app/MyApplication.kt b/app/src/main/java/com/pcl/mvvm/app/MyApplication.kt index d0cfa57..437cf49 100644 --- a/app/src/main/java/com/pcl/mvvm/app/MyApplication.kt +++ b/app/src/main/java/com/pcl/mvvm/app/MyApplication.kt @@ -3,26 +3,34 @@ package com.pcl.mvvm.app import com.aleyn.mvvm.base.BaseApplication import com.blankj.utilcode.util.LogUtils import com.pcl.mvvm.BuildConfig +import com.scwang.smart.refresh.header.ClassicsHeader +import com.scwang.smart.refresh.layout.SmartRefreshLayout /** - * @auther : Aleyn + * @author : Aleyn * time : 2019/11/04 */ class MyApplication : BaseApplication() { + companion object { + init { + SmartRefreshLayout.setDefaultRefreshHeaderCreator { context, _ -> + ClassicsHeader(context) + } + } + } + override fun onCreate() { super.onCreate() - // 在OnCreate 中可以传入 自定义的 GlobalConfig - /*MVVMLin.install(object : GlobalConfig { - override fun viewModelFactory() = ViewModelFactory.getInstance() + /*MVVMLin.setNetException(CoroutineExceptionHandler { context, e -> + })*/ LogUtils.getConfig().run { isLogSwitch = BuildConfig.DEBUG setSingleTagSwitch(true) } - } } \ No newline at end of file diff --git a/app/src/main/java/com/pcl/mvvm/app/base/BaseResult.kt b/app/src/main/java/com/pcl/mvvm/app/base/BaseResult.kt index 4e3d9c3..7f549b9 100644 --- a/app/src/main/java/com/pcl/mvvm/app/base/BaseResult.kt +++ b/app/src/main/java/com/pcl/mvvm/app/base/BaseResult.kt @@ -3,7 +3,7 @@ package com.pcl.mvvm.app.base import com.aleyn.mvvm.base.IBaseResponse /** - * @auther : Aleyn + * @author : Aleyn * time : 2019/11/01 */ data class BaseResult( diff --git a/app/src/main/java/com/pcl/mvvm/common/Constant.kt b/app/src/main/java/com/pcl/mvvm/common/Constant.kt index 0d645de..81d6d48 100644 --- a/app/src/main/java/com/pcl/mvvm/common/Constant.kt +++ b/app/src/main/java/com/pcl/mvvm/common/Constant.kt @@ -1,7 +1,7 @@ package com.pcl.mvvm.common /** - * @auther : Aleyn + * @author : Aleyn * time : 2019/08/16 */ object Constant { diff --git a/app/src/main/java/com/pcl/mvvm/data/HomeRepository.kt b/app/src/main/java/com/pcl/mvvm/data/HomeRepository.kt index 184868e..c7299c0 100644 --- a/app/src/main/java/com/pcl/mvvm/data/HomeRepository.kt +++ b/app/src/main/java/com/pcl/mvvm/data/HomeRepository.kt @@ -1,69 +1,47 @@ package com.pcl.mvvm.data +import com.aleyn.cache.CacheMode import com.aleyn.mvvm.base.BaseModel import com.pcl.mvvm.app.base.BaseResult -import com.pcl.mvvm.data.db.dao.HomeDao import com.pcl.mvvm.data.http.HomeNetWork import com.pcl.mvvm.network.entity.BannerBean import com.pcl.mvvm.network.entity.HomeListBean -import com.pcl.mvvm.network.entity.NavTypeBean -import com.pcl.mvvm.network.entity.UsedWeb +import kotlinx.coroutines.flow.Flow /** - * @auther : Aleyn + * @author : Aleyn * time : 2019/10/29 */ class HomeRepository private constructor( - private val netWork: HomeNetWork, - private val localData: HomeDao + private val netWork: HomeNetWork ) : BaseModel() { - suspend fun getBannerData(refresh: Boolean = false): List { - return cacheNetCall({ - netWork.getBannerData() - }, { - localData.getBannerList() - }, { - if (refresh) localData.deleteBannerAll() - localData.insertBanner(it) - }, { - !refresh && !it.isNullOrEmpty() - }) + fun getBannerData(refresh: Boolean): Flow>> { + val cacheModel = + if (refresh) CacheMode.NETWORK_PUT_CACHE else CacheMode.READ_CACHE_NETWORK_PUT + return netWork.getBannerData(cacheModel) } - suspend fun getHomeList(page: Int, refresh: Boolean): HomeListBean { - return cacheNetCall({ - netWork.getHomeList(page) - }, { - localData.getHomeList(page + 1) - }, { - if (refresh) localData.deleteHomeAll() - localData.insertData(it) - }, { - !refresh - }) + fun getHomeList(page: Int, refresh: Boolean = false): Flow> { + val cacheModel = + if (refresh) CacheMode.NETWORK_PUT_CACHE else CacheMode.READ_CACHE_NETWORK_PUT + return netWork.getHomeList(page, cacheModel) } - suspend fun getNaviJson(): BaseResult> { - return netWork.getNaviJson() - } + suspend fun getNaviJson() = netWork.getNaviJson() - suspend fun getProjectList(page: Int, cid: Int): BaseResult { - return netWork.getProjectList(page, cid) - } + suspend fun getProjectList(page: Int, cid: Int) = netWork.getProjectList(page, cid) - suspend fun getPopularWeb(): BaseResult> { - return netWork.getPopularWeb() - } + suspend fun getPopularWeb() = netWork.getPopularWeb() companion object { @Volatile private var INSTANCE: HomeRepository? = null - fun getInstance(netWork: HomeNetWork, homeDao: HomeDao) = + fun getInstance(netWork: HomeNetWork) = INSTANCE ?: synchronized(this) { - INSTANCE ?: HomeRepository(netWork, homeDao).also { INSTANCE = it } + INSTANCE ?: HomeRepository(netWork).also { INSTANCE = it } } } } \ No newline at end of file diff --git a/app/src/main/java/com/pcl/mvvm/data/db/LinDatabase.kt b/app/src/main/java/com/pcl/mvvm/data/db/LinDatabase.kt deleted file mode 100644 index 7b829cc..0000000 --- a/app/src/main/java/com/pcl/mvvm/data/db/LinDatabase.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.pcl.mvvm.data.db - -import androidx.room.Database -import androidx.room.Room -import androidx.room.RoomDatabase -import com.blankj.utilcode.util.Utils -import com.pcl.mvvm.data.db.dao.HomeDao -import com.pcl.mvvm.data.db.migration.MIGRATION -import com.pcl.mvvm.network.entity.BannerBean -import com.pcl.mvvm.network.entity.HomeListBean - -/** - * @auther : Aleyn - * time : 2020/03/12 - */ -@Database(entities = [HomeListBean::class, BannerBean::class], version = 2, exportSchema = false) -abstract class LinDatabase : RoomDatabase() { - - abstract fun homeLocaData(): HomeDao - - - companion object { - fun getInstanse() = SingletonHolder.INSTANCE - } - - private object SingletonHolder { - val INSTANCE = Room.databaseBuilder( - Utils.getApp(), - LinDatabase::class.java, - "lin_db" - ) - .addMigrations(MIGRATION.MIGRATION_1_2) - .build() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/pcl/mvvm/data/db/converters/ArticlesTypeConverters.kt b/app/src/main/java/com/pcl/mvvm/data/db/converters/ArticlesTypeConverters.kt deleted file mode 100644 index 0cb3066..0000000 --- a/app/src/main/java/com/pcl/mvvm/data/db/converters/ArticlesTypeConverters.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.pcl.mvvm.data.db.converters - -import androidx.room.TypeConverter -import com.blankj.utilcode.util.GsonUtils -import com.google.gson.reflect.TypeToken -import com.pcl.mvvm.network.entity.ArticlesBean - -/** - * @auther : Aleyn - * time : 2020/03/12 - */ -class ArticlesTypeConverters { - - @TypeConverter - fun stringToArticles(json: String): List { - val type = object : TypeToken>() {}.type - return GsonUtils.fromJson(json, type) - } - - @TypeConverter - fun articlesToString(data: List): String = GsonUtils.toJson(data) - -} \ No newline at end of file diff --git a/app/src/main/java/com/pcl/mvvm/data/db/dao/HomeDao.kt b/app/src/main/java/com/pcl/mvvm/data/db/dao/HomeDao.kt deleted file mode 100644 index 508370e..0000000 --- a/app/src/main/java/com/pcl/mvvm/data/db/dao/HomeDao.kt +++ /dev/null @@ -1,38 +0,0 @@ -package com.pcl.mvvm.data.db.dao - -import androidx.room.* -import com.pcl.mvvm.network.entity.BannerBean -import com.pcl.mvvm.network.entity.HomeListBean - -/** - * @auther : Aleyn - * time : 2020/03/12 - */ -@Dao -interface HomeDao { - - @Query("SELECT * FROM HOME_DATA WHERE curPage = :page") - suspend fun getHomeList(page: Int): HomeListBean? - - @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insertData(homeListBean: HomeListBean) - - @Query("DELETE FROM HOME_DATA") - suspend fun deleteHomeAll() - - @Update - suspend fun updataData(homeListBean: HomeListBean) - - @Delete - suspend fun deleteData(vararg data: HomeListBean) - - //Banner - @Query("SELECT * FROM BANNER") - suspend fun getBannerList(): List - - @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insertBanner(banners: List) - - @Query("DELETE FROM BANNER") - suspend fun deleteBannerAll() -} \ No newline at end of file diff --git a/app/src/main/java/com/pcl/mvvm/data/db/migration/MIGRATION_1_2.kt b/app/src/main/java/com/pcl/mvvm/data/db/migration/MIGRATION_1_2.kt deleted file mode 100644 index a0f3764..0000000 --- a/app/src/main/java/com/pcl/mvvm/data/db/migration/MIGRATION_1_2.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.pcl.mvvm.data.db.migration - -import androidx.room.migration.Migration -import androidx.sqlite.db.SupportSQLiteDatabase - -/** - * @auther : Aleyn - * time : 2020/03/17 - */ -object MIGRATION { - val MIGRATION_1_2 = object : Migration(1, 2) { - override fun migrate(database: SupportSQLiteDatabase) { - // 没有更改表结构,空实现 - database.execSQL("create table banner(id INTEGER NOT NULL primary key,`desc` TEXT NOT NULL,imagePath TEXT NOT NULL,isVisible INTEGER NOT NULL,`order` INTEGER NOT NULL, title TEXT NOT NULL,type INTEGER NOT NULL,url TEXT NOT NULL)") - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/pcl/mvvm/data/http/HomeNetWork.kt b/app/src/main/java/com/pcl/mvvm/data/http/HomeNetWork.kt index 089c7cd..f728fc4 100644 --- a/app/src/main/java/com/pcl/mvvm/data/http/HomeNetWork.kt +++ b/app/src/main/java/com/pcl/mvvm/data/http/HomeNetWork.kt @@ -4,16 +4,16 @@ import com.pcl.mvvm.network.api.HomeService import com.pcl.mvvm.utils.RetrofitClient /** - * @auther : Aleyn + * @author : Aleyn * time : 2019/11/01 */ class HomeNetWork { private val mService by lazy { RetrofitClient.getInstance().create(HomeService::class.java) } - suspend fun getBannerData() = mService.getBanner() + fun getBannerData(cacheModel: String) = mService.getBanner(cacheModel) - suspend fun getHomeList(page: Int) = mService.getHomeList(page) + fun getHomeList(page: Int, cacheModel: String) = mService.getHomeList(page, cacheModel) suspend fun getNaviJson() = mService.naviJson() diff --git a/app/src/main/java/com/pcl/mvvm/network/api/HomeService.kt b/app/src/main/java/com/pcl/mvvm/network/api/HomeService.kt index 0b11cc9..823fe04 100644 --- a/app/src/main/java/com/pcl/mvvm/network/api/HomeService.kt +++ b/app/src/main/java/com/pcl/mvvm/network/api/HomeService.kt @@ -1,16 +1,19 @@ package com.pcl.mvvm.network.api +import com.aleyn.cache.CacheStrategy import com.pcl.mvvm.app.base.BaseResult import com.pcl.mvvm.network.entity.BannerBean import com.pcl.mvvm.network.entity.HomeListBean import com.pcl.mvvm.network.entity.NavTypeBean import com.pcl.mvvm.network.entity.UsedWeb +import kotlinx.coroutines.flow.Flow import retrofit2.http.GET +import retrofit2.http.Header import retrofit2.http.Path import retrofit2.http.Query /** - * @auther : Aleyn + * @author : Aleyn * time : 2019/10/29 */ interface HomeService { @@ -19,7 +22,9 @@ interface HomeService { * 玩安卓轮播图 */ @GET("banner/json") - suspend fun getBanner(): BaseResult> + fun getBanner( + @Header(CacheStrategy.CACHE_MODE) cacheModel: String + ): Flow>> /** @@ -34,7 +39,10 @@ interface HomeService { * @param page 页码,从0开始 */ @GET("article/listproject/{page}/json") - suspend fun getHomeList(@Path("page") page: Int): BaseResult + fun getHomeList( + @Path("page") page: Int, + @Header(CacheStrategy.CACHE_MODE) cacheModel: String + ): Flow> /** @@ -42,7 +50,10 @@ interface HomeService { * @param page 页码,从0开始 */ @GET("project/list/{page}/json") - suspend fun getProjectList(@Path("page") page: Int, @Query("cid") cid: Int): BaseResult + suspend fun getProjectList( + @Path("page") page: Int, + @Query("cid") cid: Int + ): BaseResult /** diff --git a/app/src/main/java/com/pcl/mvvm/network/entity/ArticlesBean.kt b/app/src/main/java/com/pcl/mvvm/network/entity/ArticlesBean.kt index 08ee198..a9b50bf 100644 --- a/app/src/main/java/com/pcl/mvvm/network/entity/ArticlesBean.kt +++ b/app/src/main/java/com/pcl/mvvm/network/entity/ArticlesBean.kt @@ -1,7 +1,7 @@ package com.pcl.mvvm.network.entity /** - * @auther : Aleyn + * @author : Aleyn * time : 2019/11/01 */ @@ -47,24 +47,4 @@ data class ArticlesBean( var isShowImage: Boolean = true, // 分类name var navigationName: String? = null -) - -/*data class Data( - val apkLink: String, - val author: String, - val chapterId: Int, - val chapterName: String, - val collect: Boolean, - val courseId: Int, - val desc: String, - val envelopePic: String, - val id: Int, - val link: String, - val niceDate: String, - val origin: String, - val projectLink: String, - val publishTime: Long, - val title: String, - val visible: Int, - val zan: Int -)*/ +) \ No newline at end of file diff --git a/app/src/main/java/com/pcl/mvvm/network/entity/BannerBean.kt b/app/src/main/java/com/pcl/mvvm/network/entity/BannerBean.kt index 67d4a0e..66f8ccb 100644 --- a/app/src/main/java/com/pcl/mvvm/network/entity/BannerBean.kt +++ b/app/src/main/java/com/pcl/mvvm/network/entity/BannerBean.kt @@ -1,10 +1,7 @@ package com.pcl.mvvm.network.entity -import androidx.room.Entity -import androidx.room.PrimaryKey - /** - * @auther : Aleyn + * @author : Aleyn * time : 2019/11/01 */ @@ -23,9 +20,7 @@ import androidx.room.PrimaryKey * type : 0 * url : http://www.wanandroid.com/blog/show/2037 */ -@Entity(tableName = "banner") data class BannerBean( - @PrimaryKey val id: Int, val desc: String, val imagePath: String, diff --git a/app/src/main/java/com/pcl/mvvm/network/entity/HomeListBean.kt b/app/src/main/java/com/pcl/mvvm/network/entity/HomeListBean.kt index d0a1245..ad4352d 100644 --- a/app/src/main/java/com/pcl/mvvm/network/entity/HomeListBean.kt +++ b/app/src/main/java/com/pcl/mvvm/network/entity/HomeListBean.kt @@ -1,10 +1,5 @@ package com.pcl.mvvm.network.entity -import androidx.room.Entity -import androidx.room.PrimaryKey -import androidx.room.TypeConverters -import com.pcl.mvvm.data.db.converters.ArticlesTypeConverters - /** * @author :Aleyn * time: 2019/11/01 @@ -19,10 +14,7 @@ import com.pcl.mvvm.data.db.converters.ArticlesTypeConverters * size : 20 * total : 1049 */ -@Entity(tableName = "home_data") -@TypeConverters(ArticlesTypeConverters::class) data class HomeListBean( - @PrimaryKey val curPage: Int, val offset: Int, val over: Boolean, diff --git a/app/src/main/java/com/pcl/mvvm/ui/MainActivity.kt b/app/src/main/java/com/pcl/mvvm/ui/MainActivity.kt index 34aedbd..b2c524e 100644 --- a/app/src/main/java/com/pcl/mvvm/ui/MainActivity.kt +++ b/app/src/main/java/com/pcl/mvvm/ui/MainActivity.kt @@ -12,38 +12,29 @@ import com.pcl.mvvm.databinding.ActivityMainBinding import com.pcl.mvvm.ui.home.HomeFragment import com.pcl.mvvm.ui.me.MeFragment import com.pcl.mvvm.ui.project.ProjectFragment -import me.majiajie.pagerbottomtabstrip.listener.OnTabItemSelectedListener class MainActivity : BaseActivity() { private val fragments = ArrayList() + private lateinit var showFragment: Fragment + override fun initView(savedInstanceState: Bundle?) { BarUtils.setStatusBarColor(this, ColorUtils.getColor(R.color.colorPrimary)) fragments.add(HomeFragment.newInstance()) fragments.add(ProjectFragment.newInstance()) fragments.add(MeFragment.newInstance()) + showFragment = fragments[0] supportFragmentManager .beginTransaction() - .replace(R.id.container, fragments[0]) + .replace(R.id.container, showFragment) .commitNow() - val navCtl = mBinding.pageNavigationView.material() - .addItem(R.drawable.tab_shop_selected, "首页") - .addItem(R.drawable.tab_car_selected, "项目") - .addItem(R.drawable.tab_me_selected, "我的") - .build() - - navCtl.addTabItemSelectedListener(object : OnTabItemSelectedListener { - - override fun onSelected(index: Int, old: Int) { - switchPage(index, old) - } - - override fun onRepeat(index: Int) { - } - }) + mBinding.bottomNavigationView.setOnItemSelectedListener { + switchPage(it.itemId) + return@setOnItemSelectedListener true + } PermissionUtils.permission(*PermissionUtils.getPermissions().toTypedArray()) .callback(object : PermissionUtils.FullCallback { override fun onGranted(granted: MutableList) { @@ -60,14 +51,21 @@ class MainActivity : BaseActivity() { .request() } - private fun switchPage(index: Int, old: Int) { + private fun switchPage(itemId: Int) { + val index = when (itemId) { + R.id.action_home -> 0 + R.id.action_project -> 1 + R.id.action_me -> 2 + else -> return + } val now = fragments[index] supportFragmentManager.beginTransaction().apply { if (!now.isAdded) { add(R.id.container, now) } + hide(showFragment) show(now) - hide(fragments[old]) + showFragment = now commit() } } diff --git a/app/src/main/java/com/pcl/mvvm/ui/detail/DetailActivity.kt b/app/src/main/java/com/pcl/mvvm/ui/detail/DetailActivity.kt index f0fa116..6ee00cf 100644 --- a/app/src/main/java/com/pcl/mvvm/ui/detail/DetailActivity.kt +++ b/app/src/main/java/com/pcl/mvvm/ui/detail/DetailActivity.kt @@ -1,12 +1,10 @@ package com.pcl.mvvm.ui.detail -import android.os.Build import android.os.Bundle import android.webkit.WebResourceRequest import android.webkit.WebSettings import android.webkit.WebView import android.webkit.WebViewClient -import androidx.annotation.RequiresApi import com.aleyn.mvvm.base.BaseActivity import com.aleyn.mvvm.base.NoViewModel import com.pcl.mvvm.databinding.ActivityDetailBinding @@ -40,19 +38,17 @@ class DetailActivity : BaseActivity() { domStorageEnabled = true textZoom = 100 } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - ws.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW - } + ws.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW } private val webViewClient = object : WebViewClient() { + @Deprecated("Deprecated in Java") override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean { url?.let { view?.loadUrl(it) } return true } - @RequiresApi(Build.VERSION_CODES.LOLLIPOP) override fun shouldOverrideUrlLoading( view: WebView?, request: WebResourceRequest? diff --git a/app/src/main/java/com/pcl/mvvm/ui/home/HomeFragment.kt b/app/src/main/java/com/pcl/mvvm/ui/home/HomeFragment.kt index 12afaec..0c74abe 100644 --- a/app/src/main/java/com/pcl/mvvm/ui/home/HomeFragment.kt +++ b/app/src/main/java/com/pcl/mvvm/ui/home/HomeFragment.kt @@ -4,8 +4,10 @@ import android.content.Intent import android.os.Bundle import android.view.ViewGroup import android.view.ViewGroup.LayoutParams.MATCH_PARENT +import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import com.aleyn.mvvm.base.BaseFragment +import com.blankj.utilcode.util.LogUtils import com.pcl.mvvm.R import com.pcl.mvvm.databinding.HomeFragmentBinding import com.pcl.mvvm.network.entity.ArticlesBean @@ -16,7 +18,7 @@ import com.youth.banner.Banner /** * 此页面使用 ViewBinding - * @auther : Aleyn + * @author : Aleyn * time : 2019/11/02 */ class HomeFragment : BaseFragment() { @@ -38,7 +40,7 @@ class HomeFragment : BaseFragment() { banner.minimumWidth = MATCH_PARENT banner.layoutParams = ViewGroup.LayoutParams(MATCH_PARENT, resources.getDimension(R.dimen.dp_120).toInt()) - banner.adapter = GlideImageLoader() + banner.setAdapter(GlideImageLoader()) } mAdapter.apply { addHeaderView(banner) @@ -50,37 +52,40 @@ class HomeFragment : BaseFragment() { startActivity(intent) } } - mBinding.srlHome.setOnRefreshListener { + mBinding.refreshHome.setOnRefreshListener { dropDownRefresh() } } - override fun lazyLoadData() { - viewModel.run { - - getBanner().observe(this@HomeFragment, { - banner.setDatas(it) - }) + override fun initObserve() { + viewModel.refreshState.observe(this@HomeFragment) { + if (mBinding.refreshHome.isRefreshing) mBinding.refreshHome.finishRefresh() + } - getHomeList(page).observe(this@HomeFragment, { - if (mBinding.srlHome.isRefreshing) mBinding.srlHome.isRefreshing = false - it?.let { - if (it.curPage == 1) mAdapter.setNewInstance(it.datas) - else mAdapter.addData(it.datas) - if (it.curPage == it.pageCount) mAdapter.loadMoreModule.loadMoreEnd() - else mAdapter.loadMoreModule.loadMoreComplete() - page = it.curPage - } - }) + lifecycleScope.launchWhenCreated { + viewModel.mBanners.collect { banner.setDatas(it) } + } + lifecycleScope.launchWhenCreated { + viewModel.projectData.collect { + if (it.curPage == 1) mAdapter.setList(it.datas) + else mAdapter.addData(it.datas) + if (it.curPage == it.pageCount) mAdapter.loadMoreModule.loadMoreEnd() + else mAdapter.loadMoreModule.loadMoreComplete() + page = it.curPage + } } } + override fun lazyLoadData() { + viewModel.getBanner() + viewModel.getHomeList(page) + } + /** * 下拉刷新 */ private fun dropDownRefresh() { page = 0 - mBinding.srlHome.isRefreshing = true viewModel.getHomeList(page, true) viewModel.getBanner(true) } diff --git a/app/src/main/java/com/pcl/mvvm/ui/home/HomeListAdapter.kt b/app/src/main/java/com/pcl/mvvm/ui/home/HomeListAdapter.kt index ddb1d5b..193c0f0 100644 --- a/app/src/main/java/com/pcl/mvvm/ui/home/HomeListAdapter.kt +++ b/app/src/main/java/com/pcl/mvvm/ui/home/HomeListAdapter.kt @@ -9,7 +9,7 @@ import com.pcl.mvvm.R import com.pcl.mvvm.network.entity.ArticlesBean /** - * @auther : Aleyn + * @author : Aleyn * time : 2019/11/08 */ class HomeListAdapter : BaseQuickAdapter(R.layout.item_article_list), diff --git a/app/src/main/java/com/pcl/mvvm/ui/home/HomeViewModel.kt b/app/src/main/java/com/pcl/mvvm/ui/home/HomeViewModel.kt index 6408436..0f70165 100644 --- a/app/src/main/java/com/pcl/mvvm/ui/home/HomeViewModel.kt +++ b/app/src/main/java/com/pcl/mvvm/ui/home/HomeViewModel.kt @@ -1,36 +1,52 @@ package com.pcl.mvvm.ui.home +import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import com.aleyn.mvvm.base.BaseViewModel +import com.aleyn.mvvm.event.SingleLiveEvent +import com.aleyn.mvvm.extend.asResponse +import com.aleyn.mvvm.extend.getOrThrow import com.pcl.mvvm.network.entity.BannerBean import com.pcl.mvvm.network.entity.HomeListBean import com.pcl.mvvm.utils.InjectorUtil +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.onCompletion class HomeViewModel : BaseViewModel() { private val homeRepository by lazy { InjectorUtil.getHomeRepository() } - private val mBanners = MutableLiveData>() + private val _banners = MutableSharedFlow>() + val mBanners: SharedFlow> = _banners - private val projectData = MutableLiveData() + private val _projectData = MutableSharedFlow() + val projectData: SharedFlow = _projectData - fun getBanner(refresh: Boolean = false): MutableLiveData> { - launchGo({ - mBanners.value = homeRepository.getBannerData(refresh) - }) - return mBanners + private val _refreshState = SingleLiveEvent() + val refreshState: LiveData = _refreshState + + /** + * Banner + */ + fun getBanner(refresh: Boolean = false) { + launch { + homeRepository.getBannerData(refresh) + .asResponse() + .collect(_banners) + } } /** * @param page 页码 * @param refresh 是否刷新 */ - fun getHomeList(page: Int, refresh: Boolean = false): MutableLiveData { - launchGo({ - projectData.value = homeRepository.getHomeList(page, refresh) - }, { - projectData.value = null - }) - return projectData + fun getHomeList(page: Int, refresh: Boolean = false) { + launch { + homeRepository.getHomeList(page, refresh) + .asResponse() + .onCompletion { if (refresh) _refreshState.call() } + .collect(_projectData) + } } } diff --git a/app/src/main/java/com/pcl/mvvm/ui/me/MeFragment.kt b/app/src/main/java/com/pcl/mvvm/ui/me/MeFragment.kt index c58dd05..a7b3f32 100644 --- a/app/src/main/java/com/pcl/mvvm/ui/me/MeFragment.kt +++ b/app/src/main/java/com/pcl/mvvm/ui/me/MeFragment.kt @@ -2,6 +2,7 @@ package com.pcl.mvvm.ui.me import android.content.Intent import android.os.Bundle +import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import com.aleyn.mvvm.base.BaseFragment import com.pcl.mvvm.R @@ -23,16 +24,19 @@ class MeFragment : BaseFragment() { layoutManager = LinearLayoutManager(context) adapter = mAdapter } - viewModel.popularWeb.observe(viewLifecycleOwner, { - mAdapter.setNewInstance(it) - }) mAdapter.setOnItemClickListener { _, _, position -> val intent = Intent().apply { - setClass(activity!!, DetailActivity::class.java) + setClass(requireContext(), DetailActivity::class.java) putExtra("url", (mAdapter.data[position]).link) } startActivity(intent) } + + lifecycleScope.launchWhenCreated { + viewModel.popularWeb.collect { + mAdapter.setList(it) + } + } } override fun lazyLoadData() { diff --git a/app/src/main/java/com/pcl/mvvm/ui/me/MeViewModel.kt b/app/src/main/java/com/pcl/mvvm/ui/me/MeViewModel.kt index 29dcd17..23840a9 100644 --- a/app/src/main/java/com/pcl/mvvm/ui/me/MeViewModel.kt +++ b/app/src/main/java/com/pcl/mvvm/ui/me/MeViewModel.kt @@ -1,9 +1,13 @@ package com.pcl.mvvm.ui.me -import androidx.lifecycle.MutableLiveData import com.aleyn.mvvm.base.BaseViewModel +import com.aleyn.mvvm.extend.asResponse +import com.aleyn.mvvm.extend.getOrThrow import com.pcl.mvvm.network.entity.UsedWeb import com.pcl.mvvm.utils.InjectorUtil +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.map /** * @auther : Aleyn @@ -13,14 +17,15 @@ class MeViewModel : BaseViewModel() { private val homeRepository by lazy { InjectorUtil.getHomeRepository() } - var popularWeb = MutableLiveData>() + private val _popularWeb = MutableSharedFlow>() + val popularWeb: SharedFlow> = _popularWeb + fun getPopularWeb() { - launchGo({ - val result = homeRepository.getPopularWeb() - if (result.isSuccess()) { - popularWeb.value = result.data + launch { + homeRepository.getPopularWeb().getOrThrow().let { + _popularWeb.emit(it) } - }) + } } } \ No newline at end of file diff --git a/app/src/main/java/com/pcl/mvvm/ui/project/ProjectViewModel.kt b/app/src/main/java/com/pcl/mvvm/ui/project/ProjectViewModel.kt index 1be0c8f..f45021a 100644 --- a/app/src/main/java/com/pcl/mvvm/ui/project/ProjectViewModel.kt +++ b/app/src/main/java/com/pcl/mvvm/ui/project/ProjectViewModel.kt @@ -1,25 +1,19 @@ package com.pcl.mvvm.ui.project import androidx.databinding.ObservableArrayList -import com.aleyn.mvvm.app.MVVMLin import com.aleyn.mvvm.base.BaseViewModel import com.aleyn.mvvm.event.Message -import com.aleyn.mvvm.network.ResponseThrowable -import com.blankj.utilcode.util.LogUtils +import com.aleyn.mvvm.extend.getOrThrow import com.google.android.material.tabs.TabLayout import com.pcl.mvvm.BR import com.pcl.mvvm.R import com.pcl.mvvm.network.entity.ArticlesBean import com.pcl.mvvm.network.entity.NavTypeBean import com.pcl.mvvm.utils.InjectorUtil -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.FlowPreview -import kotlinx.coroutines.flow.* import me.tatarka.bindingcollectionadapter2.ItemBinding /** - * @auther : Aleyn + * @author : Aleyn * time : 2019/11/12 */ class ProjectViewModel : BaseViewModel() { @@ -38,44 +32,29 @@ class ProjectViewModel : BaseViewModel() { private var page: Int = 0 - /** - * 当一个请求结果,依赖另一个请求结果的时候,我们可以用 流的方式如下: - * 以此类推,还可以 用 zip 操作符 对多个请求进行合并,以及 flatMapMerge、flatMapConcat 等。 - * 熟悉 RxJava 的你,分分钟钟可以上手的 (斜眼笑 `-` ) + * 顺序请求 */ - @ExperimentalCoroutinesApi - @FlowPreview fun getFirstData() { - launchUI { - launchFlow { homeRepository.getNaviJson() } - .flatMapConcat { - return@flatMapConcat if (it.isSuccess()) { - navData.addAll(it.data) - it.data.forEach { item -> navTitle.add(item.name) } - launchFlow { homeRepository.getProjectList(page, it.data[0].id) } - } else throw ResponseThrowable(it.errorCode, it.errorMsg) - } - .onStart { defUI.showDialog.postValue(null) } - .flowOn(Dispatchers.IO) - .onCompletion { defUI.dismissDialog.call() } - .catch { - // 错误处理 - val err = MVVMLin.getConfig().globalExceptionHandle(it) - LogUtils.d("${err.code}: ${err.errMsg}") - } - .collect { - if (it.isSuccess()) items.addAll(it.data.datas) - } - } + launch { + //tab 数据 + val navResult = homeRepository.getNaviJson().getOrThrow() + navData.addAll(navResult) + navResult.forEach { item -> navTitle.add(item.name) } + //tab对应列表数据 + val listBean = homeRepository.getProjectList(page, navResult.first().id).getOrThrow() + items.addAll(listBean.datas) + } } fun getProjectList(cid: Int) { - launchOnlyresult({ homeRepository.getProjectList(page, cid) }, { - items.clear() - items.addAll(it.datas) - }) + launch { + homeRepository.getProjectList(page, cid).getOrThrow().let { + items.clear() + items.addAll(it.datas) + } + } } diff --git a/app/src/main/java/com/pcl/mvvm/utils/InjectorUtil.kt b/app/src/main/java/com/pcl/mvvm/utils/InjectorUtil.kt index 0bb08c4..43d3045 100644 --- a/app/src/main/java/com/pcl/mvvm/utils/InjectorUtil.kt +++ b/app/src/main/java/com/pcl/mvvm/utils/InjectorUtil.kt @@ -1,14 +1,10 @@ package com.pcl.mvvm.utils import com.pcl.mvvm.data.HomeRepository -import com.pcl.mvvm.data.db.LinDatabase import com.pcl.mvvm.data.http.HomeNetWork object InjectorUtil { - fun getHomeRepository() = HomeRepository.getInstance( - HomeNetWork.getInstance(), - LinDatabase.getInstanse().homeLocaData() - ) + fun getHomeRepository() = HomeRepository.getInstance(HomeNetWork.getInstance()) } \ No newline at end of file diff --git a/app/src/main/java/com/pcl/mvvm/utils/RetrofitClient.kt b/app/src/main/java/com/pcl/mvvm/utils/RetrofitClient.kt index 8b553d8..3f8ccfb 100644 --- a/app/src/main/java/com/pcl/mvvm/utils/RetrofitClient.kt +++ b/app/src/main/java/com/pcl/mvvm/utils/RetrofitClient.kt @@ -1,9 +1,13 @@ package com.pcl.mvvm.utils -import com.aleyn.mvvm.network.interceptor.LoggingInterceptor +import com.aleyn.cache.CacheManager +import com.aleyn.cache.NetCacheInterceptor +import com.aleyn.mvvm.adapter.FlowAdapterFactory +import com.blankj.utilcode.util.Utils import com.pcl.mvvm.common.Constant.BASE_URL import okhttp3.ConnectionPool import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory import java.util.concurrent.TimeUnit @@ -26,6 +30,7 @@ class RetrofitClient { init { retrofit = Retrofit.Builder() .client(getOkHttpClient()) + .addCallAdapterFactory(FlowAdapterFactory.create(true)) .addConverterFactory(GsonConverterFactory.create()) .baseUrl(BASE_URL) .build() @@ -34,9 +39,12 @@ class RetrofitClient { private fun getOkHttpClient(): OkHttpClient { return OkHttpClient.Builder() .connectTimeout(20L, TimeUnit.SECONDS) - .addNetworkInterceptor(LoggingInterceptor()) .writeTimeout(20L, TimeUnit.SECONDS) .connectionPool(ConnectionPool(8, 15, TimeUnit.SECONDS)) + .addInterceptor(NetCacheInterceptor(CacheManager(Utils.getApp().cacheDir))) + .addInterceptor(HttpLoggingInterceptor().apply { + level = HttpLoggingInterceptor.Level.BODY + }) .build() } diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index c9b99b7..0300881 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,5 +1,6 @@ - + android:layout_height="@dimen/dp_56" + android:background="@color/white" + app:menu="@menu/navigation_items" /> diff --git a/app/src/main/res/layout/home_fragment.xml b/app/src/main/res/layout/home_fragment.xml index 7c6e800..d447f66 100644 --- a/app/src/main/res/layout/home_fragment.xml +++ b/app/src/main/res/layout/home_fragment.xml @@ -1,8 +1,8 @@ - @@ -25,4 +25,4 @@ android:layout_width="match_parent" android:layout_height="match_parent" /> - + diff --git a/app/src/main/res/menu/navigation_items.xml b/app/src/main/res/menu/navigation_items.xml new file mode 100644 index 0000000..88493ae --- /dev/null +++ b/app/src/main/res/menu/navigation_items.xml @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle deleted file mode 100644 index da1165b..0000000 --- a/build.gradle +++ /dev/null @@ -1,36 +0,0 @@ -import com.aleyn.Versions - -buildscript { - repositories { - google() - jcenter() - } - dependencies { - classpath "com.android.tools.build:gradle:${Versions.buildGradle}" - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.kotlin}" - classpath "com.novoda:bintray-release:${Versions.bintrayRelease}" - // NOTE: Do not place your application dependencies here; they belong - // in the individual module build.gradle files - - } -} - -allprojects { - repositories { - google() - jcenter() - maven { url "https://jitpack.io" } - } - - tasks.withType(Javadoc) { - options { - encoding "UTF-8" - charSet 'UTF-8' - links "http://docs.oracle.com/javase/7/docs/api" - } - } -} - -task clean(type: Delete) { - delete rootProject.buildDir -} diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..1169085 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,10 @@ + +plugins { + id("com.android.application").version("7.2.1") apply (false) + id("com.android.library").version("7.2.1") apply (false) + kotlin("android").version("1.7.10").apply(false) +} + +tasks.register("clean", Delete::class) { + delete(rootProject.buildDir) +} diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 15b61a2..88cd14f 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -2,5 +2,5 @@ plugins { `kotlin-dsl` } repositories { - jcenter() + mavenCentral() } \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/com/aleyn/BuildConfig.kt b/buildSrc/src/main/kotlin/com/aleyn/BuildConfig.kt index f4a9137..139a2cf 100644 --- a/buildSrc/src/main/kotlin/com/aleyn/BuildConfig.kt +++ b/buildSrc/src/main/kotlin/com/aleyn/BuildConfig.kt @@ -6,11 +6,10 @@ package com.aleyn * @Date : 2021/2/5 17:45 */ object BuildConfig { - const val compileSdkVersion = 30 - const val buildToolsVersion = "30.0.3" - const val applicationId = "com.pcl.mvvm" - const val minSdkVersion = 19 - const val targetSdkVersion = 30 + const val compileSdkVersion = 32 + const val applicationId = "com.aleyn.mvvm" + const val minSdkVersion = 21 + const val targetSdkVersion = 32 const val versionCode = 7 const val versionName = "1.0.6" } \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/com/aleyn/Deps.kt b/buildSrc/src/main/kotlin/com/aleyn/Deps.kt index 9f256ce..0f2efb7 100644 --- a/buildSrc/src/main/kotlin/com/aleyn/Deps.kt +++ b/buildSrc/src/main/kotlin/com/aleyn/Deps.kt @@ -6,88 +6,63 @@ package com.aleyn */ object Versions { const val retrofit = "2.9.0" - const val appcompat = "1.2.0" - const val coreKtx = "1.3.2" - const val material = "1.2.1" - const val constraintlayout = "2.0.4" - const val buildGradle = "3.6.3" - const val kotlin = "1.4.21" - const val bintrayRelease = "0.9.1" - const val extensions = "2.2.0" - const val room = "2.3.0-alpha01" - const val lifecycle = "2.2.0" + const val appcompat = "1.4.2" + const val coreKtx = "1.7.0" + const val material = "1.6.1" + const val runtimeKtx = "2.5.0" + const val junit = "4.13.2" + const val junitExt = "1.1.3" + const val espressoCore = "3.4.0" - const val junit = "4.12" - const val junitExt = "1.1.2" - const val espressoCore = "3.3.0" - - const val banner = "2.1.0" - const val BRVAH = "3.0.4" - const val immersionbar = "3.0.0" - const val coil = "1.1.1" + const val banner = "2.2.2" + const val BRAVH = "3.0.7" + const val coil = "2.1.0" const val materialDialogs = "3.1.1" - const val utilCode = "1.30.5" - const val bottomTab = "2.3.0X" - const val bdclta = "3.1.1" - const val swiperefresh = "1.1.0" + const val utilCode = "1.30.6" } object AndroidX { const val appcompat = "androidx.appcompat:appcompat:${Versions.appcompat}" const val coreKtx = "androidx.core:core-ktx:${Versions.coreKtx}" - const val constraintlayout = - "androidx.constraintlayout:constraintlayout:${Versions.constraintlayout}" const val material = "com.google.android.material:material:${Versions.material}" - const val extensions = "androidx.lifecycle:lifecycle-extensions:${Versions.extensions}" - const val lifecycleViewmodelKtx = - "androidx.lifecycle:lifecycle-viewmodel-ktx:${Versions.lifecycle}" + const val recyclerview = "androidx.recyclerview:recyclerview:1.2.1" + const val viewModelKtx = "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.0" + const val runtimeKtx = "androidx.lifecycle:lifecycle-runtime-ktx:${Versions.runtimeKtx}" val values = arrayListOf( appcompat, coreKtx, - constraintlayout, material, - extensions, - lifecycleViewmodelKtx + viewModelKtx, + runtimeKtx, + recyclerview ) } -object Kt { - const val stdlibJdk7 = "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${Versions.kotlin}" - const val stdlibJdk8 = "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${Versions.kotlin}" - const val test = "org.jetbrains.kotlin:kotlin-test-junit:${Versions.kotlin}" - const val plugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.kotlin}" -} - -object Room { - const val runtime = "androidx.room:room-runtime:${Versions.room}" - const val compiler = "androidx.room:room-compiler:${Versions.room}" - const val ktx = "androidx.room:room-ktx:${Versions.room}" - const val testing = "androidx.room:room-testing:${Versions.room}" -} - object Retrofit { - const val runtime = "com.squareup.retrofit2:retrofit:${Versions.retrofit}" - const val gson = "com.squareup.retrofit2:converter-gson:${Versions.retrofit}" - val values = arrayListOf(runtime, gson) + const val retrofit = "com.squareup.retrofit2:retrofit:${Versions.retrofit}" + const val converterGson = "com.squareup.retrofit2:converter-gson:${Versions.retrofit}" + const val okhttpLogging = "com.squareup.okhttp3:logging-interceptor:4.9.3" + val values = arrayListOf(retrofit, converterGson, okhttpLogging) } object Depend { const val junit = "junit:junit:${Versions.junit}" + const val netCache = "io.github.AleynP:net-cache:1.0.0" const val androidTestJunit = "androidx.test.ext:junit:${Versions.junitExt}" const val espressoCore = "androidx.test.espresso:espresso-core:${Versions.espressoCore}" - const val banner = "com.youth.banner:banner:${Versions.banner}" - const val BRVAH = "com.github.CymChad:BaseRecyclerViewAdapterHelper:${Versions.BRVAH}" - const val immersionbar = "com.gyf.immersionbar:immersionbar:${Versions.immersionbar}" + const val banner = "io.github.youth5201314:banner:${Versions.banner}" + const val BRVAH = "com.github.CymChad:BaseRecyclerViewAdapterHelper:${Versions.BRAVH}" + const val refreshKernel = "io.github.scwang90:refresh-layout-kernel:2.0.5" + const val refreshHeader = "io.github.scwang90:refresh-header-classics:2.0.5" const val coil = "io.coil-kt:coil:${Versions.coil}" //图片加载 const val dialogs = "com.afollestad.material-dialogs:lifecycle:${Versions.materialDialogs}" const val dialogsCore = "com.afollestad.material-dialogs:core:${Versions.materialDialogs}" const val utilCode = "com.blankj:utilcodex:${Versions.utilCode}" - const val bottomTab = "me.majiajie:pager-bottom-tab-strip:${Versions.bottomTab}" + const val bdclta = - "me.tatarka.bindingcollectionadapter2:bindingcollectionadapter:${Versions.bdclta}" + "me.tatarka.bindingcollectionadapter2:bindingcollectionadapter:4.0.0" const val bdcltaRv = - "me.tatarka.bindingcollectionadapter2:bindingcollectionadapter-recyclerview:${Versions.bdclta}" - const val swiperefresh = - "androidx.swiperefreshlayout:swiperefreshlayout:${Versions.swiperefresh}" + "me.tatarka.bindingcollectionadapter2:bindingcollectionadapter-recyclerview:4.0.0" + } \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 18c713e..065b951 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip diff --git a/mvvmlin/build.gradle b/mvvmlin/build.gradle deleted file mode 100644 index c101e96..0000000 --- a/mvvmlin/build.gradle +++ /dev/null @@ -1,65 +0,0 @@ -import com.aleyn.AndroidX -import com.aleyn.BuildConfig -import com.aleyn.Depend -import com.aleyn.Retrofit - -apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' -apply plugin: 'kotlin-kapt' -apply plugin: 'com.novoda.bintray-release' - -android { - compileSdkVersion BuildConfig.compileSdkVersion - defaultConfig { - minSdkVersion BuildConfig.minSdkVersion - targetSdkVersion BuildConfig.compileSdkVersion - versionCode BuildConfig.versionCode - versionName BuildConfig.versionName - } - dataBinding { - enabled true - } - viewBinding { - enabled = true - } - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - - kotlinOptions { - jvmTarget = "1.8" - } -} - -dependencies { - api fileTree(dir: 'libs', include: ['*.jar']) - //androidx - api AndroidX.values - //network - api Retrofit.values - //material-dialogs - api Depend.dialogs - api Depend.dialogsCore - //coil - api Depend.coil - // utils 集合了大量常用的工具类 - api Depend.utilCode -} - -publish { - repoName = 'aleyn' - userOrg = 'aleyn' - groupId = 'me.aleyn' - artifactId = 'MVVMLin' - publishVersion = BuildConfig.versionName - desc = 'An Android MVVM framework' - website = 'https://github.com/AleynP/MVVMLin' -} \ No newline at end of file diff --git a/mvvmlin/build.gradle.kts b/mvvmlin/build.gradle.kts new file mode 100644 index 0000000..6cc56f9 --- /dev/null +++ b/mvvmlin/build.gradle.kts @@ -0,0 +1,58 @@ +import com.aleyn.AndroidX +import com.aleyn.BuildConfig +import com.aleyn.Depend +import com.aleyn.Retrofit + +plugins { + id("com.android.library") + kotlin("android") + kotlin("kapt") +} + +android { + compileSdk = BuildConfig.compileSdkVersion + + defaultConfig { + minSdk = BuildConfig.minSdkVersion + targetSdk = BuildConfig.targetSdkVersion + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + + buildFeatures { + dataBinding = true + viewBinding = true + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } +} + +dependencies { + implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar")))) + //androidx + AndroidX.values.forEach { implementation(it) } + //network + Retrofit.values.forEach { compileOnly(it) } + //material-dialogs + api(Depend.dialogs) + api(Depend.dialogsCore) + //coil + api(Depend.coil) + // utils 集合了大量常用的工具类 + api(Depend.utilCode) +} \ No newline at end of file diff --git a/mvvmlin/proguard-rules.pro b/mvvmlin/proguard-rules.pro index f1b4245..0deded0 100644 --- a/mvvmlin/proguard-rules.pro +++ b/mvvmlin/proguard-rules.pro @@ -1,6 +1,6 @@ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. +# proguardFiles setting in build.gradle.kts # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html diff --git a/mvvmlin/src/main/java/com/aleyn/mvvm/adapter/FlowAdapterFactory.kt b/mvvmlin/src/main/java/com/aleyn/mvvm/adapter/FlowAdapterFactory.kt new file mode 100644 index 0000000..7577dd7 --- /dev/null +++ b/mvvmlin/src/main/java/com/aleyn/mvvm/adapter/FlowAdapterFactory.kt @@ -0,0 +1,46 @@ +package com.aleyn.mvvm.adapter + +import kotlinx.coroutines.flow.Flow +import retrofit2.CallAdapter +import retrofit2.Response +import retrofit2.Retrofit +import java.lang.reflect.ParameterizedType +import java.lang.reflect.Type + +/** + * @author : Aleyn + * @date : 2022/07/24 : 14:46 + */ +class FlowAdapterFactory private constructor(private val async: Boolean) : + CallAdapter.Factory() { + + override fun get( + returnType: Type, + annotations: Array, + retrofit: Retrofit, + ): CallAdapter<*, *>? { + val rawType = getRawType(returnType) + if (rawType != Flow::class.java) return null + + require(returnType is ParameterizedType) { "the flow type is error" } + + val flowType = getParameterUpperBound(0, returnType) + val rawFlowType = getRawType(flowType) + + return if (rawFlowType == Response::class.java) { + require(flowType is ParameterizedType) { + "Response must be parameterized as Response or Response" + } + val responseBodyType = getParameterUpperBound(0, flowType) + FlowCallAdapter(responseBodyType, false, async) + } else { + FlowCallAdapter(flowType, true, async) + } + + } + + companion object { + @JvmStatic + fun create(async: Boolean = false) = FlowAdapterFactory(async) + } +} \ No newline at end of file diff --git a/mvvmlin/src/main/java/com/aleyn/mvvm/adapter/FlowCallAdapter.kt b/mvvmlin/src/main/java/com/aleyn/mvvm/adapter/FlowCallAdapter.kt new file mode 100644 index 0000000..5588414 --- /dev/null +++ b/mvvmlin/src/main/java/com/aleyn/mvvm/adapter/FlowCallAdapter.kt @@ -0,0 +1,79 @@ +package com.aleyn.mvvm.adapter + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.suspendCancellableCoroutine +import retrofit2.* +import java.lang.reflect.Type +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException + +/** + * @author : Aleyn + * @date : 2022/07/24 : 15:26 + */ +class FlowCallAdapter( + private val responseBodyType: R, + private val isBody: Boolean, + private val isAsync: Boolean +) : CallAdapter> { + + override fun responseType() = responseBodyType + + override fun adapt(call: Call): Flow<*> { + val callResponse = if (isAsync) ASyncCall(call) else SyncCall(call) + return if (isBody) bodyFlow(callResponse) else responseFlow(callResponse) + } + + private fun responseFlow(call: CallResponse): Flow> = flow { + val response = call.call() + emit(response) + } + + private fun bodyFlow(call: CallResponse): Flow<*> = flow { + val response = call.call() + if (response.isSuccessful) { + emit(response.body()!!) + } else { + throw HttpException(response) + } + } + + + internal interface CallResponse { + suspend fun call(): Response<*> + } + + internal inner class SyncCall(private val originalCall: Call) : CallResponse { + + override suspend fun call(): Response { + return suspendCancellableCoroutine { + val call = originalCall.clone() + it.invokeOnCancellation { call.cancel() } + try { + it.resume(call.execute()) + } catch (e: Exception) { + it.resumeWithException(e) + } + } + } + } + + internal inner class ASyncCall(private val originalCall: Call) : CallResponse { + + override suspend fun call(): Response = suspendCancellableCoroutine { + val call = originalCall.clone() + it.invokeOnCancellation { call.cancel() } + call.enqueue(object : Callback { + override fun onResponse(call: Call, response: Response) { + it.resume(response) + } + + override fun onFailure(call: Call, t: Throwable) { + it.resumeWithException(t) + } + }) + + } + } +} \ No newline at end of file diff --git a/mvvmlin/src/main/java/com/aleyn/mvvm/app/GlobalConfig.kt b/mvvmlin/src/main/java/com/aleyn/mvvm/app/GlobalConfig.kt deleted file mode 100644 index 34a5608..0000000 --- a/mvvmlin/src/main/java/com/aleyn/mvvm/app/GlobalConfig.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.aleyn.mvvm.app - -import androidx.lifecycle.ViewModelProvider -import com.aleyn.mvvm.base.ViewModelFactory -import com.aleyn.mvvm.network.ExceptionHandle - -/** - * @auther : Aleyn - * time : 2019/11/12 - */ -interface GlobalConfig { - - fun viewModelFactory(): ViewModelProvider.Factory? = ViewModelFactory.getInstance() - - fun globalExceptionHandle(e: Throwable) = ExceptionHandle.handleException(e) - -} \ No newline at end of file diff --git a/mvvmlin/src/main/java/com/aleyn/mvvm/app/MVVMLin.kt b/mvvmlin/src/main/java/com/aleyn/mvvm/app/MVVMLin.kt index 02ac078..9322308 100644 --- a/mvvmlin/src/main/java/com/aleyn/mvvm/app/MVVMLin.kt +++ b/mvvmlin/src/main/java/com/aleyn/mvvm/app/MVVMLin.kt @@ -1,19 +1,29 @@ package com.aleyn.mvvm.app +import com.aleyn.mvvm.network.ExceptionHandle +import com.blankj.utilcode.util.ToastUtils +import kotlinx.coroutines.CoroutineExceptionHandler + /** - * @auther : Aleyn + * @author : Aleyn * time : 2019/11/12 */ object MVVMLin { - private val DEFULT = object : GlobalConfig {} + private val defNetException = CoroutineExceptionHandler { _, throwable -> + val exception = ExceptionHandle.handleException(throwable) + ToastUtils.showShort(exception.errMsg) + } - private var mConfig: GlobalConfig = DEFULT + val netException: CoroutineExceptionHandler + get() = customNetException ?: defNetException - fun install(config: GlobalConfig) { - mConfig = config - } - fun getConfig() = mConfig + private var customNetException: CoroutineExceptionHandler? = null + + + fun setNetException(netException: CoroutineExceptionHandler) = apply { + customNetException = netException + } } \ No newline at end of file diff --git a/mvvmlin/src/main/java/com/aleyn/mvvm/base/BaseActivity.kt b/mvvmlin/src/main/java/com/aleyn/mvvm/base/BaseActivity.kt index 8da0bc1..da8ffbd 100644 --- a/mvvmlin/src/main/java/com/aleyn/mvvm/base/BaseActivity.kt +++ b/mvvmlin/src/main/java/com/aleyn/mvvm/base/BaseActivity.kt @@ -18,7 +18,7 @@ import java.lang.reflect.ParameterizedType import java.lang.reflect.Type /** - * @auther : Aleyn + * @author : Aleyn * time : 2019/11/01 */ abstract class BaseActivity : AppCompatActivity() { @@ -36,11 +36,13 @@ abstract class BaseActivity : AppCompatAct //注册 UI事件 registorDefUIChange() initView(savedInstanceState) + initObserve() initData() } open fun layoutId(): Int = 0 abstract fun initView(savedInstanceState: Bundle?) + open fun initObserve() {} abstract fun initData() @@ -78,18 +80,15 @@ abstract class BaseActivity : AppCompatAct * 注册 UI 事件 */ private fun registorDefUIChange() { - viewModel.defUI.showDialog.observe(this, { + viewModel.defUI.showDialog.observe(this) { showLoading() - }) - viewModel.defUI.dismissDialog.observe(this, { + } + viewModel.defUI.dismissDialog.observe(this) { dismissLoading() - }) - viewModel.defUI.toastEvent.observe(this, { - ToastUtils.showShort(it) - }) - viewModel.defUI.msgEvent.observe(this, { + } + viewModel.defUI.msgEvent.observe(this) { handleEvent(it) - }) + } } open fun handleEvent(msg: Message) {} @@ -123,12 +122,7 @@ abstract class BaseActivity : AppCompatAct @Suppress("UNCHECKED_CAST") private fun createViewModel(type: Type) { val tClass = type as? Class ?: BaseViewModel::class.java - viewModel = ViewModelProvider(viewModelStore, defaultViewModelProviderFactory) - .get(tClass) as VM - } - - override fun getDefaultViewModelProviderFactory(): ViewModelProvider.Factory { - return MVVMLin.getConfig().viewModelFactory() ?: super.getDefaultViewModelProviderFactory() + viewModel = ViewModelProvider(viewModelStore, defaultViewModelProviderFactory)[tClass] as VM } } \ No newline at end of file diff --git a/mvvmlin/src/main/java/com/aleyn/mvvm/base/BaseApplication.kt b/mvvmlin/src/main/java/com/aleyn/mvvm/base/BaseApplication.kt index 71e7e2c..f25d69e 100644 --- a/mvvmlin/src/main/java/com/aleyn/mvvm/base/BaseApplication.kt +++ b/mvvmlin/src/main/java/com/aleyn/mvvm/base/BaseApplication.kt @@ -2,11 +2,9 @@ package com.aleyn.mvvm.base import android.app.Application import android.content.Context -import com.aleyn.mvvm.app.GlobalConfig -import com.aleyn.mvvm.app.MVVMLin /** - * @auther : Aleyn + * @author : Aleyn * time : 2019/11/12 */ open class BaseApplication : Application() { diff --git a/mvvmlin/src/main/java/com/aleyn/mvvm/base/BaseFragment.kt b/mvvmlin/src/main/java/com/aleyn/mvvm/base/BaseFragment.kt index a30cebd..3bae7d2 100644 --- a/mvvmlin/src/main/java/com/aleyn/mvvm/base/BaseFragment.kt +++ b/mvvmlin/src/main/java/com/aleyn/mvvm/base/BaseFragment.kt @@ -14,20 +14,20 @@ import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.customview.customView import com.afollestad.materialdialogs.lifecycle.lifecycleOwner import com.aleyn.mvvm.R -import com.aleyn.mvvm.app.MVVMLin import com.aleyn.mvvm.event.Message -import com.blankj.utilcode.util.ToastUtils import java.lang.reflect.ParameterizedType /** - * @auther : Aleyn + * @author : Aleyn * time : 2019/11/01 */ abstract class BaseFragment : Fragment() { protected lateinit var viewModel: VM - protected lateinit var mBinding: DB + private var _binding: DB? = null + + protected val mBinding get() = _binding!! //是否第一次加载 private var isFirst: Boolean = true @@ -49,10 +49,13 @@ abstract class BaseFragment : Fragment() { createViewModel() lifecycle.addObserver(viewModel) //注册 UI事件 - registorDefUIChange() + registerDefUIChange() initView(savedInstanceState) + initObserve() } + open fun initObserve() {} + open fun initView(savedInstanceState: Bundle?) {} override fun onResume() { @@ -84,19 +87,16 @@ abstract class BaseFragment : Fragment() { /** * 注册 UI 事件 */ - private fun registorDefUIChange() { - viewModel.defUI.showDialog.observe(viewLifecycleOwner, { + private fun registerDefUIChange() { + viewModel.defUI.showDialog.observe(viewLifecycleOwner) { showLoading() - }) - viewModel.defUI.dismissDialog.observe(viewLifecycleOwner, { + } + viewModel.defUI.dismissDialog.observe(viewLifecycleOwner) { dismissLoading() - }) - viewModel.defUI.toastEvent.observe(viewLifecycleOwner, { - ToastUtils.showShort(it) - }) - viewModel.defUI.msgEvent.observe(viewLifecycleOwner, { + } + viewModel.defUI.msgEvent.observe(viewLifecycleOwner) { handleEvent(it) - }) + } } open fun handleEvent(msg: Message) {} @@ -131,14 +131,14 @@ abstract class BaseFragment : Fragment() { return when { ViewDataBinding::class.java.isAssignableFrom(cls) && cls != ViewDataBinding::class.java -> { if (layoutId() == 0) throw IllegalArgumentException("Using DataBinding requires overriding method layoutId") - mBinding = DataBindingUtil.inflate(inflater, layoutId(), container, false) + _binding = DataBindingUtil.inflate(inflater, layoutId(), container, false) (mBinding as ViewDataBinding).lifecycleOwner = this mBinding.root } ViewBinding::class.java.isAssignableFrom(cls) && cls != ViewBinding::class.java -> { cls.getDeclaredMethod("inflate", LayoutInflater::class.java).let { @Suppress("UNCHECKED_CAST") - mBinding = it.invoke(null, inflater) as DB + _binding = it.invoke(null, inflater) as DB mBinding.root } } @@ -153,8 +153,8 @@ abstract class BaseFragment : Fragment() { /** * 创建 ViewModel * - * 共享 ViewModel的时候,重写 Fragmnt 的 getViewModelStore() 方法, - * 返回 activity 的 ViewModelStore 或者 父 Fragmnt 的 ViewModelStore + * 共享 ViewModel的时候,重写 Fragment 的 getViewModelStore() 方法, + * 返回 activity 的 ViewModelStore 或者 父 Fragment 的 ViewModelStore */ @Suppress("UNCHECKED_CAST") private fun createViewModel() { @@ -162,13 +162,8 @@ abstract class BaseFragment : Fragment() { if (type is ParameterizedType) { val tp = type.actualTypeArguments[0] val tClass = tp as? Class ?: BaseViewModel::class.java - viewModel = ViewModelProvider(viewModelStore, defaultViewModelProviderFactory) - .get(tClass) as VM + viewModel = ViewModelProvider(this)[tClass] as VM } } - override fun getDefaultViewModelProviderFactory(): ViewModelProvider.Factory { - return MVVMLin.getConfig().viewModelFactory() ?: super.getDefaultViewModelProviderFactory() - } - } \ No newline at end of file diff --git a/mvvmlin/src/main/java/com/aleyn/mvvm/base/BaseModel.kt b/mvvmlin/src/main/java/com/aleyn/mvvm/base/BaseModel.kt index e736e5e..7ece7ba 100644 --- a/mvvmlin/src/main/java/com/aleyn/mvvm/base/BaseModel.kt +++ b/mvvmlin/src/main/java/com/aleyn/mvvm/base/BaseModel.kt @@ -1,35 +1,7 @@ package com.aleyn.mvvm.base -import com.aleyn.mvvm.network.ResponseThrowable - /** - * @auther : Aleyn + * @author : Aleyn * time : 2019/11/01 */ -abstract class BaseModel { - - /** - * @param remoto 网络数据 - * @param local 本地数据 - * @param save 当网络请求成功后,保存数据等操作 - * @param isUseCache 是否使用缓存 - */ - suspend fun cacheNetCall( - remoto: suspend () -> IBaseResponse, - local: suspend () -> T?, - save: suspend (T) -> Unit, - isUseCache: (T?) -> Boolean = { true } - ): T { - val localData = local.invoke() - return if (isUseCache(localData) && localData != null) localData - else { - remoto().let { net -> - if (net.isSuccess()) net.data().also { save(it) } - throw ResponseThrowable(net) - } - } - } - - fun onCleared() { - } -} \ No newline at end of file +abstract class BaseModel \ No newline at end of file diff --git a/mvvmlin/src/main/java/com/aleyn/mvvm/base/BaseViewModel.kt b/mvvmlin/src/main/java/com/aleyn/mvvm/base/BaseViewModel.kt index a997f0c..d508b21 100644 --- a/mvvmlin/src/main/java/com/aleyn/mvvm/base/BaseViewModel.kt +++ b/mvvmlin/src/main/java/com/aleyn/mvvm/base/BaseViewModel.kt @@ -1,25 +1,18 @@ package com.aleyn.mvvm.base -import android.app.Application -import androidx.lifecycle.AndroidViewModel -import androidx.lifecycle.LifecycleObserver +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.aleyn.mvvm.app.MVVMLin import com.aleyn.mvvm.event.Message import com.aleyn.mvvm.event.SingleLiveEvent -import com.aleyn.mvvm.network.ResponseThrowable -import com.blankj.utilcode.util.Utils import kotlinx.coroutines.* -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flow /** - * @auther : Aleyn + * @author : Aleyn * time : 2019/11/01 */ -open class BaseViewModel( - application: Application = Utils.getApp() -) : AndroidViewModel(application), LifecycleObserver { +open class BaseViewModel : ViewModel(), IViewModel, DefaultLifecycleObserver { val defUI: UIChange by lazy { UIChange() } @@ -27,102 +20,8 @@ open class BaseViewModel( * 所有网络请求都在 viewModelScope 域中启动,当页面销毁时会自动 * 调用ViewModel的 #onCleared 方法取消所有协程 */ - fun launchUI(block: suspend CoroutineScope.() -> Unit) = viewModelScope.launch { block() } - - /** - * 用流的方式进行网络请求 - */ - fun launchFlow(block: suspend () -> T): Flow { - return flow { - emit(block()) - } - } - - /** - * 不过滤请求结果 - * @param block 请求体 - * @param error 失败回调 - * @param complete 完成回调(无论成功失败都会调用) - * @param isShowDialog 是否显示加载框 - */ - fun launchGo( - block: suspend CoroutineScope.() -> Unit, - error: suspend CoroutineScope.(ResponseThrowable) -> Unit = { - defUI.toastEvent.postValue("${it.code}:${it.errMsg}") - }, - complete: suspend CoroutineScope.() -> Unit = {}, - isShowDialog: Boolean = true - ) { - if (isShowDialog) defUI.showDialog.call() - launchUI { - handleException( - withContext(Dispatchers.IO) { block }, - { error(it) }, - { - defUI.dismissDialog.call() - complete() - } - ) - } - } - - /** - * 过滤请求结果,其他全抛异常 - * @param block 请求体 - * @param success 成功回调 - * @param error 失败回调 - * @param complete 完成回调(无论成功失败都会调用) - * @param isShowDialog 是否显示加载框 - */ - fun launchOnlyresult( - block: suspend CoroutineScope.() -> IBaseResponse, - success: (T) -> Unit, - error: (ResponseThrowable) -> Unit = { - defUI.toastEvent.postValue("${it.code}:${it.errMsg}") - }, - complete: () -> Unit = {}, - isShowDialog: Boolean = true - ) { - if (isShowDialog) defUI.showDialog.call() - launchUI { - handleException( - { - withContext(Dispatchers.IO) { - block().let { - if (it.isSuccess()) it.data() - else throw ResponseThrowable(it.code(), it.msg()) - } - }.also { success(it) } - }, - { error(it) }, - { - defUI.dismissDialog.call() - complete() - } - ) - } - } - - - /** - * 异常统一处理 - */ - private suspend fun handleException( - block: suspend CoroutineScope.() -> Unit, - error: suspend CoroutineScope.(ResponseThrowable) -> Unit, - complete: suspend CoroutineScope.() -> Unit - ) { - coroutineScope { - try { - block() - } catch (e: Throwable) { - error(MVVMLin.getConfig().globalExceptionHandle(e)) - } finally { - complete() - } - } - } - + fun launch(block: suspend CoroutineScope.() -> Unit) = + viewModelScope.launch(MVVMLin.netException) { block() } /** * UI事件 @@ -130,7 +29,14 @@ open class BaseViewModel( inner class UIChange { val showDialog by lazy { SingleLiveEvent() } val dismissDialog by lazy { SingleLiveEvent() } - val toastEvent by lazy { SingleLiveEvent() } val msgEvent by lazy { SingleLiveEvent() } } + + override fun showLoading(text: String) { + defUI.showDialog.postValue(text) + } + + override fun dismissLoading() { + defUI.dismissDialog.call() + } } \ No newline at end of file diff --git a/mvvmlin/src/main/java/com/aleyn/mvvm/base/IBaseResponse.kt b/mvvmlin/src/main/java/com/aleyn/mvvm/base/IBaseResponse.kt index e54277f..e0d8021 100644 --- a/mvvmlin/src/main/java/com/aleyn/mvvm/base/IBaseResponse.kt +++ b/mvvmlin/src/main/java/com/aleyn/mvvm/base/IBaseResponse.kt @@ -1,7 +1,7 @@ package com.aleyn.mvvm.base /** - * @auther : Aleyn + * @author : Aleyn * time : 2020/01/13 */ interface IBaseResponse { diff --git a/mvvmlin/src/main/java/com/aleyn/mvvm/base/IViewModel.kt b/mvvmlin/src/main/java/com/aleyn/mvvm/base/IViewModel.kt new file mode 100644 index 0000000..0d9324b --- /dev/null +++ b/mvvmlin/src/main/java/com/aleyn/mvvm/base/IViewModel.kt @@ -0,0 +1,13 @@ +package com.aleyn.mvvm.base + +/** + * @author : Aleyn + * @date : 2022/07/31 : 20:28 + */ +interface IViewModel { + + fun showLoading(text: String) + + fun dismissLoading() + +} \ No newline at end of file diff --git a/mvvmlin/src/main/java/com/aleyn/mvvm/base/NoViewModel.kt b/mvvmlin/src/main/java/com/aleyn/mvvm/base/NoViewModel.kt index 3f7fc36..5b305f9 100644 --- a/mvvmlin/src/main/java/com/aleyn/mvvm/base/NoViewModel.kt +++ b/mvvmlin/src/main/java/com/aleyn/mvvm/base/NoViewModel.kt @@ -1,7 +1,7 @@ package com.aleyn.mvvm.base /** - * @auther : Aleyn + * @author : Aleyn * time : 2019/11/01 */ class NoViewModel : BaseViewModel() \ No newline at end of file diff --git a/mvvmlin/src/main/java/com/aleyn/mvvm/base/ViewModelFactory.kt b/mvvmlin/src/main/java/com/aleyn/mvvm/base/ViewModelFactory.kt deleted file mode 100644 index e4ccdcb..0000000 --- a/mvvmlin/src/main/java/com/aleyn/mvvm/base/ViewModelFactory.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.aleyn.mvvm.base - -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.ViewModelProvider.NewInstanceFactory - -/** - * @auther : Aleyn - * time : 2019/11/01 - */ -class ViewModelFactory : ViewModelProvider.Factory { - - companion object { - private var sInstance: ViewModelFactory? = null - - fun getInstance() = sInstance ?: ViewModelFactory().also { sInstance = it } - } - - - override fun create(modelClass: Class): T { - /* val type = modelClass.constructors[0].parameterTypes - if (type.isNotEmpty()) { - val tClass = type[0] - if (HomeRepository::class.java.isAssignableFrom(tClass)) { - return modelClass.getConstructor(tClass).newInstance(Injection.HomeRepository()) - } else if (XXXRepository::class.java.isAssignableFrom(tClass)) { - return modelClass.getConstructor(tClass).newInstance(Injection.XXXRepository()) - } - }*/ - return modelClass.newInstance() - } -} \ No newline at end of file diff --git a/mvvmlin/src/main/java/com/aleyn/mvvm/binding/ImageAdapter.kt b/mvvmlin/src/main/java/com/aleyn/mvvm/binding/ImageAdapter.kt index 5b230e9..0959913 100644 --- a/mvvmlin/src/main/java/com/aleyn/mvvm/binding/ImageAdapter.kt +++ b/mvvmlin/src/main/java/com/aleyn/mvvm/binding/ImageAdapter.kt @@ -5,7 +5,7 @@ import androidx.databinding.BindingAdapter import coil.load /** - * @auther : Aleyn + * @author : Aleyn * time : 2019/11/07 */ object ImageAdapter { diff --git a/mvvmlin/src/main/java/com/aleyn/mvvm/binding/TabLayoutAdapter.kt b/mvvmlin/src/main/java/com/aleyn/mvvm/binding/TabLayoutAdapter.kt index a7816ac..20c6d83 100644 --- a/mvvmlin/src/main/java/com/aleyn/mvvm/binding/TabLayoutAdapter.kt +++ b/mvvmlin/src/main/java/com/aleyn/mvvm/binding/TabLayoutAdapter.kt @@ -4,7 +4,7 @@ import androidx.databinding.BindingAdapter import com.google.android.material.tabs.TabLayout /** - * @auther : Aleyn + * @author : Aleyn * time : 2019/11/13 */ object TabLayoutAdapter { diff --git a/mvvmlin/src/main/java/com/aleyn/mvvm/event/Message.kt b/mvvmlin/src/main/java/com/aleyn/mvvm/event/Message.kt index 2a0a2ff..a5a91cd 100644 --- a/mvvmlin/src/main/java/com/aleyn/mvvm/event/Message.kt +++ b/mvvmlin/src/main/java/com/aleyn/mvvm/event/Message.kt @@ -1,7 +1,7 @@ package com.aleyn.mvvm.event /** - * @auther : Aleyn + * @author : Aleyn * time : 2019/11/13 */ class Message @JvmOverloads constructor( diff --git a/mvvmlin/src/main/java/com/aleyn/mvvm/extend/NetKtx.kt b/mvvmlin/src/main/java/com/aleyn/mvvm/extend/NetKtx.kt new file mode 100644 index 0000000..9bd40ed --- /dev/null +++ b/mvvmlin/src/main/java/com/aleyn/mvvm/extend/NetKtx.kt @@ -0,0 +1,69 @@ +package com.aleyn.mvvm.extend + +import com.aleyn.mvvm.base.IBaseResponse +import com.aleyn.mvvm.base.IViewModel +import com.aleyn.mvvm.network.ERROR +import com.aleyn.mvvm.network.ExceptionHandle +import com.aleyn.mvvm.network.ResponseThrowable +import kotlinx.coroutines.flow.* + +/** + * @author : Aleyn + * @date : 2022/07/31 : 11:51 + */ + +/** + * Flow 转换结果 + */ +fun Flow>.asResponse(): Flow = transform { value -> + return@transform if (value.isSuccess() && value.data() != null) { + emit(value.data()) + } else if (value.isSuccess() && value.data() == null) { + throw ResponseThrowable(ERROR.DATA_NULL) + } else throw ResponseThrowable(value) +} + +/** + * 转换结果(只在成功后做处理,不关心 data 值) + */ +fun Flow>.asSuccess(): Flow = transform { value -> + return@transform if (value.isSuccess()) { + emit(value.data()) + } else throw ResponseThrowable(value) +} + +/** + * 绑定Loading + */ +fun Flow.bindLoading(model: IViewModel, text: String = "") = onStart { + model.showLoading(text) +}.onCompletion { + model.dismissLoading() +} + + +/** + * 网络错误 + */ +fun Flow.netCache(action: (ResponseThrowable) -> Unit) = catch { + val exception = ExceptionHandle.handleException(it) + action(exception) +} + +/** + * 只取成功结果其他抛异常 + */ +fun IBaseResponse.getOrThrow(): T { + return if (isSuccess() && data() != null) { + data() + } else if (isSuccess() && data() == null) { + throw ResponseThrowable(ERROR.DATA_NULL) + } else throw ResponseThrowable(this) +} + +/** + * 检测成功结果其他抛异常(不关心 data ) + */ +fun IBaseResponse.check() { + if (!isSuccess()) throw ResponseThrowable(this) +} \ No newline at end of file diff --git a/mvvmlin/src/main/java/com/aleyn/mvvm/network/ERROR.kt b/mvvmlin/src/main/java/com/aleyn/mvvm/network/ERROR.kt index 3f0ba04..f3b3358 100644 --- a/mvvmlin/src/main/java/com/aleyn/mvvm/network/ERROR.kt +++ b/mvvmlin/src/main/java/com/aleyn/mvvm/network/ERROR.kt @@ -1,23 +1,26 @@ package com.aleyn.mvvm.network /** - * @auther : Aleyn + * @author : Aleyn * time : 2019/08/12 */ -enum class ERROR(private val code: Int, private val err: String) { +enum class ERROR(val code: Int, val err: String) { /** * 未知错误 */ UNKNOWN(1000, "未知错误"), + /** * 解析错误 */ PARSE_ERROR(1001, "解析错误"), + /** * 网络错误 */ - NETWORD_ERROR(1002, "网络错误"), + NETWORK_ERROR(1002, "网络错误"), + /** * 协议出错 */ @@ -31,14 +34,11 @@ enum class ERROR(private val code: Int, private val err: String) { /** * 连接超时 */ - TIMEOUT_ERROR(1006, "连接超时"); + TIMEOUT_ERROR(1006, "连接超时"), - fun getValue(): String { - return err - } - - fun getKey(): Int { - return code - } + /** + * data为空 + */ + DATA_NULL(1007, "返回数据为空"); } \ No newline at end of file diff --git a/mvvmlin/src/main/java/com/aleyn/mvvm/network/ExceptionHandle.kt b/mvvmlin/src/main/java/com/aleyn/mvvm/network/ExceptionHandle.kt index 9a54dc3..caa349e 100644 --- a/mvvmlin/src/main/java/com/aleyn/mvvm/network/ExceptionHandle.kt +++ b/mvvmlin/src/main/java/com/aleyn/mvvm/network/ExceptionHandle.kt @@ -8,33 +8,39 @@ import retrofit2.HttpException import java.net.ConnectException /** - * @auther : Aleyn + * @author : Aleyn * time : 2019/08/12 */ object ExceptionHandle { fun handleException(e: Throwable): ResponseThrowable { val ex: ResponseThrowable - if (e is ResponseThrowable) { - ex = e - } else if (e is HttpException) { - ex = ResponseThrowable(ERROR.HTTP_ERROR, e) - } else if (e is JsonParseException - || e is JSONException - || e is ParseException || e is MalformedJsonException - ) { - ex = ResponseThrowable(ERROR.PARSE_ERROR, e) - } else if (e is ConnectException) { - ex = ResponseThrowable(ERROR.NETWORD_ERROR, e) - } else if (e is javax.net.ssl.SSLException) { - ex = ResponseThrowable(ERROR.SSL_ERROR, e) - } else if (e is java.net.SocketTimeoutException) { - ex = ResponseThrowable(ERROR.TIMEOUT_ERROR, e) - } else if (e is java.net.UnknownHostException) { - ex = ResponseThrowable(ERROR.TIMEOUT_ERROR, e) - } else { - ex = if (!e.message.isNullOrEmpty()) ResponseThrowable(1000, e.message!!, e) - else ResponseThrowable(ERROR.UNKNOWN, e) + when (e) { + is ResponseThrowable -> { + ex = e + } + is HttpException -> { + ex = ResponseThrowable(ERROR.HTTP_ERROR, e) + } + is JsonParseException, is JSONException, is ParseException, is MalformedJsonException -> { + ex = ResponseThrowable(ERROR.PARSE_ERROR, e) + } + is ConnectException -> { + ex = ResponseThrowable(ERROR.NETWORK_ERROR, e) + } + is javax.net.ssl.SSLException -> { + ex = ResponseThrowable(ERROR.SSL_ERROR, e) + } + is java.net.SocketTimeoutException -> { + ex = ResponseThrowable(ERROR.TIMEOUT_ERROR, e) + } + is java.net.UnknownHostException -> { + ex = ResponseThrowable(ERROR.TIMEOUT_ERROR, e) + } + else -> { + ex = if (!e.message.isNullOrEmpty()) ResponseThrowable(1000, e.message!!, e) + else ResponseThrowable(ERROR.UNKNOWN, e) + } } return ex } diff --git a/mvvmlin/src/main/java/com/aleyn/mvvm/network/ResponseThrowable.kt b/mvvmlin/src/main/java/com/aleyn/mvvm/network/ResponseThrowable.kt index 1ae5f40..96a2994 100644 --- a/mvvmlin/src/main/java/com/aleyn/mvvm/network/ResponseThrowable.kt +++ b/mvvmlin/src/main/java/com/aleyn/mvvm/network/ResponseThrowable.kt @@ -3,7 +3,7 @@ package com.aleyn.mvvm.network import com.aleyn.mvvm.base.IBaseResponse /** - * @auther : Aleyn + * @author : Aleyn * time : 2019/08/12 */ open class ResponseThrowable : Exception { @@ -11,8 +11,8 @@ open class ResponseThrowable : Exception { var errMsg: String constructor(error: ERROR, e: Throwable? = null) : super(e) { - code = error.getKey() - errMsg = error.getValue() + code = error.code + errMsg = error.err } constructor(code: Int, msg: String, e: Throwable? = null) : super(e) { diff --git a/mvvmlin/src/main/java/com/aleyn/mvvm/network/interceptor/BaseInterceptor.kt b/mvvmlin/src/main/java/com/aleyn/mvvm/network/interceptor/BaseInterceptor.kt deleted file mode 100644 index be37d4c..0000000 --- a/mvvmlin/src/main/java/com/aleyn/mvvm/network/interceptor/BaseInterceptor.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.aleyn.mvvm.network.interceptor - -import okhttp3.Interceptor -import okhttp3.Response - -/** - * @auther : Aleyn - * time : 2019/11/02 - */ -class BaseInterceptor(private val headers: Map?) : Interceptor { - - override fun intercept(chain: Interceptor.Chain): Response { - return chain.proceed(chain.request().newBuilder().run { - if (!headers.isNullOrEmpty()) { - for (headMap in headers) { - addHeader(headMap.key, headMap.value).build() - } - } - build() - }) - } -} \ No newline at end of file diff --git a/mvvmlin/src/main/java/com/aleyn/mvvm/network/interceptor/DefaultPrinter.kt b/mvvmlin/src/main/java/com/aleyn/mvvm/network/interceptor/DefaultPrinter.kt deleted file mode 100644 index 82c83ba..0000000 --- a/mvvmlin/src/main/java/com/aleyn/mvvm/network/interceptor/DefaultPrinter.kt +++ /dev/null @@ -1,240 +0,0 @@ -package com.aleyn.mvvm.network.interceptor - -import android.text.TextUtils -import com.blankj.utilcode.util.JsonUtils -import okhttp3.MediaType -import okhttp3.Request -import okhttp3.internal.platform.Platform.INFO - -/** - * @auther : Aleyn - * time : 2020/07/24 - */ -class DefaultPrinter(private val logger: LoggingInterceptor.Logger = LoggingInterceptor.Logger.DEFAULT) : - FormatPrinter { - - /** - * @param request - * @param bodyString - */ - override fun printJsonRequest( - request: Request?, - bodyString: String? - ) { - val requestBody = LINE_SEPARATOR + BODY_TAG + LINE_SEPARATOR + bodyString - val tag = getTag(true) - debugInfo(tag, REQUEST_UP_LINE) - logLines(tag, arrayOf(URL_TAG + request!!.url()), false) - logLines(tag, getRequest(request), true) - logLines(tag, requestBody.split(LINE_SEPARATOR.toRegex()).toTypedArray(), true) - debugInfo(tag, END_LINE) - } - - /** - * @param request - */ - override fun printFileRequest(request: Request?) { - val tag = getTag(true) - debugInfo(tag, REQUEST_UP_LINE) - logLines(tag, arrayOf(URL_TAG + request!!.url()), false) - logLines(tag, getRequest(request), true) - logLines(tag, OMITTED_REQUEST, true) - debugInfo(tag, END_LINE) - } - - /** - * - * @param chainMs 服务器响应耗时(单位毫秒) - * @param isSuccessful 请求是否成功 - * @param code 响应码 - * @param headers 请求头 - * @param contentType 服务器返回数据的数据类型 - * @param bodyString 服务器返回的数据(已解析) - * @param segments 域名后面的资源地址 - * @param message 响应信息 - * @param responseUrl 请求地址 - */ - override fun printJsonResponse( - chainMs: Long, - isSuccessful: Boolean, - code: Int, - headers: String?, - contentType: MediaType?, - bodyString: String?, - segments: List?, - message: String?, - responseUrl: String? - ) { - var tempString = bodyString - tempString = if (LoggingInterceptor.isJson(contentType)) JsonUtils.formatJson(tempString) - else tempString - val responseBody = LINE_SEPARATOR + BODY_TAG + LINE_SEPARATOR + tempString - val tag = getTag(false) - val urlLine = arrayOf(URL_TAG + responseUrl, N) - debugInfo(tag, RESPONSE_UP_LINE) - logLines(tag, urlLine, true) - logLines(tag, getResponse(headers, chainMs, code, isSuccessful, segments, message), true) - logLines(tag, responseBody.split(LINE_SEPARATOR.toRegex()).toTypedArray(), true) - debugInfo(tag, END_LINE) - } - - /** - * - * @param chainMs 服务器响应耗时(单位毫秒) - * @param isSuccessful 请求是否成功 - * @param code 响应码 - * @param headers 请求头 - * @param segments 域名后面的资源地址 - * @param message 响应信息 - * @param responseUrl 请求地址 - */ - override fun printFileResponse( - chainMs: Long, - isSuccessful: Boolean, - code: Int, - headers: String?, - segments: List?, - message: String?, - responseUrl: String? - ) { - val tag = getTag(false) - val urlLine = arrayOf(URL_TAG + responseUrl, N) - debugInfo(tag, RESPONSE_UP_LINE) - logLines(tag, urlLine, true) - logLines(tag, getResponse(headers, chainMs, code, isSuccessful, segments, message), true) - logLines(tag, OMITTED_RESPONSE, true) - debugInfo(tag, END_LINE) - } - - companion object { - private val LINE_SEPARATOR = System.getProperty("line.separator") ?: "\n" - private val DOUBLE_SEPARATOR = - LINE_SEPARATOR + LINE_SEPARATOR - private val OMITTED_RESPONSE = arrayOf( - LINE_SEPARATOR, - "Omitted response body" - ) - private val OMITTED_REQUEST = arrayOf( - LINE_SEPARATOR, - "Omitted request body" - ) - private const val N = "\n" - private const val T = "\t" - private const val REQUEST_UP_LINE = - "┌────── Request ────────────────────────────────────────────────────────────────────────" - private const val END_LINE = - "└───────────────────────────────────────────────────────────────────────────────────────" - private const val RESPONSE_UP_LINE = - "┌────── Response ───────────────────────────────────────────────────────────────────────" - private const val BODY_TAG = "Body:" - private const val URL_TAG = "URL: " - private const val METHOD_TAG = "Method: @" - private const val HEADERS_TAG = "Headers:" - private const val STATUS_CODE_TAG = "Status Code: " - private const val RECEIVED_TAG = "Received in: " - private const val CORNER_UP = "┌ " - private const val CORNER_BOTTOM = "└ " - private const val CENTER_LINE = "├ " - private const val DEFAULT_LINE = "│ " - private fun isEmpty(line: String?): Boolean { - return TextUtils.isEmpty(line) || N == line || T == line || TextUtils.isEmpty( - line!!.trim { it <= ' ' } - ) - } - } - - /** - * 对 `lines` 中的信息进行逐行打印 - * - * @param tag - * @param lines - * @param withLineSize 为 `true` 时, 每行的信息长度不会超过110, 超过则自动换行 - */ - private fun logLines(tag: String, lines: Array, withLineSize: Boolean) { - for (line in lines) { - val lineLength = line.length - val maxSize = if (withLineSize) 110 else lineLength - for (i in 0..lineLength / maxSize) { - val start = i * maxSize - var end = (i + 1) * maxSize - end = if (end > line.length) line.length else end - debugInfo(tag, DEFAULT_LINE + line.substring(start, end)) - } - } - } - - private fun getRequest(request: Request?): Array { - val log: String - val header = request!!.headers().toString() - log = METHOD_TAG + request.method() + DOUBLE_SEPARATOR + if (isEmpty(header)) "" - else HEADERS_TAG + LINE_SEPARATOR + dotHeaders(header) - return log.split(LINE_SEPARATOR.toRegex()).toTypedArray() - } - - private fun getResponse( - header: String?, tookMs: Long, code: Int, isSuccessful: Boolean, - segments: List?, message: String? - ): Array { - val log: String - val segmentString = - slashSegments(segments) - log = - ((if (!TextUtils.isEmpty(segmentString)) "$segmentString - " else "") + "is success : " - + isSuccessful + " - " + RECEIVED_TAG + tookMs + "ms" + DOUBLE_SEPARATOR + STATUS_CODE_TAG + - code + " / " + message + DOUBLE_SEPARATOR + if (isEmpty( - header - ) - ) "" else HEADERS_TAG + LINE_SEPARATOR + - dotHeaders(header)) - return log.split(LINE_SEPARATOR.toRegex()).toTypedArray() - } - - private fun slashSegments(segments: List?): String { - val segmentString = StringBuilder() - for (segment in segments!!) { - segmentString.append("/").append(segment) - } - return segmentString.toString() - } - - /** - * 对 `header` 按规定的格式进行处理 - * - * @param header - * @return - */ - private fun dotHeaders(header: String?): String { - val headers = - header!!.split(LINE_SEPARATOR.toRegex()).toTypedArray() - val builder = StringBuilder() - var tag = "─ " - if (headers.size > 1) { - for (i in headers.indices) { - tag = when (i) { - 0 -> CORNER_UP - headers.size - 1 -> CORNER_BOTTOM - else -> CENTER_LINE - } - builder.append(tag).append(headers[i]).append("\n") - } - } else { - for (item in headers) { - builder.append(tag).append(item).append("\n") - } - } - return builder.toString() - } - - private fun getTag(isRequest: Boolean): String { - return if (isRequest) { - "-Request " - } else { - "-Response " - } - } - - private fun debugInfo(tag: String, msg: String) { - if (TextUtils.isEmpty(msg)) return - logger.log(INFO, tag, msg) - } -} \ No newline at end of file diff --git a/mvvmlin/src/main/java/com/aleyn/mvvm/network/interceptor/FormatPrinter.kt b/mvvmlin/src/main/java/com/aleyn/mvvm/network/interceptor/FormatPrinter.kt deleted file mode 100644 index 40bb1c6..0000000 --- a/mvvmlin/src/main/java/com/aleyn/mvvm/network/interceptor/FormatPrinter.kt +++ /dev/null @@ -1,60 +0,0 @@ -package com.aleyn.mvvm.network.interceptor - -import okhttp3.MediaType -import okhttp3.Request - -/** - * @auther : Aleyn - * time : 2020/07/24 - */ -interface FormatPrinter { - - - fun printJsonRequest(request: Request?, bodyString: String?) - - - fun printFileRequest(request: Request?) - - /** - * - * @param chainMs 服务器响应耗时(单位毫秒) - * @param isSuccessful 请求是否成功 - * @param code 响应码 - * @param headers 请求头 - * @param contentType 服务器返回数据的数据类型 - * @param bodyString 服务器返回的数据(已解析) - * @param segments 域名后面的资源地址 - * @param message 响应信息 - * @param responseUrl 请求地址 - */ - fun printJsonResponse( - chainMs: Long, - isSuccessful: Boolean, - code: Int, - headers: String?, - contentType: MediaType?, - bodyString: String?, - segments: List?, - message: String?, - responseUrl: String? - ) - - /** - * @param chainMs 服务器响应耗时(单位毫秒) - * @param isSuccessful 请求是否成功 - * @param code 响应码 - * @param headers 请求头 - * @param segments 域名后面的资源地址 - * @param message 响应信息 - * @param responseUrl 请求地址 - */ - fun printFileResponse( - chainMs: Long, - isSuccessful: Boolean, - code: Int, - headers: String?, - segments: List?, - message: String?, - responseUrl: String? - ) -} \ No newline at end of file diff --git a/mvvmlin/src/main/java/com/aleyn/mvvm/network/interceptor/LoggingInterceptor.kt b/mvvmlin/src/main/java/com/aleyn/mvvm/network/interceptor/LoggingInterceptor.kt deleted file mode 100644 index 9dca1cd..0000000 --- a/mvvmlin/src/main/java/com/aleyn/mvvm/network/interceptor/LoggingInterceptor.kt +++ /dev/null @@ -1,266 +0,0 @@ -package com.aleyn.mvvm.network.interceptor - -import com.blankj.utilcode.util.JsonUtils -import com.blankj.utilcode.util.LogUtils -import okhttp3.* -import okhttp3.internal.platform.Platform -import okio.Buffer -import java.io.IOException -import java.io.UnsupportedEncodingException -import java.net.URLDecoder -import java.nio.charset.Charset -import java.nio.charset.StandardCharsets -import java.util.* -import java.util.concurrent.TimeUnit - -/** - * @auther : Aleyn - * time : 2020/07/24 - */ -class LoggingInterceptor( - private var printLevel: Level = Level.ALL, - private val mPrinter: FormatPrinter = DefaultPrinter() -) : Interceptor { - - - interface Logger { - fun log(level: Int, tag: String, msg: String) - - companion object { - val DEFAULT: Logger = object : Logger { - override fun log(level: Int, tag: String, msg: String) { - Platform.get().log(level, tag + msg, null) - } - } - } - } - - - enum class Level { - /** - * 不打印log - */ - NONE, - - /** - * 只打印请求信息 - */ - REQUEST, - - /** - * 只打印响应信息 - */ - RESPONSE, - - /** - * 所有数据全部打印 - */ - ALL - } - - @Throws(IOException::class) - override fun intercept(chain: Interceptor.Chain): Response { - val request = chain.request() - val logRequest = - printLevel == Level.ALL || printLevel != Level.NONE && printLevel == Level.REQUEST - if (logRequest) { - //打印请求信息 - if (request.body() != null && isParseable( - request.body()!!.contentType() - ) - ) { - mPrinter.printJsonRequest( - request, - parseParams(request) - ) - } else { - mPrinter.printFileRequest(request) - } - } - val logResponse = - printLevel == Level.ALL || printLevel != Level.NONE && printLevel == Level.RESPONSE - val t1 = if (logResponse) System.nanoTime() else 0 - val originalResponse: Response - originalResponse = try { - chain.proceed(request) - } catch (e: Exception) { - LogUtils.w("Http Error: $e") - throw e - } - val t2 = if (logResponse) System.nanoTime() else 0 - val responseBody = originalResponse.body() - - //打印响应结果 - var bodyString: String? = null - if (responseBody != null && isParseable(responseBody.contentType())) { - bodyString = printResult(request, originalResponse, logResponse) - } - if (logResponse) { - val segmentList = - request.url().encodedPathSegments() - val header = originalResponse.headers().toString() - val code = originalResponse.code() - val isSuccessful = originalResponse.isSuccessful - val message = originalResponse.message() - val url = originalResponse.request().url().toString() - if (responseBody != null && isParseable(responseBody.contentType())) { - mPrinter.printJsonResponse( - TimeUnit.NANOSECONDS.toMillis(t2 - t1), isSuccessful, - code, header, responseBody.contentType(), bodyString, segmentList, message, url - ) - } else { - mPrinter.printFileResponse( - TimeUnit.NANOSECONDS.toMillis(t2 - t1), - isSuccessful, code, header, segmentList, message, url - ) - } - } - return originalResponse - } - - /** - * 打印响应结果 - * - * @param request [Request] - * @param response [Response] - * @param logResponse 是否打印响应结果 - * @return 解析后的响应结果 - * @throws IOException - */ - @Throws(IOException::class) - private fun printResult( - request: Request, - response: Response, - logResponse: Boolean - ): String? { - return try { - //读取服务器返回的结果 - val responseBody = response.newBuilder().build().body() - val source = responseBody!!.source() - source.request(Long.MAX_VALUE) // Buffer the entire body. - val buffer = source.buffer() - - //获取content的压缩类型 - val encoding = response - .headers()["Content-Encoding"] - val clone = buffer.clone() - - //解析response content - parseContent(responseBody, encoding, clone) - } catch (e: IOException) { - e.printStackTrace() - "{\"error\": \"" + e.message + "\"}" - } - } - - /** - * 解析服务器响应的内容 - * - * @param responseBody [ResponseBody] - * @param encoding 编码类型 - * @param clone 克隆后的服务器响应内容 - * @return 解析后的响应结果 - */ - private fun parseContent( - responseBody: ResponseBody?, - encoding: String?, - clone: Buffer - ): String? { - var charset = StandardCharsets.UTF_8 - val contentType = responseBody!!.contentType() - if (contentType != null) { - charset = contentType.charset(charset) - } - return clone.readString(charset) - } - - companion object { - /** - * 解析请求服务器的请求参数 - * - * @param request [Request] - * @return 解析后的请求信息 - * @throws UnsupportedEncodingException - */ - @Throws(UnsupportedEncodingException::class) - fun parseParams(request: Request): String { - return try { - val body = request.newBuilder().build().body() ?: return "" - val requestbuffer = Buffer() - body.writeTo(requestbuffer) - var charset = Charset.forName("UTF-8") - val contentType = body.contentType() - if (contentType != null) { - charset = contentType.charset(charset) - } - var json = requestbuffer.readString(charset) - if (hasUrlEncoded(json)) { - json = URLDecoder.decode( - json, - convertCharset(charset) - ) - } - JsonUtils.formatJson(json) - } catch (e: IOException) { - e.printStackTrace() - "{\"error\": \"" + e.message + "\"}" - } - } - - /** - * 是否可以解析 - * - * @param mediaType [MediaType] - * @return `true` 为可以解析 - */ - fun isParseable(mediaType: MediaType?): Boolean { - return if (mediaType == null) false else isText(mediaType) || isPlain(mediaType) - || isJson(mediaType) || isForm(mediaType) - } - - private fun isText(mediaType: MediaType?): Boolean { - return if (mediaType?.type() == null) false else mediaType.type() == "text" - } - - private fun isPlain(mediaType: MediaType?): Boolean { - return if (mediaType?.subtype() == null) false else mediaType.subtype() - .toLowerCase(Locale.ROOT).contains("plain") - } - - fun isJson(mediaType: MediaType?): Boolean { - return if (mediaType?.subtype() == null) false else mediaType.subtype() - .toLowerCase(Locale.ROOT).contains("json") - } - - private fun isForm(mediaType: MediaType?): Boolean { - return if (mediaType?.subtype() == null) false else mediaType.subtype() - .toLowerCase(Locale.ROOT).contains("x-www-form-urlencoded") - } - - private fun convertCharset(charset: Charset?): String { - val s = charset.toString() - val i = s.indexOf("[") - return if (i == -1) s else s.substring(i + 1, s.length - 1) - } - - private fun hasUrlEncoded(str: String): Boolean { - var encode = false - for (i in str.indices) { - val c = str[i] - if (c == '%' && i + 2 < str.length) { - // 判断是否符合urlEncode规范 - val c1 = str[i + 1] - val c2 = str[i + 2] - if (isValidHexChar(c1) && isValidHexChar(c2)) { - encode = true - break - } else break - } - } - return encode - } - - private fun isValidHexChar(c: Char): Boolean = - c in '0'..'9' || c in 'a'..'f' || c in 'A'..'F' - } -} \ No newline at end of file diff --git a/mvvmlin/src/main/java/com/aleyn/mvvm/utils/Extend.kt b/mvvmlin/src/main/java/com/aleyn/mvvm/utils/Extend.kt deleted file mode 100644 index 0a097be..0000000 --- a/mvvmlin/src/main/java/com/aleyn/mvvm/utils/Extend.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.aleyn.mvvm.utils - -import androidx.viewbinding.ViewBinding -import com.aleyn.mvvm.base.IBaseResponse -import com.aleyn.mvvm.network.ResponseThrowable -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flowOn -import kotlinx.coroutines.flow.map - -@ExperimentalCoroutinesApi -fun Flow>.applyTransform(): Flow { - return this - .flowOn(Dispatchers.IO) - .map { - if (it.isSuccess()) return@map it.data() - else throw ResponseThrowable(it.code(), it.msg()) - } -} - -//fun ViewBinding. \ No newline at end of file diff --git a/settings.gradle b/settings.gradle deleted file mode 100644 index 0ed7430..0000000 --- a/settings.gradle +++ /dev/null @@ -1 +0,0 @@ -include ':app', ':mvvmlin' diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..cdff3a1 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,20 @@ +pluginManagement { + repositories { + gradlePluginPortal() + google() + mavenCentral() + } +} + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + maven { + setUrl("https://jitpack.io") + } + } +} +rootProject.name = "MVVMLin" +include(":app", ":mvvmlin")