├── app ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── drawable-xxhdpi │ │ │ │ ├── ic_kg.webp │ │ │ │ ├── ic_bell.webp │ │ │ │ ├── ic_call.webp │ │ │ │ ├── ic_sort.webp │ │ │ │ ├── ic_time.webp │ │ │ │ ├── ic_address.webp │ │ │ │ ├── ic_dropdown.webp │ │ │ │ ├── ic_password.webp │ │ │ │ ├── ic_personal.webp │ │ │ │ ├── ic_arrow_back.webp │ │ │ │ ├── ic_phone_number.webp │ │ │ │ └── ic_default_avatar.webp │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── drawable-xxxhdpi │ │ │ │ └── ic_github.webp │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── drawable │ │ │ │ ├── shape_green_view.xml │ │ │ │ ├── shape_order_background_color.xml │ │ │ │ ├── shape_common_edit_text_background.xml │ │ │ │ ├── ic_circle_c.xml │ │ │ │ ├── ic_circle_css.xml │ │ │ │ ├── ic_circle_go.xml │ │ │ │ ├── ic_circle_java.xml │ │ │ │ ├── ic_circle_other.xml │ │ │ │ ├── ic_circle_php.xml │ │ │ │ ├── ic_circle_ruby.xml │ │ │ │ ├── ic_circle_swift.xml │ │ │ │ ├── ic_circle_kotlin.xml │ │ │ │ ├── ic_circle_python.xml │ │ │ │ ├── ic_circle_c_plus_plus.xml │ │ │ │ ├── ic_circle_java_script.xml │ │ │ │ ├── ic_star.xml │ │ │ │ ├── selector_common_button_background_color.xml │ │ │ │ ├── ic_fork.xml │ │ │ │ └── ic_launcher_background.xml │ │ │ ├── layout │ │ │ │ ├── layout_divider_line.xml │ │ │ │ ├── activity_register_and_login.xml │ │ │ │ ├── activity_splash.xml │ │ │ │ ├── layout_toolbar.xml │ │ │ │ ├── layout_loading.xml │ │ │ │ ├── fragment_repository.xml │ │ │ │ ├── layout_error.xml │ │ │ │ ├── activity_main.xml │ │ │ │ ├── fragment_login.xml │ │ │ │ ├── activity_personal_center.xml │ │ │ │ └── item_repository.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── anim │ │ │ │ ├── switch_fade_in.xml │ │ │ │ ├── switch_fade_out.xml │ │ │ │ ├── switch_in_replaced.xml │ │ │ │ ├── switch_in_bottom.xml │ │ │ │ ├── switch_in_left.xml │ │ │ │ ├── switch_in_right.xml │ │ │ │ ├── switch_in_top.xml │ │ │ │ ├── switch_out_bottom.xml │ │ │ │ ├── switch_out_hidden.xml │ │ │ │ ├── switch_out_left.xml │ │ │ │ ├── switch_out_replaced.xml │ │ │ │ ├── switch_out_right.xml │ │ │ │ └── switch_out_top.xml │ │ │ ├── values │ │ │ │ ├── styles.xml │ │ │ │ ├── colors.xml │ │ │ │ └── strings.xml │ │ │ └── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── tanjiajun │ │ │ │ └── androidgenericframework │ │ │ │ ├── ui │ │ │ │ ├── NoViewModel.kt │ │ │ │ ├── recyclerview │ │ │ │ │ ├── BaseViewHolder.kt │ │ │ │ │ ├── NoDataViewType.kt │ │ │ │ │ ├── BaseViewType.kt │ │ │ │ │ ├── BaseDataBindingAdapter.kt │ │ │ │ │ └── MultiViewTypeDataBindingAdapter.kt │ │ │ │ ├── user │ │ │ │ │ ├── activity │ │ │ │ │ │ ├── RegisterAndLoginActivity.kt │ │ │ │ │ │ └── PersonalCenterActivity.kt │ │ │ │ │ ├── viewmodel │ │ │ │ │ │ ├── PersonalCenterViewModel.kt │ │ │ │ │ │ └── LoginViewModel.kt │ │ │ │ │ └── fragment │ │ │ │ │ │ └── LoginFragment.kt │ │ │ │ ├── main │ │ │ │ │ ├── viewmodel │ │ │ │ │ │ ├── SplashViewModel.kt │ │ │ │ │ │ └── MainViewModel.kt │ │ │ │ │ └── activity │ │ │ │ │ │ ├── SplashActivity.kt │ │ │ │ │ │ └── MainActivity.kt │ │ │ │ ├── repository │ │ │ │ │ ├── adapter │ │ │ │ │ │ └── RepositoryAdapter.kt │ │ │ │ │ ├── viewmodel │ │ │ │ │ │ └── RepositoryViewModel.kt │ │ │ │ │ └── fragment │ │ │ │ │ │ └── RepositoryFragment.kt │ │ │ │ ├── BaseFragment.kt │ │ │ │ └── BaseViewModel.kt │ │ │ │ ├── AndroidGenericFrameworkFragmentTag.kt │ │ │ │ ├── AndroidGenericFrameworkExtra.kt │ │ │ │ ├── utils │ │ │ │ ├── DateUtils.kt │ │ │ │ ├── FragmentExt.kt │ │ │ │ ├── ActivityExt.kt │ │ │ │ ├── ToastExt.kt │ │ │ │ ├── BooleanExt.kt │ │ │ │ ├── GsonExt.kt │ │ │ │ ├── SingleLiveEvent.kt │ │ │ │ ├── OnTabSelectedListenerBuilder.kt │ │ │ │ ├── BindingAdapters.kt │ │ │ │ ├── Language.kt │ │ │ │ └── Preferences.kt │ │ │ │ ├── data │ │ │ │ ├── model │ │ │ │ │ ├── user │ │ │ │ │ │ ├── response │ │ │ │ │ │ │ ├── UserAccessTokenData.kt │ │ │ │ │ │ │ └── UserInfoData.kt │ │ │ │ │ │ └── request │ │ │ │ │ │ │ └── LoginRequestData.kt │ │ │ │ │ ├── ListData.kt │ │ │ │ │ └── repository │ │ │ │ │ │ └── Repository.kt │ │ │ │ ├── remote │ │ │ │ │ ├── ResponseThrowable.kt │ │ │ │ │ ├── user │ │ │ │ │ │ └── UserRemoteDataSource.kt │ │ │ │ │ ├── repository │ │ │ │ │ │ └── RepositoryRemoteDataSource.kt │ │ │ │ │ ├── BasicAuthInterceptor.kt │ │ │ │ │ └── ExceptionHandler.kt │ │ │ │ ├── local │ │ │ │ │ └── user │ │ │ │ │ │ └── UserLocalDataSource.kt │ │ │ │ └── repository │ │ │ │ │ ├── GitHubRepository.kt │ │ │ │ │ └── UserInfoRepository.kt │ │ │ │ ├── AndroidGenericFrameworkConfiguration.kt │ │ │ │ ├── AndroidGenericFrameworkAppGlideModule.kt │ │ │ │ ├── AndroidGenericFrameworkApplication.kt │ │ │ │ └── di │ │ │ │ ├── GitHubRepositoryModule.kt │ │ │ │ ├── ApplicationComponent.kt │ │ │ │ ├── RepositoryModule.kt │ │ │ │ ├── MainModule.kt │ │ │ │ ├── ApplicationModule.kt │ │ │ │ ├── UserModule.kt │ │ │ │ ├── NetworkModule.kt │ │ │ │ └── ViewModelFactory.kt │ │ └── AndroidManifest.xml │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── tanjiajun │ │ │ └── androidgenericframework │ │ │ ├── utils │ │ │ ├── LanguageTest.kt │ │ │ └── GsonExtTest.kt │ │ │ ├── viewmodel │ │ │ ├── PersonalCenterViewModelTest.kt │ │ │ ├── SplashViewModelTest.kt │ │ │ ├── RepositoryViewModelTest.kt │ │ │ ├── LoginViewModelTest.kt │ │ │ └── MainViewModelTest.kt │ │ │ └── data │ │ │ ├── RepositoryRemoteDataSourceTest.kt │ │ │ ├── UserRemoteDataSourceTest.kt │ │ │ └── FakeDataSource.kt │ └── androidTest │ │ └── java │ │ └── com │ │ └── tanjiajun │ │ └── androidgenericframework │ │ └── ExampleInstrumentedTest.kt ├── proguard-rules.pro └── build.gradle ├── settings.gradle ├── .gitignore ├── screenshot ├── data.png ├── test.png ├── ui.png ├── diKoin.png ├── utils.png ├── LoginPage.png ├── MainPage.png ├── diDagger2.png ├── PersonalCenterPage.png └── PrefixAndroidGenericFrameworkFile.png ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew.bat └── gradlew /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | local.properties 4 | build 5 | .idea -------------------------------------------------------------------------------- /screenshot/data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanJiaJunBeyond/AndroidGenericFramework/HEAD/screenshot/data.png -------------------------------------------------------------------------------- /screenshot/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanJiaJunBeyond/AndroidGenericFramework/HEAD/screenshot/test.png -------------------------------------------------------------------------------- /screenshot/ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanJiaJunBeyond/AndroidGenericFramework/HEAD/screenshot/ui.png -------------------------------------------------------------------------------- /screenshot/diKoin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanJiaJunBeyond/AndroidGenericFramework/HEAD/screenshot/diKoin.png -------------------------------------------------------------------------------- /screenshot/utils.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanJiaJunBeyond/AndroidGenericFramework/HEAD/screenshot/utils.png -------------------------------------------------------------------------------- /screenshot/LoginPage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanJiaJunBeyond/AndroidGenericFramework/HEAD/screenshot/LoginPage.png -------------------------------------------------------------------------------- /screenshot/MainPage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanJiaJunBeyond/AndroidGenericFramework/HEAD/screenshot/MainPage.png -------------------------------------------------------------------------------- /screenshot/diDagger2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanJiaJunBeyond/AndroidGenericFramework/HEAD/screenshot/diDagger2.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanJiaJunBeyond/AndroidGenericFramework/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /screenshot/PersonalCenterPage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanJiaJunBeyond/AndroidGenericFramework/HEAD/screenshot/PersonalCenterPage.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_kg.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanJiaJunBeyond/AndroidGenericFramework/HEAD/app/src/main/res/drawable-xxhdpi/ic_kg.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanJiaJunBeyond/AndroidGenericFramework/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanJiaJunBeyond/AndroidGenericFramework/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_bell.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanJiaJunBeyond/AndroidGenericFramework/HEAD/app/src/main/res/drawable-xxhdpi/ic_bell.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_call.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanJiaJunBeyond/AndroidGenericFramework/HEAD/app/src/main/res/drawable-xxhdpi/ic_call.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_sort.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanJiaJunBeyond/AndroidGenericFramework/HEAD/app/src/main/res/drawable-xxhdpi/ic_sort.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_time.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanJiaJunBeyond/AndroidGenericFramework/HEAD/app/src/main/res/drawable-xxhdpi/ic_time.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanJiaJunBeyond/AndroidGenericFramework/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanJiaJunBeyond/AndroidGenericFramework/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_address.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanJiaJunBeyond/AndroidGenericFramework/HEAD/app/src/main/res/drawable-xxhdpi/ic_address.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_dropdown.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanJiaJunBeyond/AndroidGenericFramework/HEAD/app/src/main/res/drawable-xxhdpi/ic_dropdown.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_password.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanJiaJunBeyond/AndroidGenericFramework/HEAD/app/src/main/res/drawable-xxhdpi/ic_password.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_personal.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanJiaJunBeyond/AndroidGenericFramework/HEAD/app/src/main/res/drawable-xxhdpi/ic_personal.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_github.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanJiaJunBeyond/AndroidGenericFramework/HEAD/app/src/main/res/drawable-xxxhdpi/ic_github.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanJiaJunBeyond/AndroidGenericFramework/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /screenshot/PrefixAndroidGenericFrameworkFile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanJiaJunBeyond/AndroidGenericFramework/HEAD/screenshot/PrefixAndroidGenericFrameworkFile.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_arrow_back.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanJiaJunBeyond/AndroidGenericFramework/HEAD/app/src/main/res/drawable-xxhdpi/ic_arrow_back.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanJiaJunBeyond/AndroidGenericFramework/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanJiaJunBeyond/AndroidGenericFramework/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanJiaJunBeyond/AndroidGenericFramework/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_phone_number.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanJiaJunBeyond/AndroidGenericFramework/HEAD/app/src/main/res/drawable-xxhdpi/ic_phone_number.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanJiaJunBeyond/AndroidGenericFramework/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanJiaJunBeyond/AndroidGenericFramework/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_default_avatar.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanJiaJunBeyond/AndroidGenericFramework/HEAD/app/src/main/res/drawable-xxhdpi/ic_default_avatar.webp -------------------------------------------------------------------------------- /app/src/main/java/com/tanjiajun/androidgenericframework/ui/NoViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.tanjiajun.androidgenericframework.ui 2 | 3 | /** 4 | * Created by TanJiaJun on 2020-02-13. 5 | */ 6 | class NoViewModel : BaseViewModel() -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_green_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_order_background_color.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_common_edit_text_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Sep 07 02:33:28 CST 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip 7 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_divider_line.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/java/com/tanjiajun/androidgenericframework/AndroidGenericFrameworkFragmentTag.kt: -------------------------------------------------------------------------------- 1 | package com.tanjiajun.androidgenericframework 2 | 3 | /** 4 | * Created by TanJiaJun on 2019-07-29. 5 | */ 6 | const val FRAGMENT_TAG_LOGIN = "login_fragment" 7 | const val FRAGMENT_TAG_REPOSITORY = "repository_fragment" -------------------------------------------------------------------------------- /app/src/main/java/com/tanjiajun/androidgenericframework/AndroidGenericFrameworkExtra.kt: -------------------------------------------------------------------------------- 1 | package com.tanjiajun.androidgenericframework 2 | 3 | /** 4 | * Created by TanJiaJun on 2019-08-24. 5 | */ 6 | // 用户 7 | const val EXTRA_LOGOUT = "EXTRA_LOGOUT" 8 | 9 | // 仓库 10 | const val EXTRA_LANGUAGE = "EXTRA_LANGUAGE" -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/anim/switch_fade_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/anim/switch_fade_out.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/java/com/tanjiajun/androidgenericframework/utils/DateUtils.kt: -------------------------------------------------------------------------------- 1 | package com.tanjiajun.androidgenericframework.utils 2 | 3 | import java.time.format.DateTimeFormatter 4 | 5 | /** 6 | * Created by TanJiaJun on 2020-02-07. 7 | */ 8 | fun dateFormatForRepository(): DateTimeFormatter = 9 | DateTimeFormatter.ofPattern("yyyy-MM-dd") -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_register_and_login.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/tanjiajun/androidgenericframework/data/model/user/response/UserAccessTokenData.kt: -------------------------------------------------------------------------------- 1 | package com.tanjiajun.androidgenericframework.data.model.user.response 2 | 3 | /** 4 | * Created by TanJiaJun on 2020-02-02. 5 | */ 6 | data class UserAccessTokenData( 7 | var id: Int, 8 | var token: String, 9 | var url: String 10 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/tanjiajun/androidgenericframework/data/remote/ResponseThrowable.kt: -------------------------------------------------------------------------------- 1 | package com.tanjiajun.androidgenericframework.data.remote 2 | 3 | /** 4 | * Created by TanJiaJun on 2020-02-04. 5 | */ 6 | class ResponseThrowable( 7 | var errorCode: Int, 8 | var errorMessage: String, 9 | throwable: Throwable 10 | ) : Exception(throwable) -------------------------------------------------------------------------------- /app/src/main/java/com/tanjiajun/androidgenericframework/utils/FragmentExt.kt: -------------------------------------------------------------------------------- 1 | package com.tanjiajun.androidgenericframework.utils 2 | 3 | import androidx.fragment.app.Fragment 4 | import com.tanjiajun.androidgenericframework.ui.BaseActivity 5 | 6 | /** 7 | * Created by TanJiaJun on 2019-08-07. 8 | */ 9 | inline fun > Fragment.startActivity() = 10 | startActivity(android.content.Intent(context, T::class.java)) -------------------------------------------------------------------------------- /app/src/main/java/com/tanjiajun/androidgenericframework/utils/ActivityExt.kt: -------------------------------------------------------------------------------- 1 | package com.tanjiajun.androidgenericframework.utils 2 | 3 | import android.app.Activity 4 | import android.content.Intent 5 | import com.tanjiajun.androidgenericframework.ui.BaseActivity 6 | 7 | /** 8 | * Created by TanJiaJun on 2019-08-12. 9 | */ 10 | inline fun > Activity.startActivity() = 11 | startActivity(Intent(this, T::class.java)) -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/tanjiajun/androidgenericframework/data/model/ListData.kt: -------------------------------------------------------------------------------- 1 | package com.tanjiajun.androidgenericframework.data.model 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | /** 6 | * Created by TanJiaJun on 2020-02-06. 7 | */ 8 | data class ListData( 9 | @SerializedName("total_count") val totalCount: Int? = null, 10 | @SerializedName("incomplete_results") val incompleteResults: Boolean? = null, 11 | var items: List? = null 12 | ) -------------------------------------------------------------------------------- /app/src/main/res/anim/switch_in_replaced.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/anim/switch_in_bottom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/anim/switch_in_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/anim/switch_in_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/anim/switch_in_top.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/anim/switch_out_bottom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/anim/switch_out_hidden.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/anim/switch_out_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/anim/switch_out_replaced.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/anim/switch_out_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/anim/switch_out_top.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_circle_c.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_circle_css.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_circle_go.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_circle_java.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_circle_other.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_circle_php.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_circle_ruby.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_circle_swift.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_circle_kotlin.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_circle_python.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_circle_c_plus_plus.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_circle_java_script.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/tanjiajun/androidgenericframework/utils/ToastExt.kt: -------------------------------------------------------------------------------- 1 | package com.tanjiajun.androidgenericframework.utils 2 | 3 | import android.widget.Toast 4 | import com.tanjiajun.androidgenericframework.AndroidGenericFrameworkApplication 5 | 6 | /** 7 | * Created by TanJiaJun on 2020-02-13. 8 | */ 9 | fun toastShort(text: String) = 10 | Toast.makeText(AndroidGenericFrameworkApplication.instance, text, Toast.LENGTH_SHORT).show() 11 | 12 | fun toastLong(text: String) = 13 | Toast.makeText(AndroidGenericFrameworkApplication.instance, text, Toast.LENGTH_LONG).show() 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/tanjiajun/androidgenericframework/AndroidGenericFrameworkConfiguration.kt: -------------------------------------------------------------------------------- 1 | package com.tanjiajun.androidgenericframework 2 | 3 | /** 4 | * Created by TanJiaJun on 2019-07-30. 5 | */ 6 | object AndroidGenericFrameworkConfiguration { 7 | 8 | // MMKV 9 | const val MMKV_ID = "android_generic_framework_configuration_mmkv_id" 10 | const val MMKV_CRYPT_KEY = "android_generic_framework_configuration_crypt_key" 11 | 12 | // Retrofit 13 | const val CONNECT_TIMEOUT = 20000L 14 | const val READ_TIMEOUT = 20000L 15 | const val HOST = "api.github.com" 16 | 17 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_star.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/tanjiajun/androidgenericframework/ui/recyclerview/BaseViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.tanjiajun.androidgenericframework.ui.recyclerview 2 | 3 | import androidx.databinding.ViewDataBinding 4 | import androidx.recyclerview.widget.RecyclerView 5 | import com.tanjiajun.androidgenericframework.BR 6 | 7 | /** 8 | * Created by TanJiaJun on 2019-08-25. 9 | */ 10 | class BaseViewHolder(val binding: ViewDataBinding) 11 | : RecyclerView.ViewHolder(binding.root) { 12 | 13 | fun bind(data: Any) = 14 | with(binding) { 15 | setVariable(BR.data, data) 16 | executePendingBindings() 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/selector_common_button_background_color.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/test/java/com/tanjiajun/androidgenericframework/utils/LanguageTest.kt: -------------------------------------------------------------------------------- 1 | package com.tanjiajun.androidgenericframework.utils 2 | 3 | import org.junit.Assert.assertEquals 4 | import org.junit.Assert.assertNotEquals 5 | import org.junit.Test 6 | import org.junit.runner.RunWith 7 | import org.junit.runners.JUnit4 8 | 9 | /** 10 | * Created by TanJiaJun on 2020/5/28. 11 | */ 12 | @RunWith(JUnit4::class) 13 | class LanguageTest { 14 | 15 | @Test 16 | fun getLanguage_success() { 17 | assertEquals(Language.KOTLIN, Language.of("Kotlin")) 18 | } 19 | 20 | @Test 21 | fun getLanguage_failure() { 22 | assertNotEquals(Language.JAVA, Language.of("Kotlin")) 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tanjiajun/androidgenericframework/utils/BooleanExt.kt: -------------------------------------------------------------------------------- 1 | package com.tanjiajun.androidgenericframework.utils 2 | 3 | /** 4 | * Created by TanJiaJun on 2020-01-28. 5 | */ 6 | sealed class BooleanExt 7 | 8 | class TransferData(val data: T) : BooleanExt() 9 | object Otherwise : BooleanExt() 10 | 11 | inline fun Boolean.yes(block: () -> T): BooleanExt = 12 | when { 13 | this -> TransferData(block.invoke()) 14 | else -> Otherwise 15 | } 16 | 17 | inline fun BooleanExt.otherwise(block: () -> T): T = 18 | when (this) { 19 | is Otherwise -> block() 20 | is TransferData -> data 21 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tanjiajun/androidgenericframework/utils/GsonExt.kt: -------------------------------------------------------------------------------- 1 | package com.tanjiajun.androidgenericframework.utils 2 | 3 | import com.google.gson.Gson 4 | import com.google.gson.JsonElement 5 | import com.google.gson.reflect.TypeToken 6 | import java.io.Reader 7 | 8 | /** 9 | * Created by TanJiaJun on 2019/5/3. 10 | */ 11 | inline fun Gson.fromJson(string: String): T = 12 | fromJson(string, T::class.java) 13 | 14 | inline fun Gson.fromJsonByType(string: String): T = 15 | fromJson(string, object : TypeToken() {}.type) 16 | 17 | inline fun Gson.fromJson(reader: Reader): T = 18 | fromJson(reader, T::class.java) 19 | 20 | inline fun Gson.fromJson(jsonElement: JsonElement): T = 21 | fromJson(jsonElement, T::class.java) -------------------------------------------------------------------------------- /app/src/main/java/com/tanjiajun/androidgenericframework/AndroidGenericFrameworkAppGlideModule.kt: -------------------------------------------------------------------------------- 1 | package com.tanjiajun.androidgenericframework 2 | 3 | import android.content.Context 4 | import com.bumptech.glide.GlideBuilder 5 | import com.bumptech.glide.annotation.GlideModule 6 | import com.bumptech.glide.load.DecodeFormat 7 | import com.bumptech.glide.module.AppGlideModule 8 | import com.bumptech.glide.request.RequestOptions 9 | 10 | /** 11 | * Created by TanJiaJun on 2019-09-13. 12 | */ 13 | @GlideModule 14 | class AndroidGenericFrameworkAppGlideModule : AppGlideModule() { 15 | 16 | override fun applyOptions(context: Context, builder: GlideBuilder) { 17 | super.applyOptions(context, builder) 18 | builder.setDefaultRequestOptions(RequestOptions().format(DecodeFormat.PREFER_RGB_565)) 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #ffffff 5 | #000000 6 | 7 | #5e616d 8 | 9 | #f8f9fb 10 | 11 | #f2f3f5 12 | 13 | #000913 14 | #a8acbf 15 | #5e616d 16 | 17 | #cfd6e0 18 | #5e616d 19 | #a8acbf 20 | 21 | #a8acbf 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/tanjiajun/androidgenericframework/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.tanjiajun.androidgenericframework 2 | 3 | import androidx.test.InstrumentationRegistry 4 | import androidx.test.runner.AndroidJUnit4 5 | import org.junit.Assert.assertEquals 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | /** 10 | * Instrumented test, which will execute on an Android device. 11 | * 12 | * See [testing documentation](http://d.android.com/tools/testing). 13 | */ 14 | @RunWith(AndroidJUnit4::class) 15 | class ExampleInstrumentedTest { 16 | @Test 17 | fun useAppContext() { 18 | // Context of the app under test. 19 | val appContext = InstrumentationRegistry.getTargetContext() 20 | assertEquals("com.tanjiajun.androidgenericframework", appContext.packageName) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/tanjiajun/androidgenericframework/AndroidGenericFrameworkApplication.kt: -------------------------------------------------------------------------------- 1 | package com.tanjiajun.androidgenericframework 2 | 3 | import com.tanjiajun.androidgenericframework.di.DaggerApplicationComponent 4 | import com.tencent.mmkv.MMKV 5 | import dagger.android.AndroidInjector 6 | import dagger.android.DaggerApplication 7 | 8 | /** 9 | * Created by TanJiaJun on 2019-07-28. 10 | */ 11 | class AndroidGenericFrameworkApplication : DaggerApplication() { 12 | 13 | override fun applicationInjector(): AndroidInjector = 14 | DaggerApplicationComponent.factory().create(applicationContext) 15 | 16 | override fun onCreate() { 17 | super.onCreate() 18 | instance = this 19 | MMKV.initialize(this) 20 | } 21 | 22 | companion object { 23 | lateinit var instance: AndroidGenericFrameworkApplication 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_splash.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/tanjiajun/androidgenericframework/di/GitHubRepositoryModule.kt: -------------------------------------------------------------------------------- 1 | package com.tanjiajun.androidgenericframework.di 2 | 3 | import androidx.lifecycle.ViewModel 4 | import com.tanjiajun.androidgenericframework.ui.repository.fragment.RepositoryFragment 5 | import com.tanjiajun.androidgenericframework.ui.repository.viewmodel.RepositoryViewModel 6 | import dagger.Binds 7 | import dagger.Module 8 | import dagger.android.ContributesAndroidInjector 9 | import dagger.multibindings.IntoMap 10 | 11 | /** 12 | * Created by TanJiaJun on 2020/3/8. 13 | */ 14 | @Suppress("unused") 15 | @Module 16 | abstract class GitHubRepositoryModule { 17 | 18 | @ContributesAndroidInjector(modules = [ViewModelBuilder::class]) 19 | internal abstract fun contributeRepositoryFragment(): RepositoryFragment 20 | 21 | @Binds 22 | @IntoMap 23 | @ViewModelKey(RepositoryViewModel::class) 24 | internal abstract fun bindRepositoryViewModel(viewModel: RepositoryViewModel): ViewModel 25 | 26 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tanjiajun/androidgenericframework/data/model/user/request/LoginRequestData.kt: -------------------------------------------------------------------------------- 1 | package com.tanjiajun.androidgenericframework.data.model.user.request 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import com.tanjiajun.androidgenericframework.BuildConfig 5 | 6 | /** 7 | * Created by TanJiaJun on 2019-08-02. 8 | */ 9 | data class LoginRequestData( 10 | val scopes: List, 11 | val note: String, 12 | @SerializedName("client_id") val clientId: String, 13 | @SerializedName("client_secret") val clientSecret: String 14 | ) { 15 | 16 | companion object { 17 | fun generate(): LoginRequestData = 18 | LoginRequestData( 19 | scopes = listOf("user", "repo", "gist", "notifications"), 20 | note = BuildConfig.APPLICATION_ID, 21 | clientId = BuildConfig.GITHUB_CLIENT_ID, 22 | clientSecret = BuildConfig.GITHUB_CLIENT_SECRET 23 | ) 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | AndroidGenericFramework 4 | 5 | 6 | %1$s://%2$s/ 7 | GitHub图标 8 | 9 | 10 | 出错了!请点击重试。 11 | 重试 12 | 13 | 14 | 添加 15 | 16 | 17 | 请输入用户名 18 | 请输入密码 19 | 登录 20 | 取消 21 | 22 | 个人中心 23 | 头像 24 | 名字 25 | 退出登录 26 | 27 | 28 | 语言图标 29 | star图标 30 | fork图标 31 | 32 | 33 | -------------------------------------------------------------------------------- /app/src/main/java/com/tanjiajun/androidgenericframework/ui/user/activity/RegisterAndLoginActivity.kt: -------------------------------------------------------------------------------- 1 | package com.tanjiajun.androidgenericframework.ui.user.activity 2 | 3 | import android.os.Bundle 4 | import com.tanjiajun.androidgenericframework.R 5 | import com.tanjiajun.androidgenericframework.databinding.ActivityRegisterAndLoginBinding 6 | import com.tanjiajun.androidgenericframework.ui.BaseActivity 7 | import com.tanjiajun.androidgenericframework.ui.NoViewModel 8 | import com.tanjiajun.androidgenericframework.ui.user.fragment.LoginFragment 9 | 10 | /** 11 | * Created by TanJiaJun on 2019-07-28. 12 | */ 13 | class RegisterAndLoginActivity : BaseActivity() { 14 | 15 | override val layoutRes: Int = R.layout.activity_register_and_login 16 | override val viewModel = NoViewModel() 17 | override val containId: Int = R.id.fl_content 18 | 19 | override fun onCreate(savedInstanceState: Bundle?) { 20 | super.onCreate(savedInstanceState) 21 | addFragment(LoginFragment.newInstance()) 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /app/src/test/java/com/tanjiajun/androidgenericframework/utils/GsonExtTest.kt: -------------------------------------------------------------------------------- 1 | package com.tanjiajun.androidgenericframework.utils 2 | 3 | import com.google.gson.Gson 4 | import com.tanjiajun.androidgenericframework.data.model.user.response.UserInfoData 5 | import com.tanjiajun.androidgenericframework.data.userAccessTokenData 6 | import com.tanjiajun.androidgenericframework.data.userInfoData 7 | import com.tanjiajun.androidgenericframework.data.userInfoDataJson 8 | import org.junit.Assert.assertEquals 9 | import org.junit.Assert.assertNotEquals 10 | import org.junit.Test 11 | import org.junit.runner.RunWith 12 | import org.junit.runners.JUnit4 13 | 14 | /** 15 | * Created by TanJiaJun on 2020/5/28. 16 | */ 17 | @RunWith(JUnit4::class) 18 | class GsonExtTest { 19 | 20 | @Test 21 | fun fromJson_success() { 22 | assertEquals(userInfoData.id, Gson().fromJson(userInfoDataJson).id) 23 | } 24 | 25 | @Test 26 | fun fromJson_failure() { 27 | assertNotEquals(userAccessTokenData.id, Gson().fromJson(userInfoDataJson).id) 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tanjiajun/androidgenericframework/ui/main/viewmodel/SplashViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.tanjiajun.androidgenericframework.ui.main.viewmodel 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.lifecycle.MutableLiveData 5 | import androidx.lifecycle.viewModelScope 6 | import com.tanjiajun.androidgenericframework.data.repository.UserInfoRepository 7 | import com.tanjiajun.androidgenericframework.ui.BaseViewModel 8 | import kotlinx.coroutines.delay 9 | import kotlinx.coroutines.launch 10 | import javax.inject.Inject 11 | 12 | /** 13 | * Created by TanJiaJun on 2019-08-12. 14 | */ 15 | class SplashViewModel @Inject constructor( 16 | private val repository: UserInfoRepository 17 | ) : BaseViewModel() { 18 | 19 | private val _isNavigateToMainActivity = MutableLiveData() 20 | var isNavigateToMainActivity: LiveData = _isNavigateToMainActivity 21 | 22 | fun navigateToPage() = 23 | viewModelScope.launch { 24 | delay(1000) 25 | _isNavigateToMainActivity.value = repository.isLogin() 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tanjiajun/androidgenericframework/di/ApplicationComponent.kt: -------------------------------------------------------------------------------- 1 | package com.tanjiajun.androidgenericframework.di 2 | 3 | import android.content.Context 4 | import com.tanjiajun.androidgenericframework.AndroidGenericFrameworkApplication 5 | import dagger.BindsInstance 6 | import dagger.Component 7 | import dagger.android.AndroidInjector 8 | import dagger.android.support.AndroidSupportInjectionModule 9 | import javax.inject.Singleton 10 | 11 | /** 12 | * Created by TanJiaJun on 2020/3/4. 13 | */ 14 | @Singleton 15 | @Component( 16 | modules = [ 17 | AndroidSupportInjectionModule::class, 18 | ApplicationModule::class, 19 | NetworkModule::class, 20 | RepositoryModule::class, 21 | MainModule::class, 22 | UserModule::class, 23 | GitHubRepositoryModule::class 24 | ] 25 | ) 26 | interface ApplicationComponent : AndroidInjector { 27 | 28 | @Component.Factory 29 | interface Factory { 30 | 31 | fun create(@BindsInstance applicationContext: Context): ApplicationComponent 32 | 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_toolbar.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 11 | 12 | 15 | 16 | 17 | 18 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | android.databinding.enableV2=true 21 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_loading.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 20 | 21 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/tanjiajun/androidgenericframework/ui/recyclerview/NoDataViewType.kt: -------------------------------------------------------------------------------- 1 | package com.tanjiajun.androidgenericframework.ui.recyclerview 2 | 3 | /** 4 | * Created by TanJiaJun on 2019-08-31. 5 | */ 6 | abstract class NoDataViewType : BaseViewType() { 7 | 8 | abstract fun bind(holder: BaseViewHolder, 9 | position: Int) 10 | 11 | abstract fun isMatchViewType(position: Int, 12 | headerCount: Int, 13 | itemCount: Int, 14 | footerCount: Int): Boolean 15 | 16 | override fun bind(holder: BaseViewHolder, data: T, position: Int) { 17 | // no implementation 18 | } 19 | 20 | override fun bind(holder: BaseViewHolder, data: T, 21 | position: Int, 22 | payLoads: List) { 23 | // no implementation 24 | } 25 | 26 | override fun isMatchViewType(any: Any): Boolean = false 27 | 28 | fun bind(holder: BaseViewHolder, 29 | position: Int, 30 | payLoads: List) = 31 | with(holder) { 32 | bind(this, position) 33 | binding.executePendingBindings() 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tanjiajun/androidgenericframework/di/RepositoryModule.kt: -------------------------------------------------------------------------------- 1 | package com.tanjiajun.androidgenericframework.di 2 | 3 | import com.tanjiajun.androidgenericframework.data.local.user.UserLocalDataSource 4 | import com.tanjiajun.androidgenericframework.data.remote.repository.RepositoryRemoteDataSource 5 | import com.tanjiajun.androidgenericframework.data.remote.user.UserRemoteDataSource 6 | import com.tanjiajun.androidgenericframework.data.repository.GitHubRepository 7 | import com.tanjiajun.androidgenericframework.data.repository.UserInfoRepository 8 | import dagger.Module 9 | import dagger.Provides 10 | import javax.inject.Singleton 11 | 12 | /** 13 | * Created by TanJiaJun on 2020/5/6. 14 | */ 15 | @Suppress("unused") 16 | @Module 17 | open class RepositoryModule { 18 | 19 | @Provides 20 | @Singleton 21 | fun provideUserInfoRepository(remoteDataSource: UserRemoteDataSource, 22 | localDataSource: UserLocalDataSource): UserInfoRepository = 23 | UserInfoRepository(remoteDataSource, localDataSource) 24 | 25 | @Provides 26 | @Singleton 27 | fun provideGitHubRepository(remoteDataSource: RepositoryRemoteDataSource): GitHubRepository = 28 | GitHubRepository(remoteDataSource) 29 | 30 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tanjiajun/androidgenericframework/ui/user/viewmodel/PersonalCenterViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.tanjiajun.androidgenericframework.ui.user.viewmodel 2 | 3 | import android.view.View 4 | import androidx.lifecycle.LiveData 5 | import androidx.lifecycle.MutableLiveData 6 | import com.tanjiajun.androidgenericframework.data.repository.UserInfoRepository 7 | import com.tanjiajun.androidgenericframework.ui.BaseViewModel 8 | import javax.inject.Inject 9 | 10 | /** 11 | * Created by TanJiaJun on 2019-08-24. 12 | */ 13 | class PersonalCenterViewModel @Inject constructor( 14 | private val repository: UserInfoRepository 15 | ) : BaseViewModel() { 16 | 17 | private val _avatarUrl = MutableLiveData().apply { 18 | value = repository.getAvatarUrl() 19 | } 20 | val avatarUrl: LiveData = _avatarUrl 21 | 22 | private val _name = MutableLiveData().apply { 23 | value = repository.getName() 24 | } 25 | val name: LiveData = _name 26 | 27 | fun showTitle(title: String) { 28 | _title.value = title 29 | } 30 | 31 | fun logout() = 32 | repository.logout() 33 | 34 | interface Handlers : BaseViewModel.Handlers { 35 | 36 | fun onLogoutClick(view: View) 37 | 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tanjiajun/androidgenericframework/ui/repository/adapter/RepositoryAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.tanjiajun.androidgenericframework.ui.repository.adapter 2 | 3 | import com.tanjiajun.androidgenericframework.BR 4 | import com.tanjiajun.androidgenericframework.R 5 | import com.tanjiajun.androidgenericframework.data.model.repository.RepositoryData 6 | import com.tanjiajun.androidgenericframework.ui.recyclerview.BaseViewHolder 7 | import com.tanjiajun.androidgenericframework.ui.recyclerview.BaseViewType 8 | import com.tanjiajun.androidgenericframework.ui.recyclerview.MultiViewTypeDataBindingAdapter 9 | 10 | /** 11 | * Created by TanJiaJun on 2020-02-07. 12 | */ 13 | class RepositoryAdapter : MultiViewTypeDataBindingAdapter() { 14 | 15 | init { 16 | addViewType(RepositoryViewType()) 17 | } 18 | 19 | } 20 | 21 | class RepositoryViewType : BaseViewType() { 22 | 23 | override fun getItemLayoutRes(): Int = R.layout.item_repository 24 | 25 | override fun isMatchViewType(any: Any): Boolean = 26 | any is RepositoryData 27 | 28 | override fun bind(holder: BaseViewHolder, data: RepositoryData, position: Int) { 29 | holder.binding.setVariable(BR.data, data) 30 | } 31 | 32 | override fun getSpanSize(): Int = 0 33 | 34 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tanjiajun/androidgenericframework/utils/SingleLiveEvent.kt: -------------------------------------------------------------------------------- 1 | package com.tanjiajun.androidgenericframework.utils 2 | 3 | import android.util.Log 4 | import androidx.annotation.MainThread 5 | import androidx.lifecycle.LifecycleOwner 6 | import androidx.lifecycle.MutableLiveData 7 | import androidx.lifecycle.Observer 8 | import java.util.concurrent.atomic.AtomicBoolean 9 | 10 | /** 11 | * Created by TanJiaJun on 2020-02-03. 12 | */ 13 | class SingleLiveEvent : MutableLiveData() { 14 | 15 | private val pending = AtomicBoolean(false) 16 | 17 | @MainThread 18 | override fun observe(owner: LifecycleOwner, observer: Observer) { 19 | if (hasActiveObservers()) { 20 | Log.w(TAG, "Multiple observers registered but only one will be notified of changes.") 21 | } 22 | super.observe(owner, Observer { 23 | if (pending.compareAndSet(true, false)) { 24 | observer.onChanged(it) 25 | } 26 | }) 27 | } 28 | 29 | @MainThread 30 | override fun setValue(value: T?) { 31 | pending.set(true) 32 | super.setValue(value) 33 | } 34 | 35 | @MainThread 36 | fun call() { 37 | value = null 38 | } 39 | 40 | private companion object { 41 | const val TAG = "SingleLiveEvent" 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tanjiajun/androidgenericframework/ui/recyclerview/BaseViewType.kt: -------------------------------------------------------------------------------- 1 | package com.tanjiajun.androidgenericframework.ui.recyclerview 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.annotation.LayoutRes 6 | import androidx.databinding.DataBindingUtil 7 | 8 | /** 9 | * Created by TanJiaJun on 2019-08-31. 10 | */ 11 | abstract class BaseViewType { 12 | 13 | @LayoutRes 14 | abstract fun getItemLayoutRes(): Int 15 | 16 | abstract fun isMatchViewType(any: Any): Boolean 17 | 18 | abstract fun bind(holder: BaseViewHolder, 19 | data: T, 20 | position: Int) 21 | 22 | abstract fun getSpanSize(): Int 23 | 24 | fun onCreateDataBindingViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder = 25 | BaseViewHolder(DataBindingUtil.inflate( 26 | LayoutInflater.from(parent.context), 27 | viewType, 28 | parent, 29 | false 30 | )) 31 | 32 | open fun bind(holder: BaseViewHolder, 33 | data: T, 34 | position: Int, 35 | payLoads: List) = 36 | with(holder) { 37 | bind(this, data, position) 38 | binding.executePendingBindings() 39 | } 40 | 41 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_repository.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 18 | 19 | 22 | 23 | 28 | 29 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /app/src/main/java/com/tanjiajun/androidgenericframework/di/MainModule.kt: -------------------------------------------------------------------------------- 1 | package com.tanjiajun.androidgenericframework.di 2 | 3 | import androidx.lifecycle.ViewModel 4 | import com.tanjiajun.androidgenericframework.ui.main.activity.MainActivity 5 | import com.tanjiajun.androidgenericframework.ui.main.activity.SplashActivity 6 | import com.tanjiajun.androidgenericframework.ui.main.viewmodel.MainViewModel 7 | import com.tanjiajun.androidgenericframework.ui.main.viewmodel.SplashViewModel 8 | import dagger.Binds 9 | import dagger.Module 10 | import dagger.android.ContributesAndroidInjector 11 | import dagger.multibindings.IntoMap 12 | 13 | /** 14 | * Created by TanJiaJun on 2020/5/3. 15 | */ 16 | @Suppress("unused") 17 | @Module 18 | abstract class MainModule { 19 | 20 | @ContributesAndroidInjector(modules = [ViewModelBuilder::class]) 21 | internal abstract fun contributeSplashActivity(): SplashActivity 22 | 23 | @ContributesAndroidInjector(modules = [ViewModelBuilder::class]) 24 | internal abstract fun contributeMainActivity(): MainActivity 25 | 26 | @Binds 27 | @IntoMap 28 | @ViewModelKey(SplashViewModel::class) 29 | internal abstract fun bindSplashViewModel(viewModel: SplashViewModel): ViewModel 30 | 31 | @Binds 32 | @IntoMap 33 | @ViewModelKey(MainViewModel::class) 34 | internal abstract fun bindMainViewModel(viewModel: MainViewModel): ViewModel 35 | 36 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tanjiajun/androidgenericframework/data/remote/user/UserRemoteDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.tanjiajun.androidgenericframework.data.remote.user 2 | 3 | import com.tanjiajun.androidgenericframework.data.model.user.request.LoginRequestData 4 | import com.tanjiajun.androidgenericframework.data.model.user.response.UserAccessTokenData 5 | import com.tanjiajun.androidgenericframework.data.model.user.response.UserInfoData 6 | import retrofit2.Retrofit 7 | import retrofit2.http.Body 8 | import retrofit2.http.GET 9 | import retrofit2.http.Headers 10 | import retrofit2.http.POST 11 | import javax.inject.Inject 12 | 13 | /** 14 | * Created by TanJiaJun on 2020/4/4. 15 | */ 16 | class UserRemoteDataSource @Inject constructor( 17 | retrofit: Retrofit 18 | ) { 19 | 20 | private val service: Service = retrofit.create(Service::class.java) 21 | 22 | suspend fun authorizations(): UserAccessTokenData = 23 | service.authorizations(LoginRequestData.generate()) 24 | 25 | suspend fun fetchUserInfo(): UserInfoData = 26 | service.fetchUserInfo() 27 | 28 | interface Service { 29 | 30 | @POST("authorizations") 31 | @Headers("Accept: application/json") 32 | suspend fun authorizations(@Body loginRequestData: LoginRequestData): UserAccessTokenData 33 | 34 | @GET("user") 35 | suspend fun fetchUserInfo(): UserInfoData 36 | 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tanjiajun/androidgenericframework/data/model/repository/Repository.kt: -------------------------------------------------------------------------------- 1 | package com.tanjiajun.androidgenericframework.data.model.repository 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import com.tanjiajun.androidgenericframework.utils.Language 5 | 6 | /** 7 | * Created by TanJiaJun on 2020-02-06. 8 | */ 9 | data class RepositoryResponseData( 10 | val id: Int, 11 | val name: String? = null, 12 | val description: String? = null, 13 | val language: String? = null, 14 | @SerializedName("stargazers_count") val stargazersCount: Int? = null, 15 | @SerializedName("forks_count") val forksCount: Int? = null 16 | ) 17 | 18 | data class RepositoryData( 19 | val id: Int, 20 | val name: String, 21 | val description: String, 22 | val language: Language, 23 | val starCount: Int, 24 | val forkCount: Int 25 | ) 26 | 27 | object RepositoryMapper { 28 | 29 | fun toRepositoryData(data: RepositoryResponseData): RepositoryData = 30 | RepositoryData( 31 | id = data.id, 32 | name = data.name ?: "", 33 | description = data.description ?: "", 34 | language = Language.of(data.language ?: ""), 35 | starCount = data.stargazersCount ?: 0, 36 | forkCount = data.forksCount ?: 0 37 | ) 38 | 39 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tanjiajun/androidgenericframework/ui/recyclerview/BaseDataBindingAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.tanjiajun.androidgenericframework.ui.recyclerview 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.databinding.DataBindingUtil 6 | import androidx.recyclerview.widget.RecyclerView 7 | 8 | /** 9 | * Created by TanJiaJun on 2019-08-29. 10 | */ 11 | abstract class BaseDataBindingAdapter 12 | : RecyclerView.Adapter() { 13 | 14 | protected abstract fun getLayoutResByPosition(position: Int): Int 15 | 16 | protected abstract fun getItemByPosition(position: Int): T? 17 | 18 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder = 19 | BaseViewHolder(DataBindingUtil.inflate( 20 | LayoutInflater.from(parent.context), 21 | viewType, 22 | parent, 23 | false 24 | )) 25 | 26 | override fun onBindViewHolder(holder: BaseViewHolder, position: Int) { 27 | getItemByPosition(position)?.let { holder.bind(it) } 28 | } 29 | 30 | override fun onBindViewHolder(holder: BaseViewHolder, position: Int, payloads: MutableList) { 31 | getItemByPosition(position)?.let { holder.bind(it) } 32 | } 33 | 34 | override fun getItemViewType(position: Int): Int = 35 | getLayoutResByPosition(position) 36 | 37 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tanjiajun/androidgenericframework/utils/OnTabSelectedListenerBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.tanjiajun.androidgenericframework.utils 2 | 3 | import com.google.android.material.tabs.TabLayout 4 | 5 | /** 6 | * Created by TanJiaJun on 2019-09-07. 7 | */ 8 | private typealias OnTabCallback = (tab: TabLayout.Tab?) -> Unit 9 | 10 | class OnTabSelectedListenerBuilder : TabLayout.OnTabSelectedListener { 11 | 12 | private var onTabReselectedCallback: OnTabCallback? = null 13 | private var onTabUnselectedCallback: OnTabCallback? = null 14 | private var onTabSelectedCallback: OnTabCallback? = null 15 | 16 | override fun onTabReselected(tab: TabLayout.Tab?) = 17 | onTabReselectedCallback?.invoke(tab) ?: Unit 18 | 19 | override fun onTabUnselected(tab: TabLayout.Tab?) = 20 | onTabUnselectedCallback?.invoke(tab) ?: Unit 21 | 22 | override fun onTabSelected(tab: TabLayout.Tab?) = 23 | onTabSelectedCallback?.invoke(tab) ?: Unit 24 | 25 | fun onTabReselected(callback: OnTabCallback) { 26 | onTabReselectedCallback = callback 27 | } 28 | 29 | fun onTabUnselected(callback: OnTabCallback) { 30 | onTabUnselectedCallback = callback 31 | } 32 | 33 | fun onTabSelected(callback: OnTabCallback) { 34 | onTabSelectedCallback = callback 35 | } 36 | 37 | } 38 | 39 | fun registerOnTabSelectedListener(function: OnTabSelectedListenerBuilder.() -> Unit) = 40 | OnTabSelectedListenerBuilder().also(function) -------------------------------------------------------------------------------- /app/src/main/java/com/tanjiajun/androidgenericframework/di/ApplicationModule.kt: -------------------------------------------------------------------------------- 1 | package com.tanjiajun.androidgenericframework.di 2 | 3 | import com.tanjiajun.androidgenericframework.AndroidGenericFrameworkConfiguration 4 | import com.tanjiajun.androidgenericframework.data.local.user.UserLocalDataSource 5 | import com.tanjiajun.androidgenericframework.data.remote.repository.RepositoryRemoteDataSource 6 | import com.tanjiajun.androidgenericframework.data.remote.user.UserRemoteDataSource 7 | import com.tencent.mmkv.MMKV 8 | import dagger.Module 9 | import dagger.Provides 10 | import retrofit2.Retrofit 11 | import javax.inject.Singleton 12 | 13 | /** 14 | * Created by TanJiaJun on 2020/3/4. 15 | */ 16 | @Suppress("unused") 17 | @Module 18 | open class ApplicationModule { 19 | 20 | @Provides 21 | @Singleton 22 | fun provideUserLocalDataSource(): UserLocalDataSource = 23 | UserLocalDataSource(MMKV.mmkvWithID( 24 | AndroidGenericFrameworkConfiguration.MMKV_ID, 25 | MMKV.SINGLE_PROCESS_MODE, 26 | AndroidGenericFrameworkConfiguration.MMKV_CRYPT_KEY 27 | )) 28 | 29 | @Provides 30 | @Singleton 31 | fun provideUserRemoteDataSource(retrofit: Retrofit): UserRemoteDataSource = 32 | UserRemoteDataSource(retrofit) 33 | 34 | @Provides 35 | @Singleton 36 | fun provideRepositoryRemoteDataSource(retrofit: Retrofit): RepositoryRemoteDataSource = 37 | RepositoryRemoteDataSource(retrofit) 38 | 39 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tanjiajun/androidgenericframework/utils/BindingAdapters.kt: -------------------------------------------------------------------------------- 1 | package com.tanjiajun.androidgenericframework.utils 2 | 3 | import android.graphics.drawable.Drawable 4 | import android.view.View 5 | import android.widget.ImageView 6 | import androidx.annotation.DrawableRes 7 | import androidx.appcompat.widget.Toolbar 8 | import androidx.databinding.BindingAdapter 9 | import com.bumptech.glide.Glide 10 | import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions 11 | import com.bumptech.glide.request.transition.DrawableCrossFadeFactory 12 | import com.tanjiajun.androidgenericframework.R 13 | 14 | /** 15 | * Created by TanJiaJun on 2019-08-25. 16 | */ 17 | @BindingAdapter("android:src") 18 | fun ImageView.setImageSource(@DrawableRes resId: Int) = 19 | setImageResource(resId) 20 | 21 | @BindingAdapter("onNavigationIconClick") 22 | fun Toolbar.setOnNavigationIconClickListener(listener: View.OnClickListener) = 23 | setNavigationOnClickListener(listener) 24 | 25 | @BindingAdapter(value = ["url", "placeholder", "error"], requireAll = false) 26 | fun ImageView.loadImage(url: String?, placeholder: Drawable?, error: Drawable?) = 27 | Glide 28 | .with(context) 29 | .load(url) 30 | .placeholder(placeholder ?: context.getDrawable(R.mipmap.ic_launcher)) 31 | .error(error ?: context.getDrawable(R.mipmap.ic_launcher)) 32 | .transition(DrawableTransitionOptions.withCrossFade(DrawableCrossFadeFactory.Builder().setCrossFadeEnabled(true).build())) 33 | .into(this) -------------------------------------------------------------------------------- /app/src/main/java/com/tanjiajun/androidgenericframework/ui/repository/viewmodel/RepositoryViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.tanjiajun.androidgenericframework.ui.repository.viewmodel 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.lifecycle.MutableLiveData 5 | import com.tanjiajun.androidgenericframework.data.model.repository.RepositoryData 6 | import com.tanjiajun.androidgenericframework.data.repository.GitHubRepository 7 | import com.tanjiajun.androidgenericframework.ui.BaseViewModel 8 | import com.tanjiajun.androidgenericframework.ui.UIState 9 | import javax.inject.Inject 10 | 11 | /** 12 | * Created by TanJiaJun on 2020-02-07. 13 | */ 14 | class RepositoryViewModel @Inject constructor( 15 | private val repository: GitHubRepository 16 | ) : BaseViewModel() { 17 | 18 | private val _isShowRepositoryView = MutableLiveData() 19 | val isShowRepositoryView: LiveData = _isShowRepositoryView 20 | 21 | private val _repositories = MutableLiveData>() 22 | val repositories: LiveData> = _repositories 23 | 24 | fun getRepositories(languageName: String) = 25 | launch( 26 | uiState = UIState(isShowLoadingView = true, isShowErrorView = true), 27 | block = { repository.getRepositories(languageName) }, 28 | success = { 29 | if (it.isNotEmpty()) { 30 | _repositories.value = it 31 | _isShowRepositoryView.value = true 32 | } 33 | } 34 | ) 35 | 36 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tanjiajun/androidgenericframework/data/local/user/UserLocalDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.tanjiajun.androidgenericframework.data.local.user 2 | 3 | import com.tanjiajun.androidgenericframework.utils.int 4 | import com.tanjiajun.androidgenericframework.utils.string 5 | import com.tencent.mmkv.MMKV 6 | import javax.inject.Inject 7 | 8 | /** 9 | * Created by TanJiaJun on 2019-08-08. 10 | */ 11 | class UserLocalDataSource @Inject constructor( 12 | private val mmkv: MMKV 13 | ) { 14 | 15 | var accessToken by mmkv.string("user_access_token", "") 16 | var userId by mmkv.int("user_id", -1) 17 | var username by mmkv.string("username", "") 18 | var password by mmkv.string("password", "") 19 | var name by mmkv.string("name", "") 20 | var avatarUrl by mmkv.string("avatar_url", "") 21 | 22 | fun clearUserInfoCache() = 23 | mmkv.removeValuesForKeys(arrayOf( 24 | "user_access_token", 25 | "user_id", 26 | "username", 27 | "password", 28 | "name", 29 | "avatar_url" 30 | )) 31 | 32 | fun cacheUserId(userId: Int) { 33 | this.userId = userId 34 | } 35 | 36 | fun cacheUsername(username: String) { 37 | this.username = username 38 | } 39 | 40 | fun cachePassword(password: String) { 41 | this.password = password 42 | } 43 | 44 | fun cacheName(name: String) { 45 | this.name = name 46 | } 47 | 48 | fun cacheAvatarUrl(avatarUrl: String) { 49 | this.avatarUrl = avatarUrl 50 | } 51 | 52 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tanjiajun/androidgenericframework/ui/main/activity/SplashActivity.kt: -------------------------------------------------------------------------------- 1 | package com.tanjiajun.androidgenericframework.ui.main.activity 2 | 3 | import android.os.Bundle 4 | import androidx.activity.viewModels 5 | import androidx.lifecycle.Observer 6 | import com.tanjiajun.androidgenericframework.R 7 | import com.tanjiajun.androidgenericframework.databinding.ActivitySplashBinding 8 | import com.tanjiajun.androidgenericframework.ui.BaseActivity 9 | import com.tanjiajun.androidgenericframework.ui.main.viewmodel.SplashViewModel 10 | import com.tanjiajun.androidgenericframework.ui.user.activity.RegisterAndLoginActivity 11 | import com.tanjiajun.androidgenericframework.utils.otherwise 12 | import com.tanjiajun.androidgenericframework.utils.startActivity 13 | import com.tanjiajun.androidgenericframework.utils.yes 14 | 15 | /** 16 | * Created by TanJiaJun on 2019-08-09. 17 | */ 18 | class SplashActivity : BaseActivity() { 19 | 20 | override val layoutRes: Int = R.layout.activity_splash 21 | override val viewModel by viewModels { viewModelFactory } 22 | 23 | override fun onCreate(savedInstanceState: Bundle?) { 24 | super.onCreate(savedInstanceState) 25 | binding.lifecycleOwner = this 26 | with(viewModel) { 27 | navigateToPage() 28 | isNavigateToMainActivity.observe(this@SplashActivity, Observer { 29 | it 30 | .yes { startActivity() } 31 | .otherwise { startActivity() } 32 | finish() 33 | }) 34 | } 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tanjiajun/androidgenericframework/di/UserModule.kt: -------------------------------------------------------------------------------- 1 | package com.tanjiajun.androidgenericframework.di 2 | 3 | import androidx.lifecycle.ViewModel 4 | import com.tanjiajun.androidgenericframework.ui.user.activity.PersonalCenterActivity 5 | import com.tanjiajun.androidgenericframework.ui.user.activity.RegisterAndLoginActivity 6 | import com.tanjiajun.androidgenericframework.ui.user.fragment.LoginFragment 7 | import com.tanjiajun.androidgenericframework.ui.user.viewmodel.LoginViewModel 8 | import com.tanjiajun.androidgenericframework.ui.user.viewmodel.PersonalCenterViewModel 9 | import dagger.Binds 10 | import dagger.Module 11 | import dagger.android.ContributesAndroidInjector 12 | import dagger.multibindings.IntoMap 13 | 14 | /** 15 | * Created by TanJiaJun on 2020/3/8. 16 | */ 17 | @Suppress("unused") 18 | @Module 19 | abstract class UserModule { 20 | 21 | @ContributesAndroidInjector(modules = [ViewModelBuilder::class]) 22 | internal abstract fun contributeRegisterAndLoginActivity(): RegisterAndLoginActivity 23 | 24 | @ContributesAndroidInjector(modules = [ViewModelBuilder::class]) 25 | internal abstract fun contributeLoginFragment(): LoginFragment 26 | 27 | @ContributesAndroidInjector(modules = [ViewModelBuilder::class]) 28 | internal abstract fun contributePersonalCenterActivity(): PersonalCenterActivity 29 | 30 | @Binds 31 | @IntoMap 32 | @ViewModelKey(LoginViewModel::class) 33 | internal abstract fun bindLoginViewModel(viewModel: LoginViewModel): ViewModel 34 | 35 | @Binds 36 | @IntoMap 37 | @ViewModelKey(PersonalCenterViewModel::class) 38 | internal abstract fun bindPersonalCenterViewModel(viewModel: PersonalCenterViewModel): ViewModel 39 | 40 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tanjiajun/androidgenericframework/data/repository/GitHubRepository.kt: -------------------------------------------------------------------------------- 1 | package com.tanjiajun.androidgenericframework.data.repository 2 | 3 | import com.tanjiajun.androidgenericframework.data.model.repository.RepositoryData 4 | import com.tanjiajun.androidgenericframework.data.remote.repository.RepositoryRemoteDataSource 5 | import com.tanjiajun.androidgenericframework.utils.Language 6 | import java.time.LocalDateTime 7 | import javax.inject.Inject 8 | 9 | /** 10 | * Created by TanJiaJun on 2020-02-08. 11 | */ 12 | class GitHubRepository @Inject constructor( 13 | private val remoteDataSource: RepositoryRemoteDataSource 14 | ) { 15 | 16 | fun getDefaultLanguageNames(): List = 17 | listOf( 18 | Language.KOTLIN.languageName, 19 | Language.JAVA.languageName, 20 | Language.SWIFT.languageName, 21 | Language.JAVA_SCRIPT.languageName, 22 | Language.PYTHON.languageName, 23 | Language.GO.languageName, 24 | Language.CSS.languageName 25 | ) 26 | 27 | fun getMoreLanguageNames(): List = 28 | listOf( 29 | Language.PHP.languageName, 30 | Language.RUBY.languageName, 31 | Language.C_PLUS_PLUS.languageName, 32 | Language.C.languageName, 33 | Language.OTHER.languageName 34 | ) 35 | 36 | suspend fun getRepositories(languageName: String): List = 37 | remoteDataSource.fetchRepositories( 38 | languageName = languageName, 39 | fromDateTime = LocalDateTime.now().minusMonths(1) 40 | ) 41 | 42 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_fork.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | 14 | 20 | 21 | 27 | 28 | 34 | 35 | 41 | 42 | -------------------------------------------------------------------------------- /app/src/main/java/com/tanjiajun/androidgenericframework/di/NetworkModule.kt: -------------------------------------------------------------------------------- 1 | package com.tanjiajun.androidgenericframework.di 2 | 3 | import com.tanjiajun.androidgenericframework.AndroidGenericFrameworkConfiguration 4 | import com.tanjiajun.androidgenericframework.data.local.user.UserLocalDataSource 5 | import com.tanjiajun.androidgenericframework.data.remote.BasicAuthInterceptor 6 | import dagger.Module 7 | import dagger.Provides 8 | import okhttp3.OkHttpClient 9 | import retrofit2.Retrofit 10 | import retrofit2.converter.gson.GsonConverterFactory 11 | import retrofit2.converter.scalars.ScalarsConverterFactory 12 | import java.util.concurrent.TimeUnit 13 | import javax.inject.Singleton 14 | 15 | /** 16 | * Created by TanJiaJun on 2020/4/4. 17 | */ 18 | @Suppress("unused") 19 | @Module 20 | open class NetworkModule { 21 | 22 | @Provides 23 | @Singleton 24 | fun provideOkHttpClient(localDataSource: UserLocalDataSource): OkHttpClient = 25 | OkHttpClient.Builder() 26 | .connectTimeout(AndroidGenericFrameworkConfiguration.CONNECT_TIMEOUT, TimeUnit.MILLISECONDS) 27 | .readTimeout(AndroidGenericFrameworkConfiguration.READ_TIMEOUT, TimeUnit.MILLISECONDS) 28 | .addInterceptor(BasicAuthInterceptor(localDataSource)) 29 | .build() 30 | 31 | @Provides 32 | @Singleton 33 | fun provideRetrofit(client: OkHttpClient): Retrofit = 34 | Retrofit.Builder() 35 | .client(client) 36 | .addConverterFactory(ScalarsConverterFactory.create()) 37 | .addConverterFactory(GsonConverterFactory.create()) 38 | .baseUrl(String.format("%1\$s://%2\$s/", "https", AndroidGenericFrameworkConfiguration.HOST)) 39 | .build() 40 | 41 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tanjiajun/androidgenericframework/data/remote/repository/RepositoryRemoteDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.tanjiajun.androidgenericframework.data.remote.repository 2 | 3 | import com.tanjiajun.androidgenericframework.data.model.ListData 4 | import com.tanjiajun.androidgenericframework.data.model.repository.RepositoryData 5 | import com.tanjiajun.androidgenericframework.data.model.repository.RepositoryMapper 6 | import com.tanjiajun.androidgenericframework.data.model.repository.RepositoryResponseData 7 | import com.tanjiajun.androidgenericframework.utils.dateFormatForRepository 8 | import retrofit2.Retrofit 9 | import retrofit2.http.GET 10 | import retrofit2.http.Query 11 | import java.time.LocalDateTime 12 | import javax.inject.Inject 13 | 14 | /** 15 | * Created by TanJiaJun on 2020/4/4. 16 | */ 17 | class RepositoryRemoteDataSource @Inject constructor( 18 | retrofit: Retrofit 19 | ) { 20 | 21 | private val service: Service = retrofit.create(Service::class.java) 22 | 23 | suspend fun fetchRepositories(languageName: String, 24 | fromDateTime: LocalDateTime): List = 25 | service 26 | .fetchRepositories( 27 | query = "language:${languageName} created:>${fromDateTime.format(dateFormatForRepository())}", 28 | sort = "stars" 29 | ) 30 | .items 31 | ?.map { RepositoryMapper.toRepositoryData(it) } 32 | ?: emptyList() 33 | 34 | interface Service { 35 | 36 | @GET("search/repositories") 37 | suspend fun fetchRepositories(@Query("q") query: String, 38 | @Query("sort") sort: String): ListData 39 | 40 | } 41 | 42 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tanjiajun/androidgenericframework/ui/user/activity/PersonalCenterActivity.kt: -------------------------------------------------------------------------------- 1 | package com.tanjiajun.androidgenericframework.ui.user.activity 2 | 3 | import android.content.Intent 4 | import android.os.Bundle 5 | import android.view.View 6 | import androidx.activity.viewModels 7 | import com.tanjiajun.androidgenericframework.EXTRA_LOGOUT 8 | import com.tanjiajun.androidgenericframework.R 9 | import com.tanjiajun.androidgenericframework.databinding.ActivityPersonalCenterBinding 10 | import com.tanjiajun.androidgenericframework.ui.BaseActivity 11 | import com.tanjiajun.androidgenericframework.ui.main.activity.MainActivity 12 | import com.tanjiajun.androidgenericframework.ui.user.viewmodel.PersonalCenterViewModel 13 | 14 | /** 15 | * Created by TanJiaJun on 2019-08-24. 16 | */ 17 | class PersonalCenterActivity 18 | : BaseActivity(), PersonalCenterViewModel.Handlers { 19 | 20 | override val layoutRes: Int = R.layout.activity_personal_center 21 | override val viewModel by viewModels { viewModelFactory } 22 | 23 | override fun onCreate(savedInstanceState: Bundle?) { 24 | super.onCreate(savedInstanceState) 25 | with(binding) { 26 | lifecycleOwner = this@PersonalCenterActivity 27 | viewModel = this@PersonalCenterActivity.viewModel 28 | handlers = this@PersonalCenterActivity 29 | } 30 | viewModel.showTitle(getString(R.string.personal_center)) 31 | } 32 | 33 | override fun onNavigationIconClick(view: View) = 34 | finish() 35 | 36 | override fun onLogoutClick(view: View) { 37 | viewModel.logout() 38 | startActivity(Intent(this, MainActivity::class.java).apply { 39 | putExtra(EXTRA_LOGOUT, true) 40 | }) 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tanjiajun/androidgenericframework/data/repository/UserInfoRepository.kt: -------------------------------------------------------------------------------- 1 | package com.tanjiajun.androidgenericframework.data.repository 2 | 3 | import com.tanjiajun.androidgenericframework.data.local.user.UserLocalDataSource 4 | import com.tanjiajun.androidgenericframework.data.model.user.response.UserAccessTokenData 5 | import com.tanjiajun.androidgenericframework.data.model.user.response.UserInfoData 6 | import com.tanjiajun.androidgenericframework.data.remote.user.UserRemoteDataSource 7 | import javax.inject.Inject 8 | 9 | /** 10 | * Created by TanJiaJun on 2019-07-31. 11 | */ 12 | class UserInfoRepository @Inject constructor( 13 | private val remoteDataSource: UserRemoteDataSource, 14 | private val localDataSource: UserLocalDataSource 15 | ) { 16 | 17 | fun isLogin(): Boolean = 18 | localDataSource.userId != -1 19 | 20 | fun cacheUsername(username: String) = 21 | localDataSource.cacheUsername(username) 22 | 23 | fun cachePassword(password: String) = 24 | localDataSource.cachePassword(password) 25 | 26 | suspend fun authorizations(): UserAccessTokenData = 27 | remoteDataSource.authorizations() 28 | 29 | suspend fun getUserInfo(): UserInfoData = 30 | remoteDataSource.fetchUserInfo() 31 | 32 | fun cacheUserId(userId: Int) = 33 | localDataSource.cacheUserId(userId) 34 | 35 | fun getName(): String = 36 | localDataSource.name 37 | 38 | fun cacheName(name: String) = 39 | localDataSource.cacheName(name) 40 | 41 | fun getAvatarUrl(): String = 42 | localDataSource.avatarUrl 43 | 44 | fun cacheAvatarUrl(avatarUrl: String) = 45 | localDataSource.cacheAvatarUrl(avatarUrl) 46 | 47 | fun logout() = 48 | localDataSource.clearUserInfoCache() 49 | 50 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tanjiajun/androidgenericframework/utils/Language.kt: -------------------------------------------------------------------------------- 1 | package com.tanjiajun.androidgenericframework.utils 2 | 3 | import androidx.annotation.DrawableRes 4 | import com.tanjiajun.androidgenericframework.R 5 | 6 | /** 7 | * Created by TanJiaJun on 2020-02-07. 8 | */ 9 | enum class Language(val languageName: String, @DrawableRes val iconRes: Int) { 10 | 11 | KOTLIN("Kotlin", R.drawable.ic_circle_kotlin), 12 | 13 | JAVA("Java", R.drawable.ic_circle_java), 14 | 15 | SWIFT("Swift", R.drawable.ic_circle_kotlin), 16 | 17 | JAVA_SCRIPT("JavaScript", R.drawable.ic_circle_java_script), 18 | 19 | PYTHON("Python", R.drawable.ic_circle_python), 20 | 21 | GO("Go", R.drawable.ic_circle_go), 22 | 23 | CSS("CSS", R.drawable.ic_circle_css), 24 | 25 | PHP("PHP", R.drawable.ic_circle_php), 26 | 27 | RUBY("Ruby", R.drawable.ic_circle_ruby), 28 | 29 | C_PLUS_PLUS("C++", R.drawable.ic_circle_c_plus_plus), 30 | 31 | C("C", R.drawable.ic_circle_c), 32 | 33 | OTHER("Other", R.drawable.ic_circle_other); 34 | 35 | companion object { 36 | fun of(language: String): Language = 37 | when (language) { 38 | KOTLIN.languageName -> KOTLIN 39 | JAVA.languageName -> JAVA 40 | SWIFT.languageName -> SWIFT 41 | JAVA_SCRIPT.languageName -> JAVA_SCRIPT 42 | PYTHON.languageName -> PYTHON 43 | GO.languageName -> GO 44 | CSS.languageName -> CSS 45 | PHP.languageName -> PHP 46 | RUBY.languageName -> RUBY 47 | C_PLUS_PLUS.languageName -> C_PLUS_PLUS 48 | C.languageName -> C 49 | OTHER.languageName -> OTHER 50 | else -> OTHER 51 | } 52 | } 53 | 54 | } -------------------------------------------------------------------------------- /app/src/test/java/com/tanjiajun/androidgenericframework/viewmodel/PersonalCenterViewModelTest.kt: -------------------------------------------------------------------------------- 1 | package com.tanjiajun.androidgenericframework.viewmodel 2 | 3 | import androidx.arch.core.executor.testing.InstantTaskExecutorRule 4 | import androidx.lifecycle.Observer 5 | import com.tanjiajun.androidgenericframework.data.repository.UserInfoRepository 6 | import com.tanjiajun.androidgenericframework.data.userInfoData 7 | import com.tanjiajun.androidgenericframework.ui.user.viewmodel.PersonalCenterViewModel 8 | import io.mockk.MockKAnnotations 9 | import io.mockk.every 10 | import io.mockk.impl.annotations.MockK 11 | import io.mockk.mockk 12 | import io.mockk.verify 13 | import org.junit.Before 14 | import org.junit.Rule 15 | import org.junit.Test 16 | import org.junit.runner.RunWith 17 | import org.junit.runners.JUnit4 18 | 19 | /** 20 | * Created by TanJiaJun on 2020/5/28. 21 | */ 22 | @RunWith(JUnit4::class) 23 | class PersonalCenterViewModelTest { 24 | 25 | @get:Rule 26 | var instantTaskExecutorRule = InstantTaskExecutorRule() 27 | 28 | @MockK 29 | private lateinit var repository: UserInfoRepository 30 | 31 | private lateinit var viewModel: PersonalCenterViewModel 32 | 33 | @Before 34 | fun setUp() { 35 | MockKAnnotations.init(this) 36 | every { repository.getAvatarUrl() } returns userInfoData.avatarUrl 37 | every { repository.getName() } returns userInfoData.login 38 | viewModel = PersonalCenterViewModel(repository) 39 | } 40 | 41 | @Test 42 | fun showTitle_success() { 43 | viewModel.showTitle("个人中心") 44 | val observer = mockk>(relaxed = true) 45 | viewModel.title.observeForever(observer) 46 | verify { observer.onChanged(match { it == "个人中心" }) } 47 | } 48 | 49 | @Test 50 | fun showTitle_failure() { 51 | viewModel.showTitle("标题") 52 | val observer = mockk>(relaxed = true) 53 | viewModel.title.observeForever(observer) 54 | verify { observer.onChanged(match { it != "个人中心" }) } 55 | } 56 | 57 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tanjiajun/androidgenericframework/data/remote/BasicAuthInterceptor.kt: -------------------------------------------------------------------------------- 1 | package com.tanjiajun.androidgenericframework.data.remote 2 | 3 | import android.util.Base64 4 | import com.tanjiajun.androidgenericframework.data.local.user.UserLocalDataSource 5 | import com.tanjiajun.androidgenericframework.utils.otherwise 6 | import com.tanjiajun.androidgenericframework.utils.yes 7 | import okhttp3.Interceptor 8 | import okhttp3.Response 9 | 10 | /** 11 | * Created by TanJiaJun on 2020-02-01. 12 | */ 13 | class BasicAuthInterceptor( 14 | private val localDataSource: UserLocalDataSource 15 | ) : Interceptor { 16 | 17 | override fun intercept(chain: Interceptor.Chain): Response = 18 | with(chain) { 19 | var request = request() 20 | val authorization = getAuthorization() 21 | (authorization.isNotEmpty()) 22 | .yes { 23 | request = request 24 | .newBuilder() 25 | .addHeader("Authorization", authorization) 26 | .url(request.url().toString()) 27 | .build() 28 | } 29 | proceed(request) 30 | } 31 | 32 | private fun getAuthorization(): String = 33 | with(localDataSource) { 34 | (accessToken.isBlank()) 35 | .yes { 36 | (username.isNotBlank() && password.isNotBlank()) 37 | .yes { 38 | "basic " + Base64.encodeToString(( 39 | "$username:$password").toByteArray(), 40 | Base64.NO_WRAP 41 | ) 42 | } 43 | .otherwise { "" } 44 | } 45 | .otherwise { "token $accessToken" } 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/java/com/tanjiajun/androidgenericframework/di/ViewModelFactory.kt: -------------------------------------------------------------------------------- 1 | package com.tanjiajun.androidgenericframework.di 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.ViewModelProvider 5 | import dagger.Binds 6 | import dagger.MapKey 7 | import dagger.Module 8 | import javax.inject.Inject 9 | import javax.inject.Provider 10 | import kotlin.reflect.KClass 11 | 12 | /** 13 | * Created by TanJiaJun on 2019-08-07. 14 | */ 15 | class AndroidGenericFrameworkViewModelFactory @Inject constructor( 16 | private val creators: @JvmSuppressWildcards Map, Provider> 17 | ) : ViewModelProvider.Factory { 18 | 19 | override fun create(modelClass: Class): T = 20 | creators[modelClass] 21 | ?.let { 22 | creators[modelClass] 23 | ?.let { getViewModel(it) } 24 | ?: throw IllegalArgumentException("Unknown ViewModel class: ${modelClass.name}") 25 | } 26 | ?: creators.entries 27 | .find { modelClass.isAssignableFrom(it.key) } 28 | ?.value 29 | ?.let { getViewModel(it) } 30 | ?: throw IllegalArgumentException("Unknown ViewModel class: ${modelClass.name}") 31 | 32 | private fun getViewModel(provider: Provider): T = 33 | try { 34 | @Suppress("UNCHECKED_CAST") 35 | provider.get() as T 36 | } catch (e: Exception) { 37 | throw RuntimeException(e) 38 | } 39 | 40 | } 41 | 42 | @Module 43 | internal abstract class ViewModelBuilder { 44 | 45 | @Binds 46 | internal abstract fun bindViewModelFactory( 47 | factory: AndroidGenericFrameworkViewModelFactory 48 | ): ViewModelProvider.Factory 49 | 50 | } 51 | 52 | @Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER) 53 | @Retention(AnnotationRetention.RUNTIME) 54 | @MapKey 55 | annotation class ViewModelKey(val value: KClass) -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 18 | 19 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 41 | 42 | 47 | 48 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /app/src/main/java/com/tanjiajun/androidgenericframework/data/model/user/response/UserInfoData.kt: -------------------------------------------------------------------------------- 1 | package com.tanjiajun.androidgenericframework.data.model.user.response 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | /** 6 | * Created by TanJiaJun on 2019-07-31. 7 | */ 8 | data class UserInfoData( 9 | val id: Int, 10 | val login: String, 11 | @SerializedName("node_id") val nodeId: String, 12 | @SerializedName("avatar_url") val avatarUrl: String, 13 | @SerializedName("gravatar_id") val gravatarId: String, 14 | val url: String, 15 | @SerializedName("html_url") val htmlUrl: String, 16 | @SerializedName("followers_url") val followersUrl: String, 17 | @SerializedName("following_url") val followingUrl: String, 18 | @SerializedName("gists_url") val gistsUrl: String, 19 | @SerializedName("starred_url") val starredUrl: String, 20 | @SerializedName("subscriptions_url") val subscriptionsUrl: String, 21 | @SerializedName("organizations_url") val organizationsUrl: String, 22 | @SerializedName("repos_url") val reposUrl: String, 23 | @SerializedName("events_url") val eventsUrl: String, 24 | @SerializedName("received_events_url") val receivedEventsUrl: String, 25 | val type: String, 26 | @SerializedName("site_admin") val siteAdmin: Boolean, 27 | val name: String, 28 | val company: String, 29 | val blog: String, 30 | val location: String, 31 | val email: String, 32 | val hireable: String, 33 | val bio: String, 34 | @SerializedName("public_repos") val publicRepos: Int, 35 | @SerializedName("public_gists") val publicGists: Int, 36 | val followers: Int, 37 | val following: Int, 38 | @SerializedName("created_at") val createdAt: String, 39 | @SerializedName("updated_at") val updatedAt: String, 40 | @SerializedName("private_gists") val privateGists: Int, 41 | @SerializedName("total_private_repos") val totalPrivateRepos: Int, 42 | @SerializedName("owned_private_repos") val ownedPrivateRepos: Int, 43 | @SerializedName("disk_usage") val diskUsage: Int, 44 | val collaborators: Int, 45 | @SerializedName("two_factor_authentication") val twoFactorAuthentication: Boolean 46 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/tanjiajun/androidgenericframework/data/remote/ExceptionHandler.kt: -------------------------------------------------------------------------------- 1 | package com.tanjiajun.androidgenericframework.data.remote 2 | 3 | import android.util.MalformedJsonException 4 | import com.google.gson.JsonParseException 5 | import org.json.JSONException 6 | import retrofit2.HttpException 7 | import java.net.ConnectException 8 | import java.net.SocketTimeoutException 9 | import java.net.UnknownHostException 10 | import java.text.ParseException 11 | import javax.net.ssl.SSLException 12 | 13 | /** 14 | * Created by TanJiaJun on 2020-02-04. 15 | */ 16 | object ExceptionHandler { 17 | 18 | fun handleException(throwable: Throwable): ResponseThrowable = 19 | when (throwable) { 20 | is JsonParseException -> 21 | ResponseThrowable(errorCode = 0, errorMessage = "JsonParseException", throwable = throwable) 22 | 23 | is JSONException -> 24 | ResponseThrowable(errorCode = 0, errorMessage = "JSONException", throwable = throwable) 25 | 26 | is ParseException -> 27 | ResponseThrowable(errorCode = 0, errorMessage = "ParseException", throwable = throwable) 28 | 29 | is MalformedJsonException -> 30 | ResponseThrowable(errorCode = 0, errorMessage = "MalformedJsonException", throwable = throwable) 31 | 32 | is ConnectException -> 33 | ResponseThrowable(errorCode = 0, errorMessage = "ConnectException", throwable = throwable) 34 | 35 | is HttpException -> 36 | ResponseThrowable(errorCode = throwable.code(), errorMessage = throwable.message(), throwable = throwable) 37 | 38 | is SSLException -> 39 | ResponseThrowable(errorCode = 0, errorMessage = "SSLException", throwable = throwable) 40 | 41 | is SocketTimeoutException -> 42 | ResponseThrowable(errorCode = 0, errorMessage = "SocketTimeoutException", throwable = throwable) 43 | 44 | is UnknownHostException -> 45 | ResponseThrowable(errorCode = 0, errorMessage = "UnknownHostException", throwable = throwable) 46 | 47 | else -> 48 | ResponseThrowable(errorCode = 0, errorMessage = "UnknownError", throwable = throwable) 49 | } 50 | 51 | } -------------------------------------------------------------------------------- /app/src/test/java/com/tanjiajun/androidgenericframework/data/RepositoryRemoteDataSourceTest.kt: -------------------------------------------------------------------------------- 1 | package com.tanjiajun.androidgenericframework.data 2 | 3 | import com.google.common.truth.Truth.assertThat 4 | import com.tanjiajun.androidgenericframework.data.remote.repository.RepositoryRemoteDataSource 5 | import kotlinx.coroutines.runBlocking 6 | import okhttp3.mockwebserver.MockResponse 7 | import okhttp3.mockwebserver.MockWebServer 8 | import org.junit.Before 9 | import org.junit.Rule 10 | import org.junit.Test 11 | import org.junit.runner.RunWith 12 | import org.junit.runners.JUnit4 13 | import retrofit2.Retrofit 14 | import retrofit2.converter.gson.GsonConverterFactory 15 | import retrofit2.converter.scalars.ScalarsConverterFactory 16 | import java.time.LocalDateTime 17 | 18 | /** 19 | * Created by TanJiaJun on 2020/5/26. 20 | */ 21 | @RunWith(JUnit4::class) 22 | class RepositoryRemoteDataSourceTest { 23 | 24 | @get:Rule 25 | val mockWebServer = MockWebServer() 26 | 27 | private lateinit var remoteDataSource: RepositoryRemoteDataSource 28 | 29 | @Before 30 | fun setUp() { 31 | remoteDataSource = RepositoryRemoteDataSource( 32 | Retrofit.Builder() 33 | .addConverterFactory(ScalarsConverterFactory.create()) 34 | .addConverterFactory(GsonConverterFactory.create()) 35 | .baseUrl(mockWebServer.url("/").toString()) 36 | .build() 37 | ) 38 | } 39 | 40 | @Test 41 | fun fetchRepositories() { 42 | runBlocking { 43 | mockWebServer.enqueue(MockResponse().setResponseCode(200).setBody(repositoryDataJson)) 44 | remoteDataSource.fetchRepositories( 45 | languageName = "Kotlin", 46 | fromDateTime = LocalDateTime.now().minusMonths(1) 47 | ).first().run { 48 | assertThat(id).isEqualTo(repositoryData.id) 49 | assertThat(name).isEqualTo(repositoryData.name) 50 | assertThat(description).isEqualTo(repositoryData.description) 51 | assertThat(language).isEqualTo(repositoryData.language) 52 | assertThat(starCount).isEqualTo(repositoryData.starCount) 53 | assertThat(forkCount).isEqualTo(repositoryData.forkCount) 54 | } 55 | } 56 | } 57 | 58 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_error.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 12 | 13 | 16 | 17 | 18 | 19 | 24 | 25 | 37 | 38 |