├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── devyk │ │ └── kotlin_github │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── devyk │ │ │ └── kotlin_github │ │ │ ├── GitHubApp.kt │ │ │ ├── adapter │ │ │ ├── IssueListAdapter.kt │ │ │ ├── PeopleListAdapter.kt │ │ │ └── RepoListAdapter.kt │ │ │ ├── api │ │ │ ├── ActivityService.kt │ │ │ ├── AuthService.kt │ │ │ ├── IssueService.kt │ │ │ ├── RepositoryService.kt │ │ │ └── UserService.kt │ │ │ ├── config │ │ │ ├── Contacts.kt │ │ │ ├── NavViewItem.kt │ │ │ ├── NaviViewExt.kt │ │ │ ├── Themer.kt │ │ │ ├── UserConfig.kt │ │ │ └── settings │ │ │ │ ├── Configs.kt │ │ │ │ └── Settings.kt │ │ │ ├── mvp │ │ │ ├── base │ │ │ │ ├── BaseDetailActivity.kt │ │ │ │ ├── CommonListAdapter.kt │ │ │ │ ├── CommonListFragment.kt │ │ │ │ ├── CommonListPresenter.kt │ │ │ │ ├── CommonSinglePageFragment.kt │ │ │ │ ├── CommonViewPageAdapter.kt │ │ │ │ ├── CommonViewPagerFragment.kt │ │ │ │ ├── DataProvider.kt │ │ │ │ ├── GitHubPaging.kt │ │ │ │ └── ListPage.kt │ │ │ ├── m │ │ │ │ ├── AccountManager.kt │ │ │ │ ├── IssueModel.kt │ │ │ │ ├── PeopleModel.kt │ │ │ │ ├── RepoModel.kt │ │ │ │ └── entity │ │ │ │ │ ├── AccountEntities.kt │ │ │ │ │ ├── Issue.kt │ │ │ │ │ ├── PagingWrapper.kt │ │ │ │ │ ├── Repository.kt │ │ │ │ │ ├── SearchRepositories.kt │ │ │ │ │ ├── SubscriptionBody.kt │ │ │ │ │ ├── SubscriptionResponse.kt │ │ │ │ │ └── User.kt │ │ │ ├── p │ │ │ │ ├── LoginPresenter.kt │ │ │ │ ├── MainPersenter.kt │ │ │ │ ├── MyIssuePresenter.kt │ │ │ │ ├── PeopleListPresenter.kt │ │ │ │ └── RepoListPresenter.kt │ │ │ └── v │ │ │ │ ├── AboutFragment.kt │ │ │ │ ├── MainActivity.kt │ │ │ │ ├── RepoDetailActivity.kt │ │ │ │ ├── issue │ │ │ │ ├── MyIssueFragment.kt │ │ │ │ └── MyIssueListFragment.kt │ │ │ │ ├── login │ │ │ │ └── LoginActivity.kt │ │ │ │ ├── people │ │ │ │ ├── PeopleFragment.kt │ │ │ │ └── PeopleListFragment.kt │ │ │ │ └── repo │ │ │ │ ├── RepoFragment.kt │ │ │ │ └── RepoListFragment.kt │ │ │ └── widget │ │ │ ├── ActionBarController.kt │ │ │ ├── DetailItemView.kt │ │ │ ├── FixLuRecyclerView.java │ │ │ └── NavigationController.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── ic_about_us.xml │ │ ├── ic_day.xml │ │ ├── ic_fork.xml │ │ ├── ic_github.xml │ │ ├── ic_go.xml │ │ ├── ic_issue.xml │ │ ├── ic_issue_closed.xml │ │ ├── ic_issue_open.xml │ │ ├── ic_launcher_background.xml │ │ ├── ic_night.xml │ │ ├── ic_people.xml │ │ ├── ic_repository.xml │ │ ├── ic_round.xml │ │ ├── ic_speech_bubble.xml │ │ ├── ic_star.xml │ │ ├── ic_star_o.xml │ │ ├── ic_unwatch.xml │ │ ├── ic_watch.xml │ │ ├── sel_daynight.xml │ │ ├── sel_star.xml │ │ ├── sel_watch.xml │ │ ├── side_nav_bar.xml │ │ └── side_nav_bar_night.xml │ │ ├── layout │ │ ├── activity_login.xml │ │ ├── activity_main.xml │ │ ├── activity_repo_details.xml │ │ ├── app_bar_details.xml │ │ ├── app_bar_main.xml │ │ ├── app_bar_simple.xml │ │ ├── detail_item.xml │ │ ├── fragment_common_list.xml │ │ ├── item_card.xml │ │ ├── item_issue.xml │ │ ├── item_repo.xml │ │ ├── item_user.xml │ │ ├── menu_item_daynight.xml │ │ └── nav_header_main.xml │ │ ├── menu │ │ ├── activity_actionbar.xml │ │ ├── activity_main_drawer.xml │ │ └── activity_main_drawer_no_logged_in.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── github.png │ │ ├── github_about.png │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── ids.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── devyk │ └── kotlin_github │ ├── BooleanExpTest.kt │ └── ExampleUnitTest.kt ├── build.gradle ├── common ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── devyk │ │ └── common │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ ├── android │ │ │ └── support │ │ │ │ └── v7 │ │ │ │ └── widget │ │ │ │ └── TintableToggleButton.kt │ │ ├── com │ │ │ └── devyk │ │ │ │ └── common │ │ │ │ ├── App.kt │ │ │ │ ├── anno │ │ │ │ └── PoKo.kt │ │ │ │ ├── compat │ │ │ │ ├── Tls12.kt │ │ │ │ └── Tls12SocketFactory.java │ │ │ │ ├── config │ │ │ │ └── UserInfo.kt │ │ │ │ ├── ext │ │ │ │ ├── BooleanExp.kt │ │ │ │ ├── ConfirmDialog.kt │ │ │ │ ├── CoroutinesExt.kt │ │ │ │ ├── DeviceId.kt │ │ │ │ ├── FileExt.kt │ │ │ │ ├── FragmentContainerExt.kt │ │ │ │ ├── GlideExt.kt │ │ │ │ ├── GsonExt.kt │ │ │ │ ├── InputMethod.kt │ │ │ │ ├── LogExt.kt │ │ │ │ ├── NumberExt.kt │ │ │ │ ├── PrefExt.kt │ │ │ │ ├── PreferencesExp.kt │ │ │ │ ├── PropertiesDelegate.kt │ │ │ │ ├── Result.java │ │ │ │ ├── RxJavaExt.kt │ │ │ │ ├── ViewExt.kt │ │ │ │ ├── bindExtra.kt │ │ │ │ └── delegateOf.kt │ │ │ │ ├── mvp │ │ │ │ ├── ILifecycle.kt │ │ │ │ ├── impl │ │ │ │ │ ├── BaseActivity.kt │ │ │ │ │ ├── BaseFragment.kt │ │ │ │ │ └── BasePresenter.kt │ │ │ │ └── mvps.kt │ │ │ │ ├── network │ │ │ │ ├── RESTFulService.kt │ │ │ │ └── interceptor │ │ │ │ │ ├── AcceptInterceptor.kt │ │ │ │ │ ├── AuthInterceptor.kt │ │ │ │ │ └── CacheInterceptor.kt │ │ │ │ ├── utils │ │ │ │ ├── AdapterList.kt │ │ │ │ ├── Network.kt │ │ │ │ ├── ViewPagerAdapterList.kt │ │ │ │ ├── Weak.kt │ │ │ │ └── githubDateFormatter.kt │ │ │ │ └── weiget │ │ │ │ ├── AppBarLayoutBehavior.kt │ │ │ │ ├── AppCompatAvatarImageView.java │ │ │ │ └── ErrorInfoView.kt │ │ └── retrofit2 │ │ │ └── adapter │ │ │ └── rxjava2 │ │ │ ├── RxJava2CallAdapter2.java │ │ │ └── RxJava2CallAdapterFactory2.java │ └── res │ │ ├── anim │ │ ├── left_in.xml │ │ ├── left_out.xml │ │ ├── rignt_in.xml │ │ └── rignt_out.xml │ │ ├── drawable │ │ ├── side_nav_bar.xml │ │ └── side_nav_bar_night.xml │ │ ├── values-sw1024dp │ │ └── dimens.xml │ │ ├── values-sw1280dp │ │ └── dimens.xml │ │ ├── values-sw384dp │ │ └── dimens.xml │ │ ├── values-sw392dp │ │ └── dimens.xml │ │ ├── values-sw400dp │ │ └── dimens.xml │ │ ├── values-sw410dp │ │ └── dimens.xml │ │ ├── values-sw411dp │ │ └── dimens.xml │ │ ├── values-sw432dp │ │ └── dimens.xml │ │ ├── values-sw480dp │ │ └── dimens.xml │ │ ├── values-sw533dp │ │ └── dimens.xml │ │ ├── values-sw592dp │ │ └── dimens.xml │ │ ├── values-sw600dp │ │ └── dimens.xml │ │ ├── values-sw640dp │ │ └── dimens.xml │ │ ├── values-sw662dp │ │ └── dimens.xml │ │ ├── values-sw720dp │ │ └── dimens.xml │ │ ├── values-sw768dp │ │ └── dimens.xml │ │ ├── values-sw800dp │ │ └── dimens.xml │ │ ├── values-sw820dp │ │ └── dimens.xml │ │ ├── values-sw960dp │ │ └── dimens.xml │ │ └── values │ │ ├── attrs.xml │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── devyk │ └── common │ └── ExampleUnitTest.java ├── config.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── screenMatch.properties └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | local.properties 4 | .idea 5 | .DS_Store 6 | build 7 | captures 8 | .externalNativeBuild -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 介绍 2 | 3 | 由于官方推荐使用 Kotlin 语言来开发 Android 程序,之前一直没有时间来学习这门语言,最近抽出了一点时间写了一个 kotlin 版本的 APP ,现在把它开源出来,效果图如下: 4 | 5 | 效果图看不见请移步:https://juejin.im/post/5dc294d5f265da4d4434afc9 6 | 7 | ![MPbvb4.gif](https://s2.ax1x.com/2019/11/06/MPbvb4.gif) 8 | 9 | ## 使用框架 10 | 11 | - MVP 12 | - retrofit 13 | - glide 14 | - anko 15 | - okhttp 16 | - RxJava 17 | - sw 屏幕适配 18 | 19 | ## 项目地址 20 | 21 | [Kotlin_GitHub](https://github.com/yangkun19921001/Kotlin_GitHub.git) 22 | 23 | 由于作者是第一次接触 Kotlin 开发 APP , 写的不好的地方还望多多指教,此项目非常适合新手入门。 24 | 25 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | apply plugin: 'kotlin-android' 4 | 5 | apply plugin: 'kotlin-android-extensions' 6 | 7 | 8 | 9 | kotlin { 10 | androidExtensions { 11 | experimental = true 12 | } 13 | } 14 | 15 | 16 | android { 17 | compileSdkVersion rootProject.ext.android.compileSdkVersion 18 | buildToolsVersion "28.0.3" 19 | defaultConfig { 20 | applicationId "com.devyk.kotlin_github" 21 | minSdkVersion rootProject.ext.android.minSdkVersion 22 | targetSdkVersion rootProject.ext.android.targetSdkVersion 23 | versionCode rootProject.ext.android.versionCode 24 | versionName rootProject.ext.android.versionName 25 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 26 | 27 | //dex 分包 28 | multiDexEnabled true 29 | 30 | // 如果需要使用兼容库对svg的支持,就需要这样配置 31 | vectorDrawables.useSupportLibrary = true 32 | } 33 | buildTypes { 34 | release { 35 | minifyEnabled false 36 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 37 | } 38 | } 39 | } 40 | 41 | dependencies { 42 | implementation fileTree(dir: 'libs', include: ['*.jar']) 43 | testImplementation 'junit:junit:4.12' 44 | 45 | api project(path: ':common') 46 | 47 | //Dex 分包 48 | implementation 'com.android.support:multidex:1.0.1' 49 | 50 | implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.46' 51 | 52 | implementation 'com.github.jdsjlzx:LRecyclerView:1.5.0' 53 | 54 | 55 | } 56 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/devyk/kotlin_github/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.kotlin_github 2 | 3 | import androidx.test.InstrumentationRegistry 4 | import androidx.test.runner.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getTargetContext() 22 | assertEquals("com.devyk.kotlin_github", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 17 | 20 | 21 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/kotlin_github/GitHubApp.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.kotlin_github 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import android.support.multidex.MultiDex 6 | import android.support.v7.app.AppCompatDelegate 7 | import com.bennyhuo.swipefinishable.SwipeFinishable 8 | import com.devyk.common.App 9 | 10 | /** 11 | *
12 |  *     author  : devyk on 2019-10-30 16:09
13 |  *     blog    : https://juejin.im/user/578259398ac2470061f3a3fb/posts
14 |  *     github  : https://github.com/yangkun19921001
15 |  *     mailbox : yang1001yk@gmail.com
16 |  *     desc    : This is GitHubApp
17 |  * 
18 | */ 19 | 20 | 21 | class GitHubApp : Application(){ 22 | 23 | 24 | 25 | 26 | /** 27 | * 声明一个伴生对象 28 | */ 29 | companion object { 30 | /** 31 | * lateinit : 允许在构造函数之外初始化非空属性 32 | * 为应用级别的 APP 提供全局 Context 33 | */ 34 | lateinit var instance: GitHubApp; 35 | 36 | } 37 | 38 | 39 | /** 40 | * 重写 Application onCreate 生命周期函数 41 | */ 42 | override fun onCreate() { 43 | super.onCreate() 44 | //init GitHubApp 全局上下文 45 | instance = this; 46 | //滑动关闭 Activity 47 | SwipeFinishable.INSTANCE.init(this); 48 | //适配低版本向量图 49 | AppCompatDelegate.setCompatVectorFromResourcesEnabled(true) 50 | //init App 可以获取内部的 Application 级别的 Context 51 | App.init(this); 52 | 53 | 54 | 55 | } 56 | 57 | 58 | /** 59 | * 重写 Application 生命周期函数,刚刚创建出来,比 onCreate 更早 60 | */ 61 | override fun attachBaseContext(base: Context?) { 62 | MultiDex.install(this); 63 | super.attachBaseContext(base) 64 | } 65 | } -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/kotlin_github/adapter/IssueListAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.kotlin_github.adapter 2 | 3 | import android.support.v7.widget.RecyclerView 4 | import android.view.View 5 | import com.devyk.common.ext.htmlText 6 | import com.devyk.common.utils.githubTimeToDate 7 | import com.devyk.common.utils.view 8 | import com.devyk.kotlin_github.R 9 | import com.devyk.kotlin_github.mvp.base.CommonListAdapter 10 | import com.devyk.kotlin_github.mvp.m.entity.Issue 11 | import kotlinx.android.synthetic.main.item_issue.view.* 12 | import org.jetbrains.anko.imageResource 13 | 14 | /** 15 | *
16 |  *     author  : devyk on 2019-11-05 11:01
17 |  *     blog    : https://juejin.im/user/578259398ac2470061f3a3fb/posts
18 |  *     github  : https://github.com/yangkun19921001
19 |  *     mailbox : yang1001yk@gmail.com
20 |  *     desc    : This is IssueListAdapter
21 |  * 
22 | */ 23 | class IssueListAdapter : CommonListAdapter(R.layout.item_issue) { 24 | override fun onBindData(viewHolder: RecyclerView.ViewHolder, issue: Issue) { 25 | viewHolder.itemView.run { 26 | iconView.imageResource = if (issue.state == "open") R.drawable.ic_issue_open else R.drawable.ic_issue_closed 27 | titleView.text = issue.title 28 | timeView.text = githubTimeToDate(issue.created_at).view() 29 | bodyView.htmlText = issue.body_html 30 | commentCount.text = issue.comments.toString() 31 | } 32 | 33 | } 34 | 35 | override fun onItemClicked(itemView: View, item: Issue) { 36 | 37 | } 38 | } -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/kotlin_github/adapter/PeopleListAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.kotlin_github.adapter 2 | 3 | import android.support.v7.widget.RecyclerView 4 | import android.view.View 5 | import com.bennyhuo.github.network.entities.User 6 | import com.devyk.common.ext.loadWithGlide 7 | import com.devyk.kotlin_github.R 8 | import com.devyk.kotlin_github.mvp.base.CommonListAdapter 9 | import kotlinx.android.synthetic.main.item_user.view.* 10 | 11 | /** 12 | *
13 |  *     author  : devyk on 2019-11-06 10:02
14 |  *     blog    : https://juejin.im/user/578259398ac2470061f3a3fb/posts
15 |  *     github  : https://github.com/yangkun19921001
16 |  *     mailbox : yang1001yk@gmail.com
17 |  *     desc    : This is PeopleListAdapter
18 |  * 
19 | */ 20 | class PeopleListAdapter : CommonListAdapter(R.layout.item_user) { 21 | override fun onBindData(viewHolder: RecyclerView.ViewHolder, user: User) { 22 | viewHolder.itemView.apply { 23 | avatarView.loadWithGlide(user.avatar_url, user.login.first()) 24 | nameView.text = user.login 25 | 26 | } 27 | } 28 | 29 | override fun onItemClicked(itemView: View, item: User) { 30 | } 31 | } -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/kotlin_github/adapter/RepoListAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.kotlin_github.adapter 2 | 3 | import android.content.Intent 4 | import android.os.Bundle 5 | import android.support.v4.app.FragmentActivity 6 | import android.support.v7.widget.RecyclerView 7 | import android.view.View 8 | import com.devyk.common.App 9 | import com.devyk.common.ext.loadWithGlide 10 | import com.devyk.common.ext.toKilo 11 | import com.devyk.kotlin_github.R 12 | import com.devyk.kotlin_github.mvp.base.CommonListAdapter 13 | import com.devyk.kotlin_github.mvp.m.entity.Repository 14 | import com.devyk.kotlin_github.mvp.v.RepoDetailActivity 15 | import kotlinx.android.synthetic.main.item_repo.view.* 16 | import org.jetbrains.anko.newTask 17 | 18 | /** 19 | *
20 |  *     author  : devyk on 2019-11-05 13:56
21 |  *     blog    : https://juejin.im/user/578259398ac2470061f3a3fb/posts
22 |  *     github  : https://github.com/yangkun19921001
23 |  *     mailbox : yang1001yk@gmail.com
24 |  *     desc    : This is RepoListAdapter
25 |  * 
26 | */ 27 | class RepoListAdapter() : CommonListAdapter(R.layout.item_repo) { 28 | override fun onBindData(viewHolder: RecyclerView.ViewHolder, repository: Repository) { 29 | viewHolder.itemView.apply { 30 | avatarView.loadWithGlide(repository.owner.avatar_url, repository.owner.login.first()) 31 | repoNameView.text = repository.name 32 | descriptionView.text = repository.description 33 | langView.text = repository.language ?: "Unknown" 34 | starView.text = repository.stargazers_count.toKilo() 35 | forkView.text = repository.forks_count.toKilo() 36 | } 37 | } 38 | 39 | override fun onItemClicked(itemView: View, item: Repository) { 40 | var intent = Intent(App.getInstance(), RepoDetailActivity::class.java); 41 | intent.newTask() 42 | var bundle = Bundle() 43 | bundle.putParcelable("repository", item) 44 | intent.putExtras(bundle) 45 | 46 | App.getInstance().applicationContext.startActivity(intent) 47 | 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/kotlin_github/api/ActivityService.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.kotlin_github.api 2 | 3 | import SubscriptionBody 4 | import SubscriptionResponse 5 | import WATCH 6 | import com.devyk.common.network.RETROFIT 7 | import com.devyk.kotlin_github.mvp.base.GitHubPaging 8 | import com.devyk.kotlin_github.mvp.m.entity.Repository 9 | import io.reactivex.Observable 10 | import kotlinx.coroutines.Deferred 11 | import retrofit2.Response 12 | import retrofit2.http.* 13 | 14 | 15 | interface ActivityApi{ 16 | //region star 17 | @GET("/user/starred/{owner}/{repo}") 18 | fun isStarred(@Path("owner") owner: String, @Path("repo") repo: String): Observable> 19 | 20 | @PUT("/user/starred/{owner}/{repo}") 21 | fun star(@Path("owner") owner: String, @Path("repo") repo: String): Observable 22 | 23 | @DELETE("/user/starred/{owner}/{repo}") 24 | fun unstar(@Path("owner") owner: String, @Path("repo") repo: String): Observable 25 | 26 | @GET("/users/{username}/starred") 27 | fun reposStarredBy(@Path("username") username: String): Observable> 28 | 29 | @GET("/user/starred") 30 | fun reposStarredByCurrentUser(): Observable> 31 | //endregion 32 | 33 | //region watch / subscription 34 | @GET("/repos/{owner}/{repo}/subscription") 35 | fun isWatched(@Path("owner") owner: String, @Path("repo") repo: String): Observable 36 | 37 | @GET("/repos/{owner}/{repo}/subscription") 38 | fun isWatchedDeferred(@Path("owner") owner: String, @Path("repo") repo: String): Observable 39 | 40 | @PUT("/repos/{owner}/{repo}/subscription") 41 | @Headers("Content-Type:application/json") 42 | fun watch(@Path("owner") owner: String, @Path("repo") repo: String, @Body subscriptionBody: SubscriptionBody = WATCH): Observable 43 | 44 | @DELETE("/repos/{owner}/{repo}/subscription") 45 | fun unwatch(@Path("owner") owner: String, @Path("repo") repo: String): Observable 46 | 47 | @GET("/users/{username}/subscriptions") 48 | fun reposWatchedBy(@Path("username") username: String): Observable> 49 | 50 | @GET("/user/subscriptions") 51 | fun reposWatchedByCurrentUser(): Observable> 52 | 53 | //endregion 54 | } 55 | 56 | object ActivityService: ActivityApi by RETROFIT.create(ActivityApi::class.java) -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/kotlin_github/api/AuthService.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.kotlin_github.api 2 | 3 | import com.devyk.common.network.RETROFIT 4 | import com.devyk.kotlin_github.mvp.m.entity.AuthorizationReq 5 | import com.devyk.kotlin_github.mvp.m.entity.AuthorizationRsp 6 | import io.reactivex.Observable 7 | import retrofit2.Response 8 | import retrofit2.http.Body 9 | import retrofit2.http.DELETE 10 | import retrofit2.http.PUT 11 | import retrofit2.http.Path 12 | 13 | interface AuthApi{ 14 | 15 | @PUT("/authorizations/clients/${Configs.Account.clientId}/{fingerPrint}") 16 | fun createAuthorization(@Body req: AuthorizationReq, @Path("fingerPrint") fingerPrint: String = Configs.Account.fingerPrint) 17 | : Observable 18 | 19 | @DELETE("/authorizations/{id}") 20 | fun deleteAuthorization(@Path("id") id: Int): Observable> 21 | 22 | } 23 | 24 | object AuthService: AuthApi by RETROFIT.create(AuthApi::class.java) -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/kotlin_github/api/IssueService.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.kotlin_github.api 2 | 3 | import com.devyk.common.network.RETROFIT 4 | import com.devyk.kotlin_github.mvp.base.GitHubPaging 5 | import com.devyk.kotlin_github.mvp.m.entity.Issue 6 | import io.reactivex.Observable 7 | import retrofit2.http.GET 8 | import retrofit2.http.Query 9 | 10 | /** 11 | * Created by benny on 2/4/18. 12 | */ 13 | interface IssueApi{ 14 | @GET("/issues?filter=all&state=all") 15 | fun listIssuesOfAuthenticatedUser(@Query("page") page: Int = 1, @Query("per_page") per_page: Int = 20): Observable> 16 | 17 | } 18 | 19 | object IssueService : IssueApi by RETROFIT.create(IssueApi::class.java) -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/kotlin_github/api/RepositoryService.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.kotlin_github.api 2 | 3 | import com.devyk.common.network.FORCE_NETWORK 4 | import com.devyk.common.network.RETROFIT 5 | import com.devyk.kotlin_github.mvp.base.GitHubPaging 6 | import com.devyk.kotlin_github.mvp.m.entity.Repository 7 | import com.devyk.kotlin_github.mvp.m.entity.SearchRepositories 8 | import io.reactivex.Observable 9 | import retrofit2.http.GET 10 | import retrofit2.http.Path 11 | import retrofit2.http.Query 12 | 13 | /** 14 | *
15 |  *     author  : devyk on 2019-11-05 13:18
16 |  *     blog    : https://juejin.im/user/578259398ac2470061f3a3fb/posts
17 |  *     github  : https://github.com/yangkun19921001
18 |  *     mailbox : yang1001yk@gmail.com
19 |  *     desc    : This is RepositoryService
20 |  * 
21 | */ 22 | 23 | interface RepositoryApi{ 24 | 25 | @GET("/users/{owner}/repos?type=all") 26 | fun listRepositoriesOfUser(@Path("owner") owner: String, @Query("page") page: Int = 1, @Query("per_page") per_page: Int = 20): Observable> 27 | 28 | @GET("/search/repositories?order=desc&sort=updated") 29 | fun allRepositories(@Query("page") page: Int = 1, @Query("q") q: String, @Query("per_page") per_page: Int = 20): Observable 30 | 31 | @GET("/repos/{owner}/{repo}") 32 | fun getRepository(@Path("owner") owner: String, @Path("repo") repo: String, @Query(FORCE_NETWORK) forceNetwork: Boolean = false): Observable 33 | } 34 | 35 | object RepositoryService : RepositoryApi by RETROFIT.create(RepositoryApi::class.java) -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/kotlin_github/api/UserService.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.kotlin_github.api 2 | 3 | import com.bennyhuo.github.network.entities.User 4 | import com.devyk.common.network.RETROFIT 5 | import com.devyk.kotlin_github.mvp.base.GitHubPaging 6 | import io.reactivex.Observable 7 | import retrofit2.http.GET 8 | import retrofit2.http.Path 9 | import retrofit2.http.Query 10 | 11 | interface UserApi { 12 | 13 | @GET("/user") 14 | fun getAuthenticatedUser(): Observable 15 | 16 | @GET("/users") 17 | fun allUsers(@Query("since") since: Int, @Query("per_page") per_page: Int = 20): Observable> 18 | 19 | @GET("/users/{login}/following") 20 | fun following(@Path("login") login: String, @Query("page") page: Int = 1, @Query("per_page") per_page: Int = 20): Observable> 21 | 22 | @GET("/users/{login}/followers") 23 | fun followers(@Path("login") login: String, @Query("page") page: Int = 1): Observable> 24 | 25 | 26 | } 27 | 28 | object UserService: UserApi by RETROFIT.create(UserApi::class.java) -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/kotlin_github/config/Contacts.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.kotlin_github.config 2 | 3 | /** 4 | *
 5 |  *     author  : devyk on 2019-10-31 12:50
 6 |  *     blog    : https://juejin.im/user/578259398ac2470061f3a3fb/posts
 7 |  *     github  : https://github.com/yangkun19921001
 8 |  *     mailbox : yang1001yk@gmail.com
 9 |  *     desc    : This is Contacts
10 |  * 
11 | */ 12 | class Contacts { 13 | 14 | public val REQUIRED_TYPE: String 15 | get() = "type" 16 | 17 | val OPTIONAL_LOGIN: String 18 | get() = "login" 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/kotlin_github/config/NavViewItem.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.kotlin_github.config 2 | 3 | import android.os.Bundle 4 | import android.support.annotation.DrawableRes 5 | import android.support.annotation.IdRes 6 | import android.support.v4.app.Fragment 7 | import com.devyk.kotlin_github.R 8 | import com.devyk.kotlin_github.mvp.v.AboutFragment 9 | import com.devyk.kotlin_github.mvp.v.issue.MyIssueFragment 10 | import com.devyk.kotlin_github.mvp.v.people.PeopleFragment 11 | import com.devyk.kotlin_github.mvp.v.repo.RepoFragment 12 | 13 | 14 | class NavViewItem private constructor(val groupId: Int = 0, val title: String, @DrawableRes val icon: Int, val fragmentClass: Class, val arguements: Bundle = Bundle()){ 15 | 16 | companion object{ 17 | private val items = mapOf( 18 | R.id.navRepos to NavViewItem(0, "Repository", R.drawable.ic_repository, RepoFragment::class.java , Bundle().apply { putParcelable("user", null) }), 19 | R.id.navPeople to NavViewItem(0, "People", R.drawable.ic_people, PeopleFragment::class.java), 20 | R.id.navIssue to NavViewItem(0, "Issue", R.drawable.ic_issue, MyIssueFragment::class.java), 21 | R. id.navAbout to NavViewItem(0, "About",R. drawable.ic_about_us, AboutFragment::class.java) 22 | ) 23 | 24 | operator fun get(@IdRes navId: Int): NavViewItem { 25 | return items[navId]?: items[R.id.navRepos]!! 26 | } 27 | 28 | operator fun get(item: NavViewItem): Int{ 29 | return items.filter { it.value == item }.keys.first() 30 | } 31 | } 32 | 33 | override fun toString(): String { 34 | return "NavViewItem(groupId=$groupId, title='$title', icon=$icon, fragmentClass=$fragmentClass, arguements=$arguements)" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/kotlin_github/config/NaviViewExt.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.kotlin_github.config 2 | 3 | import android.annotation.SuppressLint 4 | import android.support.annotation.IdRes 5 | import android.support.design.widget.NavigationView 6 | import android.support.v4.view.GravityCompat 7 | import android.support.v4.view.ViewCompat 8 | import android.support.v4.widget.DrawerLayout 9 | import android.support.v7.view.menu.MenuItemImpl 10 | import android.view.View 11 | import com.bennyhuo.common.log.logger 12 | import com.devyk.common.ext.otherwise 13 | import com.devyk.common.ext.yes 14 | 15 | 16 | inline fun NavigationView.doOnLayoutAvailable(crossinline block: () -> Unit) { 17 | ViewCompat.isLaidOut(this).yes { 18 | block() 19 | }.otherwise { 20 | addOnLayoutChangeListener(object : View.OnLayoutChangeListener { 21 | override fun onLayoutChange(v: View?, left: Int, top: Int, right: Int, bottom: Int, oldLeft: Int, oldTop: Int, oldRight: Int, oldBottom: Int) { 22 | removeOnLayoutChangeListener(this) 23 | block() 24 | } 25 | }) 26 | } 27 | } 28 | 29 | 30 | /** 31 | * 选择指定的菜单,并执行相应的操作 32 | */ 33 | @SuppressLint("RestrictedApi") 34 | fun NavigationView.selectItem(@IdRes resId: Int){ 35 | doOnLayoutAvailable { 36 | logger.debug("select Item: ${NavViewItem[resId].title}") 37 | setCheckedItem(resId) 38 | (menu.findItem(resId) as MenuItemImpl)() 39 | } 40 | } 41 | 42 | inline fun DrawerLayout.afterClosed(crossinline block: () -> Unit){ 43 | if(isDrawerOpen(GravityCompat.START)) { 44 | closeDrawer(GravityCompat.START) 45 | addDrawerListener( 46 | object : DrawerLayout.DrawerListener { 47 | override fun onDrawerStateChanged(newState: Int) = Unit 48 | override fun onDrawerSlide(drawerView: View, slideOffset: Float) = Unit 49 | override fun onDrawerOpened(drawerView: View) = Unit 50 | 51 | override fun onDrawerClosed(drawerView: View) { 52 | removeDrawerListener(this) 53 | block() 54 | } 55 | }) 56 | } else { 57 | block() 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/kotlin_github/config/Themer.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.kotlin_github.config 2 | 3 | import android.app.Activity 4 | import android.support.annotation.StyleRes 5 | import com.devyk.kotlin_github.R 6 | 7 | object Themer { 8 | enum class ThemeMode(@StyleRes val normal: Int, @StyleRes val translucent: Int){ 9 | DAY(R.style.AppTheme, R.style.AppTheme_Translucent), NIGHT(R.style.AppTheme_Dark, R.style.AppTheme_Dark_Translucent) 10 | } 11 | 12 | fun applyProperTheme(activity: Activity, translucent: Boolean = false){ 13 | activity.setTheme(currentTheme().let { if(translucent) it.translucent else it.normal }) 14 | } 15 | 16 | fun currentTheme() = ThemeMode.valueOf(Settings.themeMode) 17 | 18 | fun toggle(activity: Activity){ 19 | when(currentTheme()){ 20 | Themer.ThemeMode.DAY -> Settings.themeMode = ThemeMode.NIGHT.name 21 | Themer.ThemeMode.NIGHT -> Settings.themeMode = ThemeMode.DAY.name 22 | } 23 | activity.recreate() 24 | } 25 | } -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/kotlin_github/config/UserConfig.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.kotlin_github.config 2 | 3 | import com.devyk.common.ext.PropertiesDelegate 4 | 5 | /** 6 | *
 7 |  *     author  : devyk on 2019-10-31 11:11
 8 |  *     blog    : https://juejin.im/user/578259398ac2470061f3a3fb/posts
 9 |  *     github  : https://github.com/yangkun19921001
10 |  *     mailbox : yang1001yk@gmail.com
11 |  *     desc    : This is UserConfig 用户信息管理类
12 |  * 
13 | */ 14 | class UserConfig(path: String) : PropertiesDelegate.AbsProperties(path) { 15 | /** 16 | * 通过属性代理 17 | * 将接口的实现委托给另一个对象 18 | * 将属性访问器的实现委托给另一个对象 19 | */ 20 | var name: String by PROPERTIES 21 | var email: String by PROPERTIES 22 | 23 | 24 | fun config( 25 | name: String = "yangkun19921001", 26 | email: String = "yang1001yk@gmail.com" 27 | 28 | /** 29 | * let 语法支持传递当前this 进去,默认就返回当前 this 30 | */ 31 | ) = let { 32 | it.name = name 33 | it.email = email; 34 | } 35 | 36 | } 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/kotlin_github/config/settings/Configs.kt: -------------------------------------------------------------------------------- 1 | 2 | import com.bennyhuo.common.log.logger 3 | import com.devyk.common.App 4 | import com.devyk.common.ext.deviceId 5 | 6 | object Configs { 7 | 8 | object Account{ 9 | val SCOPES = listOf("user", "repo", "notifications", "gist", "admin:org") 10 | const val clientId = "978939bf3ceadc720e5e" 11 | const val clientSecret = "4aa3d83510fb20800e97c55144ccc451f5eaba90" 12 | const val note = "kotlin" 13 | const val noteUrl = "https://github.com/yangkun19921001" 14 | 15 | val fingerPrint by lazy { 16 | (App.getInstance().deviceId + clientId).also { logger.info("fingerPrint: "+it) } 17 | } 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/kotlin_github/config/settings/Settings.kt: -------------------------------------------------------------------------------- 1 | 2 | import com.devyk.common.App 3 | import com.devyk.common.config.UserInfo 4 | import com.devyk.common.ext.pref 5 | import com.devyk.kotlin_github.R 6 | 7 | 8 | object Settings { 9 | var lastPage: Int 10 | get() = if(lastPageIdString.isEmpty()) 0 else App.getInstance().resources.getIdentifier(lastPageIdString, "id", App.getInstance().packageName) 11 | set(value) { 12 | lastPageIdString = App.getInstance().resources.getResourceEntryName(value) 13 | } 14 | 15 | val defaultPage 16 | get() = if(UserInfo.isLoginIn()) defaultPageForUser else defaultPageForVisitor 17 | 18 | private var defaultPageForUser by pref(R.id.navRepos) 19 | 20 | private var defaultPageForVisitor by pref(R.id.navRepos) 21 | 22 | private var lastPageIdString by pref("") 23 | 24 | var themeMode by pref("DAY") 25 | 26 | 27 | 28 | } -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/kotlin_github/mvp/base/BaseDetailActivity.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.kotlin_github.mvp.base 2 | 3 | import android.os.Bundle 4 | import android.support.v7.app.AppCompatActivity 5 | import android.view.GestureDetector 6 | import android.view.MenuItem 7 | import android.view.MotionEvent 8 | import com.bennyhuo.swipefinishable.SwipeFinishable 9 | import com.devyk.kotlin_github.R 10 | import com.devyk.kotlin_github.config.Themer 11 | import org.jetbrains.anko.dip 12 | 13 | /** 14 | *
 15 |  *     author  : devyk on 2019-11-06 10:22
 16 |  *     blog    : https://juejin.im/user/578259398ac2470061f3a3fb/posts
 17 |  *     github  : https://github.com/yangkun19921001
 18 |  *     mailbox : yang1001yk@gmail.com
 19 |  *     desc    : This is BaseDetailActivity
 20 |  * 
21 | */ 22 | abstract class BaseDetailActivity : AppCompatActivity() { 23 | 24 | 25 | /** 26 | * 定义一个滑动返回的委托属性 27 | */ 28 | private val swipeBackTouchDelegate by lazy { 29 | SwipeBackTouchDelegate(this, ::finish) 30 | } 31 | 32 | 33 | override fun onCreate(savedInstanceState: Bundle?) { 34 | super.onCreate(savedInstanceState) 35 | Themer.applyProperTheme(this) 36 | } 37 | 38 | override fun onOptionsItemSelected(item: MenuItem?): Boolean { 39 | when (item!!.itemId) { 40 | android.R.id.home -> { 41 | finish() 42 | } 43 | } 44 | return super.onOptionsItemSelected(item) 45 | } 46 | 47 | override fun finish() { 48 | super.finish() 49 | overridePendingTransition(R.anim.left_in, R.anim.rignt_out) 50 | } 51 | 52 | override fun dispatchTouchEvent(ev: MotionEvent): Boolean { 53 | return swipeBackTouchDelegate.onTouchEvent(ev) || super.dispatchTouchEvent(ev) 54 | } 55 | } 56 | 57 | 58 | class SwipeBackTouchDelegate(val activity: AppCompatActivity, block: () -> Unit) { 59 | companion object { 60 | private const val MIN_FLING_TO_BACK = 2000 61 | } 62 | 63 | private val minFLlingToBack by lazy { 64 | activity.dip(MIN_FLING_TO_BACK) 65 | } 66 | 67 | private val swipeBackDelegate by lazy { 68 | GestureDetector(activity, object : GestureDetector.SimpleOnGestureListener() { 69 | override fun onFling(e1: MotionEvent?, e2: MotionEvent?, velocityX: Float, velocityY: Float): Boolean { 70 | //如果滑动的距离超过 2000 就关闭 71 | return if (velocityX > minFLlingToBack) { 72 | block() 73 | true 74 | } else { 75 | false 76 | } 77 | } 78 | }) 79 | 80 | } 81 | 82 | fun onTouchEvent(event: MotionEvent) = swipeBackDelegate.onTouchEvent(event) 83 | 84 | } 85 | 86 | abstract class BaseDetailSwipeFinishableActivity : AppCompatActivity(), SwipeFinishable.SwipeFinishableActivity { 87 | override fun onCreate(savedInstanceState: Bundle?) { 88 | super.onCreate(savedInstanceState) 89 | Themer.applyProperTheme(this, true) 90 | } 91 | 92 | override fun onOptionsItemSelected(item: MenuItem?): Boolean { 93 | when (item!!.itemId) { 94 | android.R.id.home 95 | -> { 96 | finish() 97 | return true 98 | } 99 | } 100 | return super.onOptionsItemSelected(item) 101 | } 102 | 103 | override fun finishThisActivity() { 104 | super.finish() 105 | } 106 | 107 | override fun finish() { 108 | SwipeFinishable.INSTANCE.finishCurrentActivity() 109 | } 110 | } -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/kotlin_github/mvp/base/CommonListAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.kotlin_github.mvp.base 2 | 3 | import android.animation.ObjectAnimator 4 | import android.support.annotation.LayoutRes 5 | import android.support.v4.view.ViewCompat 6 | import android.support.v7.widget.RecyclerView 7 | import android.support.v7.widget.RecyclerView.ViewHolder 8 | import android.view.LayoutInflater 9 | import android.view.MotionEvent 10 | import android.view.View 11 | import android.view.ViewGroup 12 | import com.devyk.common.utils.AdapterList 13 | import com.devyk.kotlin_github.R 14 | import kotlinx.android.synthetic.main.item_card.view.* 15 | import org.jetbrains.anko.dip 16 | import org.jetbrains.anko.sdk15.coroutines.onClick 17 | 18 | abstract class CommonListAdapter(@LayoutRes val itemResId: Int) : RecyclerView.Adapter() { 19 | companion object { 20 | private const val CARD_TAP_DURATION = 100L 21 | } 22 | 23 | init { 24 | setHasStableIds(true) 25 | } 26 | 27 | private var oldPosition = -1 28 | val data = AdapterList(this) 29 | 30 | override fun getItemId(position: Int): Long { 31 | return position.toLong() 32 | } 33 | 34 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 35 | val itemView = LayoutInflater.from(parent.context).inflate(R.layout.item_card, parent, false) 36 | LayoutInflater.from(itemView.context).inflate(itemResId, itemView.contentContainer) 37 | return CommonViewHolder(itemView) 38 | } 39 | 40 | override fun getItemCount(): Int { 41 | return data.size 42 | } 43 | 44 | override fun onBindViewHolder(holder: ViewHolder, position: Int) { 45 | onBindData(holder, data[position]) 46 | 47 | holder.itemView.setOnTouchListener { _, event -> 48 | when (event.action) { 49 | MotionEvent.ACTION_DOWN -> ViewCompat.animate(holder.itemView).scaleX(1.03f).scaleY(1.03f).translationZ(holder.itemView.dip(10).toFloat()).duration = CARD_TAP_DURATION 50 | MotionEvent.ACTION_UP, 51 | MotionEvent.ACTION_CANCEL -> { 52 | ViewCompat.animate(holder.itemView).scaleX(1f).scaleY(1f).translationZ(holder.itemView.dip(0).toFloat()).duration = CARD_TAP_DURATION 53 | } 54 | } 55 | false 56 | } 57 | 58 | holder.itemView.onClick { 59 | onItemClicked(holder.itemView, data[position]) 60 | } 61 | } 62 | 63 | override fun onViewAttachedToWindow(holder: ViewHolder) { 64 | if(holder is CommonViewHolder && holder.layoutPosition > oldPosition){ 65 | addItemAnimation(holder.itemView) 66 | oldPosition = holder.layoutPosition 67 | } 68 | } 69 | 70 | private fun addItemAnimation(itemView: View) { 71 | ObjectAnimator.ofFloat(itemView, "translationY", 500f, 0f).setDuration(500).start() 72 | } 73 | 74 | abstract fun onBindData(viewHolder: ViewHolder, item: T) 75 | 76 | abstract fun onItemClicked(itemView: View, item: T) 77 | 78 | class CommonViewHolder(itemView: View): ViewHolder(itemView) 79 | } -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/kotlin_github/mvp/base/CommonListPresenter.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.kotlin_github.mvp.base 2 | 3 | import BasePresenter 4 | import com.devyk.common.ext.otherwise 5 | import com.devyk.common.ext.yes 6 | import io.reactivex.disposables.Disposable 7 | import org.reactivestreams.Subscription 8 | 9 | /** 10 | *
11 |  *     author  : devyk on 2019-11-04 14:20
12 |  *     blog    : https://juejin.im/user/578259398ac2470061f3a3fb/posts
13 |  *     github  : https://github.com/yangkun19921001
14 |  *     mailbox : yang1001yk@gmail.com
15 |  *     desc    : This is CommonListPresenter
16 |  * 
17 | */ 18 | abstract class CommonListPresenter>> : 19 | BasePresenter() { 20 | abstract val listPage: ListPage 21 | 22 | private var firstInView = true 23 | private val subscriptionList = ArrayList() 24 | 25 | /** 26 | * 初始化数据 27 | */ 28 | fun initData() { 29 | listPage.loadFromFirst() 30 | .subscribe( 31 | { 32 | it.isEmpty().yes { 33 | v.onDataInitWithNothing() 34 | }.otherwise { 35 | v.onDataInit(it) 36 | } 37 | }, { 38 | v.onDataInitWithError(it.message ?: it.toString()) 39 | }).let(subscriptionList::add) 40 | } 41 | 42 | /** 43 | * 刷新数据 44 | */ 45 | fun refreshData() { 46 | listPage.loadFromFirst() 47 | .subscribe( 48 | { 49 | it.isEmpty().yes { 50 | v.onDataRefreshWithNothing() 51 | }.otherwise { 52 | v.onDataRefresh(it) 53 | } 54 | }, { 55 | v.onDataRefreshWithError(it.message ?: it.toString()) 56 | }).let(subscriptionList::add) 57 | 58 | } 59 | 60 | /** 61 | * 加载更多 62 | */ 63 | fun loadMore() { 64 | listPage.loadMore() 65 | .subscribe( 66 | { v.onMoreDataLoaded(it) }, 67 | { v.onMoreDataLoadedWithError(it.message ?: it.toString()) } 68 | ) 69 | 70 | } 71 | 72 | override fun onResume() { 73 | super.onResume() 74 | (!firstInView).yes { 75 | refreshData() 76 | } 77 | firstInView = false 78 | } 79 | 80 | override fun onDestroy() { 81 | super.onDestroy() 82 | subscriptionList.forEach { 83 | it.dispose() 84 | } 85 | subscriptionList.clear() 86 | 87 | } 88 | 89 | } -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/kotlin_github/mvp/base/CommonSinglePageFragment.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.kotlin_github.mvp.base 2 | 3 | import android.os.Bundle 4 | import android.support.v4.app.Fragment 5 | import android.view.View 6 | import com.devyk.kotlin_github.mvp.v.MainActivity 7 | 8 | /** 9 | *
10 |  *     author  : devyk on 2019-11-06 13:28
11 |  *     blog    : https://juejin.im/user/578259398ac2470061f3a3fb/posts
12 |  *     github  : https://github.com/yangkun19921001
13 |  *     mailbox : yang1001yk@gmail.com
14 |  *     desc    : This is CommonSinglePageFragment
15 |  * 
16 | */ 17 | 18 | abstract class CommonSinglePageFragment:Fragment(){ 19 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 20 | super.onViewCreated(view, savedInstanceState) 21 | (activity as MainActivity).actionBarController.setupWithViewPager(null) 22 | } 23 | } -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/kotlin_github/mvp/base/CommonViewPageAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.kotlin_github.mvp.base 2 | 3 | import android.support.v4.app.Fragment 4 | import android.support.v4.app.FragmentManager 5 | import android.support.v4.app.FragmentPagerAdapter 6 | import android.support.v4.view.PagerAdapter 7 | import android.view.View 8 | import com.devyk.common.utils.ViewPagerAdapterList 9 | 10 | class CommonViewPageAdapter(childFragmentManager: FragmentManager) : FragmentPagerAdapter(childFragmentManager) { 11 | 12 | val fragmentPages = ViewPagerAdapterList(this) 13 | 14 | override fun getItem(p0: Int): Fragment = fragmentPages[p0].fragment 15 | 16 | 17 | override fun getCount(): Int = 18 | fragmentPages.size 19 | 20 | override fun getItemPosition(`object`: Any): Int { 21 | for ((index, page) in fragmentPages.withIndex()) { 22 | if (`object` == page.fragment) 23 | return index 24 | 25 | } 26 | return PagerAdapter.POSITION_NONE 27 | } 28 | 29 | override fun getPageTitle(position: Int): CharSequence? { 30 | return fragmentPages[position].title 31 | } 32 | } -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/kotlin_github/mvp/base/CommonViewPagerFragment.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.kotlin_github.mvp.base 2 | 3 | import android.os.Bundle 4 | import android.support.v4.app.Fragment 5 | import android.support.v4.app.FragmentManager 6 | import android.support.v4.view.ViewPager 7 | import android.view.LayoutInflater 8 | import android.view.View 9 | import android.view.ViewGroup 10 | import com.bennyhuo.github.network.entities.User 11 | import com.devyk.common.config.UserInfo 12 | import com.devyk.common.ext.yes 13 | import com.devyk.kotlin_github.R 14 | import com.devyk.kotlin_github.mvp.m.AccountManager 15 | import com.devyk.kotlin_github.mvp.m.OnAccountStateChangeLister 16 | import com.devyk.kotlin_github.mvp.v.MainActivity 17 | import org.jetbrains.anko.support.v4.UI 18 | import org.jetbrains.anko.support.v4._ViewPager 19 | import org.jetbrains.anko.support.v4.viewPager 20 | import org.jetbrains.anko.verticalLayout 21 | 22 | /** 23 | *
 24 |  *     author  : devyk on 2019-11-01 17:55
 25 |  *     blog    : https://juejin.im/user/578259398ac2470061f3a3fb/posts
 26 |  *     github  : https://github.com/yangkun19921001
 27 |  *     mailbox : yang1001yk@gmail.com
 28 |  *     desc    : This is CommonViewPager
 29 |  * 
30 | */ 31 | abstract class CommonViewPagerFragment : Fragment(), OnAccountStateChangeLister, ViewPagerFragmentConfig { 32 | 33 | /** 34 | * lateinit 延迟初始化 ViewPager 35 | */ 36 | private lateinit var viewPager: ViewPager 37 | /** 38 | * 延迟加载 ViewPagerAdapter 39 | */ 40 | private val viewPageAdapter by lazy { 41 | CommonViewPageAdapter(childFragmentManager) 42 | } 43 | 44 | 45 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 46 | //使用 anko 创建动态布局,比 XML 快 4 倍 但是能使用 XML 的地方还是尽力使用 XML 布局 47 | return UI { 48 | verticalLayout { 49 | viewPager = viewPager { 50 | id = R.id.viewPager 51 | } 52 | viewPager.adapter = viewPageAdapter 53 | } 54 | }.view 55 | } 56 | 57 | 58 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 59 | super.onViewCreated(view, savedInstanceState) 60 | // tablayout set ViewPager 61 | (activity as MainActivity).actionBarController.setupWithViewPager(viewPager) 62 | //根据配置添加 主页面 63 | viewPageAdapter.fragmentPages.addAll( 64 | if (UserInfo.isLoginIn()) { 65 | //交于子类决定 66 | getFragmentPagesLoggedIn() 67 | } else { 68 | //交于子类决定 69 | getFragmentPagesNotLoggedIn() 70 | } 71 | ) 72 | } 73 | 74 | 75 | override fun onLogin(user: User) { 76 | viewPageAdapter.fragmentPages.clear() 77 | viewPageAdapter.fragmentPages.addAll(getFragmentPagesLoggedIn()) 78 | } 79 | 80 | override fun onLogOut() { 81 | viewPageAdapter.fragmentPages.clear() 82 | viewPageAdapter.fragmentPages.addAll(getFragmentPagesNotLoggedIn()) 83 | } 84 | 85 | 86 | override fun onCreate(savedInstanceState: Bundle?) { 87 | super.onCreate(savedInstanceState) 88 | //添加登录成功或失败的监听 89 | AccountManager.onAccountStateChangeLister.add(this) 90 | } 91 | 92 | override fun onDestroy() { 93 | super.onDestroy() 94 | AccountManager.onAccountStateChangeLister.remove(this) 95 | } 96 | } 97 | 98 | 99 | interface ViewPagerFragmentConfig { 100 | /** 101 | * 登录成功需要的模块 102 | */ 103 | fun getFragmentPagesLoggedIn(): List 104 | 105 | /** 106 | * 登录失败需要的模块 107 | */ 108 | fun getFragmentPagesNotLoggedIn(): List 109 | 110 | } 111 | 112 | /** 113 | * 封装模块 data 114 | */ 115 | data class FragmentPage(val fragment: Fragment, val title: String) -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/kotlin_github/mvp/base/DataProvider.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.kotlin_github.mvp.base 2 | 3 | import io.reactivex.Observable 4 | 5 | 6 | /** 7 | *
 8 |  *     author  : devyk on 2019-11-04 16:00
 9 |  *     blog    : https://juejin.im/user/578259398ac2470061f3a3fb/posts
10 |  *     github  : https://github.com/yangkun19921001
11 |  *     mailbox : yang1001yk@gmail.com
12 |  *     desc    : This is DataProvider
13 |  * 
14 | */ 15 | interface DataProvider { 16 | fun getData(page: Int): Observable> 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/kotlin_github/mvp/base/GitHubPaging.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.kotlin_github.mvp.base 2 | 3 | import com.bennyhuo.common.log.logger 4 | import okhttp3.HttpUrl 5 | 6 | /** 7 | *
 8 |  *     author  : devyk on 2019-11-04 14:58
 9 |  *     blog    : https://juejin.im/user/578259398ac2470061f3a3fb/posts
10 |  *     github  : https://github.com/yangkun19921001
11 |  *     mailbox : yang1001yk@gmail.com
12 |  *     desc    : This is GitHubPaging
13 |  * 
14 | */ 15 | 16 | class GitHubPaging : ArrayList() { 17 | companion object { 18 | const val URL_PATTERN = """(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]""" 19 | } 20 | 21 | private val relMap = HashMap().withDefault { 22 | null 23 | } 24 | 25 | private val first by relMap 26 | private val last by relMap 27 | private val next by relMap 28 | private val prev by relMap 29 | 30 | val isLast 31 | get() = last == null 32 | 33 | val hasNext 34 | get() = next != null 35 | 36 | val isFirst 37 | get() = first == null 38 | 39 | val hasFirst 40 | get() = first == null 41 | 42 | var since: Int = 0 43 | 44 | operator fun get(key: String): String? = relMap[key] 45 | 46 | fun setupLinks(link: String) { 47 | logger.warn("setupLinks:$link") 48 | 49 | Regex("""<($URL_PATTERN)>; rel="(\w+)"""").findAll(link).asIterable() 50 | .map { matchResult -> 51 | val url = matchResult.groupValues[1] 52 | relMap[matchResult.groupValues[3]] = url 53 | if (url.contains("since")) { 54 | HttpUrl.parse(url)?.queryParameter("since")?.let { 55 | since = it.toInt() 56 | } 57 | } 58 | logger.warn("${matchResult.groupValues[3]} => ${matchResult.groupValues[1]}") 59 | } 60 | } 61 | 62 | fun mergeData(paging: GitHubPaging): GitHubPaging { 63 | addAll(paging) 64 | relMap.clear() 65 | relMap.putAll(paging.relMap) 66 | since = paging.since 67 | return this 68 | } 69 | } -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/kotlin_github/mvp/base/ListPage.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.kotlin_github.mvp.base 2 | 3 | import com.bennyhuo.common.log.logger 4 | import io.reactivex.Observable 5 | import io.reactivex.android.schedulers.AndroidSchedulers 6 | import io.reactivex.schedulers.Schedulers 7 | 8 | /** 9 | *
10 |  *     author  : devyk on 2019-11-04 15:56
11 |  *     blog    : https://juejin.im/user/578259398ac2470061f3a3fb/posts
12 |  *     github  : https://github.com/yangkun19921001
13 |  *     mailbox : yang1001yk@gmail.com
14 |  *     desc    : This is ListPage
15 |  * 
16 | */ 17 | 18 | abstract class ListPage : DataProvider { 19 | 20 | //声明伴生对象 21 | companion object { 22 | const val PAGE_SIZE = 20 23 | } 24 | 25 | var currentPage = 1 26 | private set 27 | 28 | val data = GitHubPaging() 29 | 30 | fun loadMore() = getData(currentPage + 1) 31 | .doOnNext { 32 | currentPage + 1 33 | }.doOnError { 34 | logger.error("loadMore Error", it) 35 | }.map { 36 | data.mergeData(it) 37 | data 38 | } 39 | 40 | fun loadFromFirst(pageCount: Int = currentPage) = 41 | Observable.range(1, pageCount) 42 | //concatMap() 转发出来的事件是有序的,而 flatMap() 是无序的 43 | .concatMap { 44 | getData(it) 45 | }.doOnError { 46 | logger.error("loadFromFirst,pageCount=$pageCount", it) 47 | //与 scan() 操作符的作用也是将发送数据以一定逻辑聚合起来, 48 | // 这两个的区别在于 scan() 每处理一次数据就会将事件发送给观察者, 49 | // 而 reduce() 会将所有数据聚合在一起才会发送事件给观察者。 50 | }.reduce { acc, page -> 51 | acc.mergeData(page) 52 | }.doAfterSuccess { 53 | data.clear() 54 | data.mergeData(it) 55 | } 56 | 57 | 58 | } -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/kotlin_github/mvp/m/AccountManager.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.kotlin_github.mvp.m 2 | 3 | import com.bennyhuo.github.network.entities.User 4 | import com.devyk.common.config.UserInfo 5 | import com.devyk.common.ext.fromJson 6 | import com.devyk.common.ext.otherwise 7 | import com.devyk.common.ext.pref 8 | import com.devyk.common.ext.yes 9 | import com.devyk.kotlin_github.api.AuthService 10 | import com.devyk.kotlin_github.api.UserService 11 | import com.devyk.kotlin_github.mvp.m.entity.AuthorizationReq 12 | import com.devyk.kotlin_github.mvp.m.entity.AuthorizationRsp 13 | import com.google.gson.Gson 14 | import io.reactivex.Observable 15 | import io.reactivex.Scheduler 16 | import io.reactivex.android.schedulers.AndroidSchedulers 17 | import io.reactivex.schedulers.Schedulers 18 | import retrofit2.HttpException 19 | 20 | /** 21 | *
 22 |  *     author  : devyk on 2019-10-31 15:09
 23 |  *     blog    : https://juejin.im/user/578259398ac2470061f3a3fb/posts
 24 |  *     github  : https://github.com/yangkun19921001
 25 |  *     mailbox : yang1001yk@gmail.com
 26 |  *     desc    : This is AccountManager
 27 |  * 
28 | */ 29 | 30 | /** 31 | * 定义一个回调 32 | */ 33 | interface OnAccountStateChangeLister { 34 | fun onLogin(user: User) 35 | fun onLogOut() 36 | } 37 | 38 | 39 | /** 40 | * 直接把 AccountManager 以 object 定义成单例 41 | */ 42 | object AccountManager { 43 | 44 | 45 | private var userJson by pref(""); 46 | 47 | var currentUser: User? = null 48 | get() { 49 | if (field == null && userJson.isNotEmpty()) { 50 | field = Gson().fromJson(userJson) 51 | } 52 | return field 53 | } 54 | set(value) { 55 | if (value == null) { 56 | userJson = "" 57 | } else { 58 | userJson = Gson().toJson(value) 59 | } 60 | 61 | field = value 62 | } 63 | val onAccountStateChangeLister = ArrayList() 64 | 65 | /** 66 | * 通知退出 67 | */ 68 | private fun notifyLogout() { 69 | onAccountStateChangeLister.forEach { it.onLogOut() } 70 | } 71 | 72 | 73 | /** 74 | * 定义登录接口,V 层调用 75 | */ 76 | fun login() = 77 | AuthService.createAuthorization(AuthorizationReq()) 78 | .doOnNext { 79 | //请求之前判断,也可以进行进度显示 80 | it.token.isEmpty().yes { 81 | throw AccountException(it) 82 | } 83 | 84 | //出现错误会执行此处 85 | }.retryWhen { 86 | it.flatMap { 87 | if (it is AccountException) { 88 | AuthService.deleteAuthorization(it.authorizationRsp.id) 89 | } else { 90 | Observable.error(it) 91 | } 92 | } 93 | }.flatMap { 94 | UserInfo.token = it.token 95 | UserInfo.authID = it.id 96 | UserService.getAuthenticatedUser() 97 | } 98 | .map { 99 | currentUser = it 100 | notifyLogin(it as User) 101 | println("login->$it") 102 | } 103 | 104 | /** 105 | * 退出 github 106 | */ 107 | fun logout() = 108 | AuthService.deleteAuthorization(UserInfo.authID) 109 | .doOnNext { 110 | it.isSuccessful.yes { 111 | UserInfo.authID = -1 112 | UserInfo.token = "" 113 | currentUser = null 114 | notifyLogout() 115 | }.otherwise { 116 | throw HttpException(it) 117 | } 118 | } 119 | 120 | 121 | private fun notifyLogin(user: User) { 122 | onAccountStateChangeLister.forEach { 123 | it.onLogin(user) 124 | } 125 | } 126 | 127 | 128 | class AccountException(val authorizationRsp: AuthorizationRsp) : Exception("already logged in.") 129 | 130 | } -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/kotlin_github/mvp/m/IssueModel.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.kotlin_github.mvp.m 2 | 3 | import com.devyk.kotlin_github.api.IssueService 4 | import com.devyk.kotlin_github.mvp.base.GitHubPaging 5 | import com.devyk.kotlin_github.mvp.base.ListPage 6 | import com.devyk.kotlin_github.mvp.m.entity.Issue 7 | import io.reactivex.Observable 8 | 9 | /** 10 | *
11 |  *     author  : devyk on 2019-11-05 10:55
12 |  *     blog    : https://juejin.im/user/578259398ac2470061f3a3fb/posts
13 |  *     github  : https://github.com/yangkun19921001
14 |  *     mailbox : yang1001yk@gmail.com
15 |  *     desc    : This is IssueModel
16 |  * 
17 | */ 18 | class MyIssuePage : ListPage() { 19 | override fun getData(page: Int): Observable> = IssueService.listIssuesOfAuthenticatedUser(page = page) 20 | 21 | } -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/kotlin_github/mvp/m/PeopleModel.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.kotlin_github.mvp.m 2 | 3 | import com.bennyhuo.github.network.entities.User 4 | import com.devyk.kotlin_github.api.UserService 5 | import com.devyk.kotlin_github.mvp.base.GitHubPaging 6 | import com.devyk.kotlin_github.mvp.base.ListPage 7 | import io.reactivex.Observable 8 | 9 | /** 10 | *
11 |  *     author  : devyk on 2019-11-06 09:52
12 |  *     blog    : https://juejin.im/user/578259398ac2470061f3a3fb/posts
13 |  *     github  : https://github.com/yangkun19921001
14 |  *     mailbox : yang1001yk@gmail.com
15 |  *     desc    : This is PeopleModel
16 |  * 
17 | */ 18 | 19 | class PeoplePageParams(val type: String, val login: String?) 20 | 21 | class PeoplePage(val params: PeoplePageParams) : ListPage() { 22 | enum class Type { 23 | FOLLOWER, FOLLOWING, ALL 24 | } 25 | 26 | override fun getData(page: Int): Observable> = 27 | when (Type.valueOf(params.type)) { 28 | Type.FOLLOWER -> UserService.followers(params.login!!, page = page) 29 | Type.FOLLOWING -> UserService.following(params.login!!, page = page) 30 | Type.ALL -> UserService.allUsers(data.since) 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/kotlin_github/mvp/m/RepoModel.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.kotlin_github.mvp.m 2 | 3 | import com.bennyhuo.github.network.entities.User 4 | import com.devyk.common.ext.otherwise 5 | import com.devyk.common.ext.yes 6 | import com.devyk.common.utils.format 7 | import com.devyk.kotlin_github.api.RepositoryService 8 | import com.devyk.kotlin_github.mvp.base.GitHubPaging 9 | import com.devyk.kotlin_github.mvp.base.ListPage 10 | import com.devyk.kotlin_github.mvp.m.entity.Repository 11 | import io.reactivex.Observable 12 | import java.util.* 13 | 14 | /** 15 | *
16 |  *     author  : devyk on 2019-11-05 13:09
17 |  *     blog    : https://juejin.im/user/578259398ac2470061f3a3fb/posts
18 |  *     github  : https://github.com/yangkun19921001
19 |  *     mailbox : yang1001yk@gmail.com
20 |  *     desc    : This is RepoModel
21 |  * 
22 | */ 23 | class RepoListPage(val owner: User?) : ListPage() { 24 | override fun getData(page: Int): Observable> { 25 | return if (owner == null) { 26 | RepositoryService.allRepositories(page, "pushed:<" + Date().format("yyyy-MM-dd")) 27 | .map { 28 | GitHubPaging().also { git -> 29 | git.addAll(it.getElements()) 30 | } 31 | } 32 | } else { 33 | RepositoryService.listRepositoriesOfUser(owner.login, page) 34 | 35 | } 36 | } 37 | 38 | 39 | } 40 | 41 | 42 | -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/kotlin_github/mvp/m/entity/AccountEntities.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.kotlin_github.mvp.m.entity 2 | 3 | 4 | import com.devyk.common.anno.PoKo 5 | 6 | 7 | @PoKo 8 | data class AuthorizationReq(var scopes: List = Configs.Account.SCOPES, 9 | var note: String = Configs.Account.note, 10 | var note_url: String = Configs.Account.noteUrl, 11 | var client_secret: String = Configs.Account.clientSecret) 12 | 13 | @PoKo 14 | data class AuthorizationRsp(var id: Int, 15 | var url: String, 16 | var app: AppInfo, 17 | var token: String, 18 | var hashed_token: String, 19 | var token_last_eight: String, 20 | var note: String, 21 | var note_url: String, 22 | var created_at: String, 23 | var updated_at: String, 24 | var scopes: List) 25 | 26 | @PoKo 27 | data class AppInfo(var name: String, var url: String, var client_id: String) -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/kotlin_github/mvp/m/entity/Issue.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.kotlin_github.mvp.m.entity 2 | 3 | import android.os.Parcelable 4 | import com.bennyhuo.github.network.entities.BasicUser 5 | import com.devyk.common.anno.PoKo 6 | import kotlinx.android.parcel.Parcelize 7 | 8 | 9 | @PoKo 10 | @Parcelize 11 | data class Issue(var url: String, 12 | var repository_url: String, 13 | var labels_url: String, 14 | var comments_url: String, 15 | var events_url: String, 16 | var html_url: String, 17 | var id: Int, 18 | var number: Int, 19 | var title: String, 20 | var user: BasicUser, 21 | var state: String, 22 | var locked: Boolean, 23 | var comments: Int, 24 | var created_at: String, 25 | var updated_at: String, 26 | var author_association: String, 27 | var body: String, 28 | var body_html: String, 29 | var labels: List, 30 | var assignees: List, 31 | var repository: Repository): Parcelable { 32 | @PoKo 33 | @Parcelize 34 | data class Labels(var id: Int, 35 | var url: String, 36 | var name: String, 37 | var color: String, 38 | var default: Boolean): Parcelable 39 | } -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/kotlin_github/mvp/m/entity/PagingWrapper.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.kotlin_github.mvp.m.entity 2 | 3 | 4 | abstract class PagingWrapper { 5 | 6 | abstract fun getElements(): List 7 | 8 | 9 | } -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/kotlin_github/mvp/m/entity/Repository.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.kotlin_github.mvp.m.entity 2 | 3 | import android.os.Parcelable 4 | import com.bennyhuo.github.network.entities.BasicUser 5 | import com.devyk.common.anno.PoKo 6 | import kotlinx.android.parcel.Parcelize 7 | import java.security.Permissions 8 | 9 | @PoKo 10 | @Parcelize 11 | data class Repository(var id: Int, 12 | var name: String, 13 | var full_name: String, 14 | var owner: BasicUser, 15 | var `private`: Boolean, 16 | var html_url: String, 17 | var description: String?, 18 | var fork: Boolean, 19 | var url: String, 20 | var forks_url: String, 21 | var keys_url: String, 22 | var collaborators_url: String, 23 | var teams_url: String, 24 | var hooks_url: String, 25 | var issue_events_url: String, 26 | var events_url: String, 27 | var assignees_url: String, 28 | var branches_url: String, 29 | var tags_url: String, 30 | var blobs_url: String, 31 | var git_tags_url: String, 32 | var git_refs_url: String, 33 | var trees_url: String, 34 | var statuses_url: String, 35 | var languages_url: String, 36 | var stargazers_url: String, 37 | var contributors_url: String, 38 | var subscribers_url: String, 39 | var subscription_url: String, 40 | var commits_url: String, 41 | var git_commits_url: String, 42 | var comments_url: String, 43 | var issue_comment_url: String, 44 | var contents_url: String, 45 | var compare_url: String, 46 | var merges_url: String, 47 | var archive_url: String, 48 | var downloads_url: String, 49 | var issues_url: String, 50 | var pulls_url: String, 51 | var milestones_url: String, 52 | var notifications_url: String, 53 | var labels_url: String, 54 | var releases_url: String, 55 | var deployments_url: String, 56 | var created_at: String, 57 | var updated_at: String, 58 | var pushed_at: String, 59 | var git_url: String, 60 | var ssh_url: String, 61 | var clone_url: String, 62 | var svn_url: String, 63 | var homepage: String?, 64 | var size: Int, 65 | var stargazers_count: Int, 66 | var watchers_count: Int, 67 | var language: String?, 68 | var has_issues: Boolean, 69 | var has_projects: Boolean, 70 | var has_downloads: Boolean, 71 | var has_wiki: Boolean, 72 | var has_pages: Boolean, 73 | var forks_count: Int, 74 | var mirror_url: String?, 75 | var open_issues_count: Int, 76 | var forks: Int, 77 | var open_issues: Int, 78 | var watchers: Int, 79 | var subscribers_count: Int = 0, 80 | var default_branch: String, 81 | var permissions: Permissions?) : Parcelable -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/kotlin_github/mvp/m/entity/SearchRepositories.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.kotlin_github.mvp.m.entity 2 | 3 | import com.devyk.common.anno.PoKo 4 | import com.devyk.kotlin_github.mvp.base.GitHubPaging 5 | 6 | 7 | @PoKo 8 | data class SearchRepositories( 9 | var total_count: Int, 10 | var incomplete_results: Boolean, 11 | var items: List 12 | ) : PagingWrapper() { 13 | 14 | 15 | 16 | 17 | 18 | override fun getElements() = items 19 | 20 | 21 | } 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/kotlin_github/mvp/m/entity/SubscriptionBody.kt: -------------------------------------------------------------------------------- 1 | 2 | sealed class SubscriptionBody(val ignored: Boolean, val subscribed: Boolean) 3 | 4 | object IGNORED: SubscriptionBody(true, false) 5 | 6 | object WATCH: SubscriptionBody(false, true) 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/kotlin_github/mvp/m/entity/SubscriptionResponse.kt: -------------------------------------------------------------------------------- 1 | 2 | import com.devyk.common.anno.PoKo 3 | 4 | 5 | @PoKo 6 | data class SubscriptionResponse(var subscribed: Boolean, 7 | var ignored: Boolean, 8 | var reason: Any?, 9 | var created_at: String, 10 | var url: String, 11 | var repository_url: String) -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/kotlin_github/mvp/m/entity/User.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.github.network.entities 2 | 3 | import android.os.Parcelable 4 | import com.devyk.common.anno.PoKo 5 | import kotlinx.android.parcel.Parcelize 6 | 7 | 8 | @PoKo 9 | @Parcelize 10 | data class BasicUser( 11 | var login: String, 12 | var avatar_url: String, 13 | var html_url: String 14 | ): Parcelable 15 | 16 | 17 | @PoKo 18 | @Parcelize 19 | data class User(var login: String, 20 | var avatar_url: String, 21 | var html_url: String, 22 | var id: Int, 23 | var type: String, 24 | var site_admin: Boolean, 25 | var name: String?, 26 | var company: String?, 27 | var blog: String?, 28 | var location: String?, 29 | var email: String?, 30 | var bio: String?, 31 | var public_repos: Int, 32 | var public_gists: Int, 33 | var followers: Int, 34 | var following: Int, 35 | var created_at: String, 36 | var updated_at: String, 37 | var private_gists: Int, 38 | var total_private_repos: Int, 39 | var owned_private_repos: Int, 40 | var disk_usage: Int, 41 | var collaborators: Int): Parcelable -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/kotlin_github/mvp/p/LoginPresenter.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.kotlin_github.mvp.p 2 | 3 | import BasePresenter 4 | import com.devyk.common.config.UserInfo 5 | import com.devyk.kotlin_github.mvp.m.AccountManager 6 | import com.devyk.kotlin_github.mvp.v.login.LoginActivity 7 | 8 | /** 9 | *
10 |  *     author  : devyk on 2019-10-30 15:58
11 |  *     blog    : https://juejin.im/user/578259398ac2470061f3a3fb/posts
12 |  *     github  : https://github.com/yangkun19921001
13 |  *     mailbox : yang1001yk@gmail.com
14 |  *     desc    : This is LoginPresenter
15 |  * 
16 | */ 17 | class LoginPresenter : BasePresenter() { 18 | 19 | 20 | fun doLogin(name: String, passwd: String) { 21 | UserInfo.username = name 22 | UserInfo.password = passwd 23 | //开始登录 24 | v.onRequest() 25 | AccountManager.login() 26 | .subscribe( 27 | { 28 | v.onSuccess() 29 | }, 30 | { 31 | v.onError(it) 32 | } 33 | ) 34 | } 35 | 36 | fun checkUser(name: String, pwd:String):Boolean{ 37 | return true 38 | } 39 | 40 | 41 | 42 | //进行用户信息重绘 43 | override fun onResume() { 44 | super.onResume() 45 | v.onDataInit(UserInfo.username,UserInfo.password) 46 | 47 | } 48 | } -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/kotlin_github/mvp/p/MainPersenter.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.kotlin_github.mvp.p 2 | 3 | import BasePresenter 4 | import com.bennyhuo.common.log.logger 5 | import com.devyk.kotlin_github.mvp.m.AccountManager 6 | import com.devyk.kotlin_github.mvp.v.MainActivity 7 | import org.jetbrains.anko.toast 8 | 9 | /** 10 | *
11 |  *     author  : devyk on 2019-11-01 11:10
12 |  *     blog    : https://juejin.im/user/578259398ac2470061f3a3fb/posts
13 |  *     github  : https://github.com/yangkun19921001
14 |  *     mailbox : yang1001yk@gmail.com
15 |  *     desc    : This is MainPersenter
16 |  * 
17 | */ 18 | class MainPersenter : BasePresenter() { 19 | fun logout() { 20 | AccountManager.logout() 21 | .subscribe({ 22 | v.onSuccess() 23 | }, { 24 | v.onError(it) 25 | }) 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/kotlin_github/mvp/p/MyIssuePresenter.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.kotlin_github.mvp.p 2 | 3 | import com.devyk.kotlin_github.mvp.base.CommonListPresenter 4 | import com.devyk.kotlin_github.mvp.base.ListPage 5 | import com.devyk.kotlin_github.mvp.m.MyIssuePage 6 | import com.devyk.kotlin_github.mvp.m.entity.Issue 7 | import com.devyk.kotlin_github.mvp.v.issue.MyIssueListFragment 8 | 9 | /** 10 | *
11 |  *     author  : devyk on 2019-11-05 10:53
12 |  *     blog    : https://juejin.im/user/578259398ac2470061f3a3fb/posts
13 |  *     github  : https://github.com/yangkun19921001
14 |  *     mailbox : yang1001yk@gmail.com
15 |  *     desc    : This is MyIssuePresenter
16 |  * 
17 | */ 18 | class MyIssuePresenter() : CommonListPresenter(){ 19 | override val listPage: ListPage = MyIssuePage() 20 | } -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/kotlin_github/mvp/p/PeopleListPresenter.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.kotlin_github.mvp.p 2 | 3 | import com.bennyhuo.github.network.entities.User 4 | import com.devyk.kotlin_github.mvp.base.CommonListPresenter 5 | import com.devyk.kotlin_github.mvp.base.ListPage 6 | import com.devyk.kotlin_github.mvp.m.PeoplePage 7 | import com.devyk.kotlin_github.mvp.m.PeoplePageParams 8 | import com.devyk.kotlin_github.mvp.v.people.PeopleListFragment 9 | 10 | /** 11 | *
12 |  *     author  : devyk on 2019-11-06 09:58
13 |  *     blog    : https://juejin.im/user/578259398ac2470061f3a3fb/posts
14 |  *     github  : https://github.com/yangkun19921001
15 |  *     mailbox : yang1001yk@gmail.com
16 |  *     desc    : This is PeopleListPresenter
17 |  * 
18 | */ 19 | class PeopleListPresenter : CommonListPresenter() { 20 | override val listPage: ListPage by lazy { 21 | PeoplePage(PeoplePageParams(v.type,v.login)) 22 | } 23 | } -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/kotlin_github/mvp/p/RepoListPresenter.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.kotlin_github.mvp.p 2 | 3 | import com.devyk.kotlin_github.mvp.base.CommonListPresenter 4 | import com.devyk.kotlin_github.mvp.base.ListPage 5 | import com.devyk.kotlin_github.mvp.m.RepoListPage 6 | import com.devyk.kotlin_github.mvp.m.entity.Repository 7 | import com.devyk.kotlin_github.mvp.v.repo.RepoListFragment 8 | 9 | /** 10 | *
11 |  *     author  : devyk on 2019-11-05 13:06
12 |  *     blog    : https://juejin.im/user/578259398ac2470061f3a3fb/posts
13 |  *     github  : https://github.com/yangkun19921001
14 |  *     mailbox : yang1001yk@gmail.com
15 |  *     desc    : This is RepoListPresenter
16 |  * 
17 | */ 18 | class RepoListPresenter : CommonListPresenter(){ 19 | override val listPage: ListPage by lazy { 20 | RepoListPage(v.user) 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/kotlin_github/mvp/v/AboutFragment.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.kotlin_github.mvp.v 2 | 3 | import android.os.Bundle 4 | import android.support.v4.app.Fragment 5 | import android.view.Gravity 6 | import android.view.LayoutInflater 7 | import android.view.View 8 | import android.view.ViewGroup 9 | import com.devyk.common.ext.markdownText 10 | import com.devyk.kotlin_github.R 11 | import com.devyk.kotlin_github.mvp.base.CommonSinglePageFragment 12 | import kotlinx.android.synthetic.main.activity_repo_details.view.* 13 | import org.jetbrains.anko.* 14 | import org.jetbrains.anko.support.v4.UI 15 | import org.jetbrains.anko.support.v4.nestedScrollView 16 | import kotlin.reflect.jvm.internal.impl.builtins.jvm.JvmBuiltInClassDescriptorFactory 17 | 18 | 19 | /** 20 | *
21 |  *     author  : devyk on 2019-11-01 11:32
22 |  *     blog    : https://juejin.im/user/578259398ac2470061f3a3fb/posts
23 |  *     github  : https://github.com/yangkun19921001
24 |  *     mailbox : yang1001yk@gmail.com
25 |  *     desc    : This is AboutFragment
26 |  * 
27 | */ 28 | class AboutFragment : CommonSinglePageFragment() { 29 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 30 | return AboutFragmentUIManager().createView(AnkoContext.Companion.create(context!!, this)) 31 | } 32 | } 33 | 34 | class AboutFragmentUIManager : AnkoComponent { 35 | override fun createView(ui: AnkoContext) = ui.apply { 36 | nestedScrollView { 37 | verticalLayout { 38 | imageView { 39 | imageResource = R.mipmap.github_about 40 | }.lparams(width = wrapContent, height = wrapContent) { 41 | gravity = Gravity.CENTER_HORIZONTAL 42 | } 43 | 44 | themedTextView("Kotlin_GitHub", R.style.detail_title).lparams(){ 45 | gravity = Gravity.CENTER_HORIZONTAL 46 | } 47 | 48 | themedTextView().apply { 49 | markdownText = "**GitHub:** https://github.com/yangkun19921001" 50 | padding = dip(5) 51 | } 52 | 53 | themedTextView().apply { 54 | markdownText = "**掘金博客:** https://juejin.im/user/578259398ac2470061f3a3fb" 55 | padding = dip(5) 56 | } 57 | 58 | themedTextView().apply { 59 | markdownText = "**项目地址:** https://juejin.im/user/578259398ac2470061f3a3fb" 60 | padding = dip(5) 61 | } 62 | }.lparams() { 63 | gravity = Gravity.CENTER 64 | padding = dip(20) 65 | } 66 | 67 | } 68 | }.view 69 | 70 | 71 | } 72 | -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/kotlin_github/mvp/v/issue/MyIssueFragment.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.kotlin_github.mvp.v.issue 2 | 3 | import com.devyk.kotlin_github.mvp.base.CommonViewPagerFragment 4 | import com.devyk.kotlin_github.mvp.base.FragmentPage 5 | 6 | 7 | /** 8 | *
 9 |  *     author  : devyk on 2019-11-01 11:31
10 |  *     blog    : https://juejin.im/user/578259398ac2470061f3a3fb/posts
11 |  *     github  : https://github.com/yangkun19921001
12 |  *     mailbox : yang1001yk@gmail.com
13 |  *     desc    : This is MyIssueFragment
14 |  * 
15 | */ 16 | class MyIssueFragment : CommonViewPagerFragment() { 17 | override fun getFragmentPagesLoggedIn(): List { 18 | return listOf(FragmentPage(MyIssueListFragment(),"My")) 19 | 20 | } 21 | 22 | 23 | 24 | override fun getFragmentPagesNotLoggedIn(): List { 25 | return listOf( 26 | FragmentPage(MyIssueListFragment(),"My") 27 | ) 28 | } 29 | 30 | 31 | } -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/kotlin_github/mvp/v/issue/MyIssueListFragment.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.kotlin_github.mvp.v.issue 2 | 3 | import android.support.v4.app.Fragment 4 | import com.devyk.kotlin_github.adapter.IssueListAdapter 5 | import com.devyk.kotlin_github.mvp.base.CommonListAdapter 6 | import com.devyk.kotlin_github.mvp.base.CommonListFragment 7 | import com.devyk.kotlin_github.mvp.m.entity.Issue 8 | import com.devyk.kotlin_github.mvp.p.MyIssuePresenter 9 | 10 | class MyIssueListFragment : CommonListFragment() { 11 | override val adapter = IssueListAdapter() 12 | 13 | } -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/kotlin_github/mvp/v/login/LoginActivity.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.kotlin_github.mvp.v.login 2 | 3 | import BaseActivity 4 | import android.animation.Animator 5 | import android.animation.AnimatorListenerAdapter 6 | import android.os.Bundle 7 | import android.view.View 8 | import android.widget.EditText 9 | import com.devyk.common.config.UserInfo 10 | import com.devyk.common.ext.otherwise 11 | import com.devyk.common.ext.yes 12 | import com.devyk.kotlin_github.R 13 | import com.devyk.kotlin_github.config.Themer 14 | import com.devyk.kotlin_github.mvp.p.LoginPresenter 15 | import com.devyk.kotlin_github.mvp.v.MainActivity 16 | import kotlinx.android.synthetic.main.activity_login.* 17 | import kotlinx.android.synthetic.main.app_bar_simple.* 18 | import org.jetbrains.anko.sdk15.coroutines.onClick 19 | import org.jetbrains.anko.startActivity 20 | import org.jetbrains.anko.toast 21 | 22 | class LoginActivity : BaseActivity() { 23 | 24 | 25 | override fun onCreate(savedInstanceState: Bundle?) { 26 | super.onCreate(savedInstanceState) 27 | UserInfo.isLoginIn().yes { 28 | /** 29 | * 利用 anko Context 扩展,启动 startActivity 30 | */ 31 | startActivity() 32 | finish() 33 | } 34 | Themer.applyProperTheme(this); 35 | setContentView(R.layout.activity_login) 36 | //绑定 Toolbar 37 | setSupportActionBar(toolbar) 38 | 39 | //调用 anko 扩展 40 | signInButton.onClick { 41 | onLogin() 42 | } 43 | } 44 | 45 | fun onLogin() { 46 | p.checkUser(username.text.toString().trim(), password.text.toString().trim()) 47 | .yes { 48 | hideSoftInput() 49 | p.doLogin(username.text.toString().trim(), password.text.toString().trim()) 50 | }.otherwise { 51 | showTips(password, "用户名或者密码不合法") 52 | } 53 | } 54 | 55 | 56 | private fun showTips(view: EditText, tips: String) { 57 | view.requestFocus() 58 | view.error = tips 59 | } 60 | 61 | override fun onRequest() { 62 | toast("开始登录") 63 | showProgress(true) 64 | 65 | } 66 | 67 | override fun onSuccess() { 68 | toast("登录成功") 69 | showProgress(false) 70 | /** 71 | * 利用 anko Context 扩展,启动 startActivity 72 | */ 73 | startActivity() 74 | finish() 75 | } 76 | 77 | override fun onError(error: Throwable) { 78 | toast("登录失败:${error.message}") 79 | showProgress(false) 80 | } 81 | 82 | private fun showProgress(show: Boolean) { 83 | val shortAnimTime = resources.getInteger(android.R.integer.config_shortAnimTime) 84 | loginForm.animate().setDuration(shortAnimTime.toLong()).alpha( 85 | (if (show) 0 else 1).toFloat() 86 | ).setListener(object : AnimatorListenerAdapter() { 87 | override fun onAnimationEnd(animation: Animator) { 88 | loginForm.visibility = if (show) View.GONE else View.VISIBLE 89 | } 90 | }) 91 | 92 | loginProgress.animate().setDuration(shortAnimTime.toLong()).alpha( 93 | (if (show) 1 else 0).toFloat() 94 | ).setListener(object : AnimatorListenerAdapter() { 95 | override fun onAnimationEnd(animation: Animator) { 96 | loginProgress.visibility = if (show) View.VISIBLE else View.GONE 97 | } 98 | }) 99 | } 100 | 101 | fun onDataInit(name: String, pwd: String) { 102 | username.setText(name) 103 | password.setText(pwd) 104 | } 105 | 106 | 107 | 108 | 109 | 110 | } 111 | -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/kotlin_github/mvp/v/people/PeopleFragment.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.kotlin_github.mvp.v.people 2 | 3 | import android.os.Bundle 4 | import com.devyk.kotlin_github.mvp.base.CommonViewPagerFragment 5 | import com.devyk.kotlin_github.mvp.base.FragmentPage 6 | import com.devyk.kotlin_github.mvp.m.AccountManager 7 | import com.devyk.kotlin_github.mvp.m.PeoplePage 8 | 9 | /** 10 | *
11 |  *     author  : devyk on 2019-11-01 11:31
12 |  *     blog    : https://juejin.im/user/578259398ac2470061f3a3fb/posts
13 |  *     github  : https://github.com/yangkun19921001
14 |  *     mailbox : yang1001yk@gmail.com
15 |  *     desc    : This is PeopleFragment
16 |  * 
17 | */ 18 | class PeopleFragment : CommonViewPagerFragment() { 19 | 20 | 21 | /** 22 | * 当登录成功的时候加载 Followers、Following、ALL 3 个页面 23 | */ 24 | override fun getFragmentPagesNotLoggedIn(): List { 25 | return listOf( 26 | FragmentPage(PeopleListFragment().also { 27 | it.arguments = Bundle().apply { 28 | putString("type", PeoplePage.Type.ALL.name) 29 | 30 | } 31 | }, "All") 32 | ) 33 | } 34 | 35 | /** 36 | * 登录失败的时候加载 PeopleListFragment ALL 页面 37 | */ 38 | override fun getFragmentPagesLoggedIn(): List { 39 | //alse 会返回自身引用 40 | return listOf( 41 | FragmentPage(PeopleListFragment().also { 42 | it.arguments = Bundle().apply { 43 | putString("login", AccountManager.currentUser?.login) 44 | putString("type", PeoplePage.Type.FOLLOWER.name) 45 | } 46 | }, "Followers"), 47 | FragmentPage(PeopleListFragment().also { 48 | it.arguments = Bundle().apply { 49 | putString("login", AccountManager.currentUser?.login) 50 | putString("type", PeoplePage.Type.FOLLOWING.name) 51 | } 52 | }, "Following"), 53 | FragmentPage(PeopleListFragment().also { 54 | it.arguments = Bundle().apply { 55 | putString("login", AccountManager.currentUser?.login) 56 | putString("type", PeoplePage.Type.ALL.name) 57 | } 58 | }, "All") 59 | ) 60 | } 61 | } -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/kotlin_github/mvp/v/people/PeopleListFragment.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.kotlin_github.mvp.v.people 2 | 3 | import android.os.Bundle 4 | import android.support.v4.app.Fragment 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import com.bennyhuo.github.network.entities.User 9 | import com.devyk.common.ext.bindArgument 10 | import com.devyk.kotlin_github.R 11 | import com.devyk.kotlin_github.adapter.PeopleListAdapter 12 | import com.devyk.kotlin_github.mvp.base.CommonListAdapter 13 | import com.devyk.kotlin_github.mvp.base.CommonListFragment 14 | import com.devyk.kotlin_github.mvp.p.PeopleListPresenter 15 | import org.jetbrains.anko.support.v4.UI 16 | import org.jetbrains.anko.support.v4.viewPager 17 | import org.jetbrains.anko.textView 18 | import org.jetbrains.anko.verticalLayout 19 | 20 | /** 21 | *
22 |  *     author  : devyk on 2019-11-04 11:07
23 |  *     blog    : https://juejin.im/user/578259398ac2470061f3a3fb/posts
24 |  *     github  : https://github.com/yangkun19921001
25 |  *     mailbox : yang1001yk@gmail.com
26 |  *     desc    : This is PeopleListFragment
27 |  * 
28 | */ 29 | class PeopleListFragment : CommonListFragment() { 30 | 31 | 32 | //添加适配器 33 | override val adapter = PeopleListAdapter() 34 | 35 | //拿到登录信息 36 | val login: String by bindArgument("login") 37 | 38 | //拿到类型 39 | val type: String by bindArgument("type") 40 | 41 | 42 | } -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/kotlin_github/mvp/v/repo/RepoFragment.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.kotlin_github.mvp.v.repo 2 | 3 | import android.os.Bundle 4 | import android.support.v4.app.Fragment 5 | import com.devyk.kotlin_github.mvp.base.CommonViewPagerFragment 6 | import com.devyk.kotlin_github.mvp.base.FragmentPage 7 | import com.devyk.kotlin_github.mvp.m.AccountManager 8 | 9 | 10 | /** 11 | *
12 |  *     author  : devyk on 2019-11-01 11:30
13 |  *     blog    : https://juejin.im/user/578259398ac2470061f3a3fb/posts
14 |  *     github  : https://github.com/yangkun19921001
15 |  *     mailbox : yang1001yk@gmail.com
16 |  *     desc    : This is RepoFragment
17 |  * 
18 | */ 19 | class RepoFragment : CommonViewPagerFragment() { 20 | override fun getFragmentPagesLoggedIn(): List = 21 | listOf( 22 | FragmentPage(RepoListFragment().apply { 23 | arguments = Bundle().apply { putParcelable("user", AccountManager.currentUser) } 24 | }, "My"), 25 | FragmentPage(RepoListFragment(), "All") 26 | ) 27 | 28 | override fun getFragmentPagesNotLoggedIn(): List = listOf(FragmentPage(RepoListFragment(), "All")) 29 | 30 | 31 | } -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/kotlin_github/mvp/v/repo/RepoListFragment.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.kotlin_github.mvp.v.repo 2 | 3 | import com.bennyhuo.github.network.entities.User 4 | import com.devyk.common.ext.bindArgument 5 | import com.devyk.kotlin_github.adapter.RepoListAdapter 6 | import com.devyk.kotlin_github.mvp.base.CommonListFragment 7 | import com.devyk.kotlin_github.mvp.m.entity.Repository 8 | import com.devyk.kotlin_github.mvp.p.RepoListPresenter 9 | 10 | /** 11 | *
12 |  *     author  : devyk on 2019-11-05 13:05
13 |  *     blog    : https://juejin.im/user/578259398ac2470061f3a3fb/posts
14 |  *     github  : https://github.com/yangkun19921001
15 |  *     mailbox : yang1001yk@gmail.com
16 |  *     desc    : This is RepoListFragment
17 |  * 
18 | */ 19 | class RepoListFragment : CommonListFragment() { 20 | 21 | val user: User by bindArgument("user") 22 | 23 | override val adapter = RepoListAdapter() 24 | 25 | 26 | } -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/kotlin_github/widget/ActionBarController.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.github.view.widget 2 | 3 | import android.database.DataSetObserver 4 | import android.support.design.widget.TabLayout 5 | import android.support.v4.view.ViewPager 6 | import android.view.View 7 | import com.devyk.common.utils.Weak 8 | import com.devyk.kotlin_github.mvp.v.MainActivity 9 | import kotlinx.android.synthetic.main.app_bar_main.* 10 | 11 | class ActionBarController(val mainActivity: MainActivity) { 12 | 13 | private val context by Weak { 14 | mainActivity 15 | } 16 | 17 | private val tab by lazy { 18 | context!!.tabLayout 19 | } 20 | 21 | class ViewPagerDataSetObserver(val tabLayout: TabLayout) : DataSetObserver() { 22 | 23 | var viewPager: ViewPager? = null 24 | set(value) { 25 | viewPager?.adapter?.unregisterDataSetObserver(this) 26 | value?.adapter?.registerDataSetObserver(this) 27 | field = value 28 | } 29 | 30 | override fun onChanged() { 31 | super.onChanged() 32 | viewPager?.let { viewPager -> 33 | if (viewPager.adapter?.count ?: 0 <= 1) { 34 | tabLayout.visibility = View.GONE 35 | } else { 36 | tabLayout.visibility = View.VISIBLE 37 | tabLayout.tabMode = if (viewPager.adapter?.count ?: 0 > 3) TabLayout.MODE_SCROLLABLE else TabLayout.MODE_FIXED 38 | } 39 | } 40 | } 41 | } 42 | 43 | private val dataSetObserver by lazy { 44 | ViewPagerDataSetObserver(tab) 45 | } 46 | 47 | fun setupWithViewPager(viewPager: ViewPager?) { 48 | viewPager?.let(dataSetObserver::viewPager::set)?: run{ tab.visibility = View.GONE } 49 | tab.setupWithViewPager(viewPager) 50 | } 51 | } -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/kotlin_github/widget/DetailItemView.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.kotlin_github.widget 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.view.View 6 | import android.widget.RelativeLayout 7 | import com.devyk.common.ext.delegateOf 8 | import com.devyk.common.ext.subscribeIgnoreError 9 | import com.devyk.kotlin_github.R 10 | import io.reactivex.Observable 11 | import kotlinx.android.synthetic.main.detail_item.view.* 12 | import org.jetbrains.anko.sdk15.coroutines.onClick 13 | import kotlin.reflect.KProperty 14 | 15 | typealias CheckEvent = (Boolean) -> Observable 16 | 17 | class ObjectPropertyDelegate(val receiver: R, val getter: ((R) -> T)? = null, val setter: ((R, T) -> Unit)? = null, defaultValue: T? = null) { 18 | private var value: T? = defaultValue 19 | 20 | operator fun getValue(ref: Any, property: KProperty<*>): T { 21 | return getter?.invoke(receiver) ?: value!! 22 | } 23 | 24 | operator fun setValue(ref: Any, property: KProperty<*>, value: T) { 25 | setter?.invoke(receiver, value) 26 | this.value = value 27 | } 28 | 29 | } 30 | 31 | class DetailItemView 32 | @JvmOverloads 33 | constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) 34 | : RelativeLayout(context, attrs, defStyleAttr) { 35 | 36 | init { 37 | View.inflate(context, R.layout.detail_item, this) 38 | } 39 | 40 | var title by delegateOf(titleView::getText, titleView::setText) 41 | 42 | var content by delegateOf(contentView::getText, contentView::setText, "") 43 | 44 | var icon by delegateOf(iconView::setImageResource, 0) 45 | 46 | var operatorIcon by delegateOf(operatorIconView::setBackgroundResource, 0) 47 | 48 | var isChecked by delegateOf(operatorIconView::isChecked, operatorIconView::setChecked) 49 | 50 | var checkEvent: CheckEvent? = null 51 | 52 | init { 53 | attrs?.let { 54 | val a = context.obtainStyledAttributes(it, R.styleable.DetailItemView) 55 | title = a.getString(R.styleable.DetailItemView_item_title) ?: "" 56 | content = a.getString(R.styleable.DetailItemView_item_content) ?: "" 57 | icon = a.getResourceId(R.styleable.DetailItemView_item_icon, 0) 58 | operatorIcon = a.getResourceId(R.styleable.DetailItemView_item_op_icon, 0) 59 | a.recycle() 60 | } 61 | 62 | onClick { 63 | checkEvent?.invoke(isChecked) 64 | ?.subscribeIgnoreError { 65 | isChecked = it 66 | } 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/kotlin_github/widget/NavigationController.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.kotlin_github.widget 2 | 3 | import android.support.design.widget.NavigationView 4 | import android.view.MenuItem 5 | import com.bennyhuo.common.log.logger 6 | import com.bennyhuo.github.network.entities.User 7 | import com.devyk.common.ext.loadWithGlide 8 | import com.devyk.kotlin_github.R 9 | import com.devyk.kotlin_github.config.NavViewItem 10 | import com.devyk.kotlin_github.config.doOnLayoutAvailable 11 | import com.devyk.kotlin_github.config.selectItem 12 | import com.devyk.kotlin_github.mvp.m.AccountManager 13 | import kotlinx.android.synthetic.main.nav_header_main.view.* 14 | import org.jetbrains.anko.imageResource 15 | import org.jetbrains.anko.sdk15.coroutines.onClick 16 | 17 | 18 | class NavigationController( 19 | private val navigationView: NavigationView, 20 | private val onNavItemChanged: (NavViewItem) -> Unit, 21 | private val onHeaderClick: () -> Unit 22 | ) : NavigationView.OnNavigationItemSelectedListener { 23 | 24 | init { 25 | navigationView.setNavigationItemSelectedListener(this) 26 | } 27 | 28 | private var currentItem: NavViewItem? = null 29 | 30 | override fun onNavigationItemSelected(item: MenuItem): Boolean { 31 | navigationView.apply { 32 | Settings.lastPage = item.itemId 33 | val navItem = NavViewItem[item.itemId] 34 | onNavItemChanged(navItem) 35 | } 36 | return true 37 | } 38 | 39 | fun useLoginLayout() { 40 | navigationView.menu.clear() 41 | navigationView.inflateMenu(R.menu.activity_main_drawer) //inflate new items. 42 | onUpdate(AccountManager.currentUser) 43 | selectProperItem() 44 | } 45 | 46 | fun useNoLoginLayout() { 47 | navigationView.menu.clear() 48 | navigationView.inflateMenu(R.menu.activity_main_drawer_no_logged_in) //inflate new items. 49 | onUpdate(AccountManager.currentUser) 50 | selectProperItem() 51 | } 52 | 53 | private fun onUpdate(user: User?) { 54 | navigationView.doOnLayoutAvailable { 55 | navigationView.apply { 56 | usernameView.text = user?.login ?: "请登录" 57 | emailView.text = user?.email ?: "yang1001yk@mail.com" 58 | if (user == null) { 59 | avatarView.imageResource = R.drawable.ic_github 60 | } else { 61 | avatarView.loadWithGlide(user.avatar_url, user.login.first()) 62 | } 63 | 64 | navigationHeader.onClick { onHeaderClick() } 65 | } 66 | } 67 | } 68 | 69 | fun selectProperItem() { 70 | logger.debug("selectProperItem") 71 | navigationView.doOnLayoutAvailable { 72 | logger.debug("selectProperItem onLayout: $currentItem") 73 | ((currentItem?.let { NavViewItem[it] } ?: Settings.lastPage) 74 | .takeIf { navigationView.menu.findItem(it) != null } ?: run { Settings.defaultPage }) 75 | .let(navigationView::selectItem) 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_about_us.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_day.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_fork.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_github.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_go.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_issue.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_issue_closed.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_issue_open.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_night.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_people.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_repository.xml: -------------------------------------------------------------------------------- 1 | 3 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_round.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_speech_bubble.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_star.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_star_o.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_unwatch.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_watch.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/sel_daynight.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/sel_star.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/sel_watch.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/side_nav_bar.xml: -------------------------------------------------------------------------------- 1 | 3 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/side_nav_bar_night.xml: -------------------------------------------------------------------------------- 1 | 3 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 15 | 16 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/layout/app_bar_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 17 | 18 | 19 | 20 | 26 | 27 | 28 | 29 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/res/layout/app_bar_simple.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/layout/detail_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | 13 | 18 | 19 | 26 | 27 | 34 | 35 | 42 | 43 | 44 | 45 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_common_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_card.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_issue.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 14 | 15 | 21 | 22 | 28 | 29 | 34 | 35 | 41 | 42 | 43 | 44 | 45 | 51 | 52 | 60 | 61 | 62 | 68 | 69 | 77 | 78 | 79 | 80 | 81 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_repo.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 14 | 15 | 21 | 22 | 26 | 27 | 31 | 32 | 37 | 38 | 39 | 40 | 45 | 46 | 49 | 50 | 54 | 55 | 58 | 59 | 63 | 64 | 67 | 68 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_user.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 15 | 16 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/layout/menu_item_daynight.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/layout/nav_header_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 16 | 21 | 22 | 29 | 30 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /app/src/main/res/menu/activity_actionbar.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/menu/activity_main_drawer.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 13 | 17 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/menu/activity_main_drawer_no_logged_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 13 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangkun19921001/Kotlin_GitHub/989761f90db567a817b7135b2da158cb98440e5a/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangkun19921001/Kotlin_GitHub/989761f90db567a817b7135b2da158cb98440e5a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangkun19921001/Kotlin_GitHub/989761f90db567a817b7135b2da158cb98440e5a/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangkun19921001/Kotlin_GitHub/989761f90db567a817b7135b2da158cb98440e5a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangkun19921001/Kotlin_GitHub/989761f90db567a817b7135b2da158cb98440e5a/app/src/main/res/mipmap-xhdpi/github.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/github_about.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangkun19921001/Kotlin_GitHub/989761f90db567a817b7135b2da158cb98440e5a/app/src/main/res/mipmap-xhdpi/github_about.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangkun19921001/Kotlin_GitHub/989761f90db567a817b7135b2da158cb98440e5a/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangkun19921001/Kotlin_GitHub/989761f90db567a817b7135b2da158cb98440e5a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangkun19921001/Kotlin_GitHub/989761f90db567a817b7135b2da158cb98440e5a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangkun19921001/Kotlin_GitHub/989761f90db567a817b7135b2da158cb98440e5a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangkun19921001/Kotlin_GitHub/989761f90db567a817b7135b2da158cb98440e5a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangkun19921001/Kotlin_GitHub/989761f90db567a817b7135b2da158cb98440e5a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | @dimen/dp_16 4 | @dimen/dp_16 5 | @dimen/dp_16 6 | @dimen/dp_160 7 | @dimen/dp_16 8 | 9 | @dimen/dp_90 10 | @dimen/dp_50 11 | @dimen/dp_40 12 | @dimen/dp_30 13 | @dimen/dp_20 14 | 15 | @dimen/dp_10 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Kotlin_GitHub 3 | 4 | 请输入你的 GitHub 账号 5 | 请输入你的 GitHub 密码 6 | Sign in or register 7 | 登录 8 | This user address is invalid 9 | This password is too short 10 | This password is incorrect 11 | This field is required 12 | "Contacts permissions are needed for providing email 13 | completions." 14 | 15 | 16 | Open navigation drawer 17 | Close navigation drawer 18 | 19 | Open Source Licenses 20 | Click to Login 21 | 22 | [%1$s](%2$s)/[%3$s](%4$s) \n\n Created by [%5$s](%6$s) on %7$s 23 | GitHub 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/com/devyk/kotlin_github/BooleanExpTest.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.kotlin_github 2 | 3 | import com.devyk.common.ext.BooleanExp 4 | import com.devyk.common.ext.yes 5 | import org.junit.Test 6 | 7 | /** 8 | *
 9 |  *     author  : devyk on 2019-10-29 16:11
10 |  *     blog    : https://juejin.im/user/578259398ac2470061f3a3fb/posts
11 |  *     github  : https://github.com/yangkun19921001
12 |  *     mailbox : yang1001yk@gmail.com
13 |  *     desc    : This is BooleanExpTest
14 |  * 
15 | */ 16 | class BooleanExpTest { 17 | 18 | 19 | @Test 20 | fun test(){ 21 | true.yes { } 22 | } 23 | } -------------------------------------------------------------------------------- /app/src/test/java/com/devyk/kotlin_github/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.kotlin_github 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | apply from: "config.gradle" 3 | buildscript { 4 | ext.kotlin_version = '1.3.50' 5 | ext.support_version = '28.0.0' 6 | ext.kotlinx_coroutines_version = '1.1.1' 7 | ext.anko_version='0.10.8' 8 | repositories { 9 | google() 10 | jcenter() 11 | 12 | } 13 | dependencies { 14 | classpath 'com.android.tools.build:gradle:3.4.1' 15 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 16 | // NOTE: Do not place your application dependencies here; they belong 17 | // in the individual module build.gradle files 18 | } 19 | } 20 | 21 | allprojects { 22 | repositories { 23 | google() 24 | jcenter() 25 | maven { url "https://jitpack.io" } 26 | } 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /common/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /common/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | apply plugin: 'kotlin-android' 4 | 5 | android { 6 | compileSdkVersion rootProject.ext.android.compileSdkVersion 7 | 8 | defaultConfig { 9 | minSdkVersion rootProject.ext.android.minSdkVersion 10 | targetSdkVersion rootProject.ext.android.targetSdkVersion 11 | versionCode rootProject.ext.android.versionCode 12 | versionName rootProject.ext.android.versionName 13 | 14 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 15 | 16 | } 17 | 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | 25 | } 26 | 27 | dependencies { 28 | implementation fileTree(dir: 'libs', include: ['*.jar']) 29 | 30 | 31 | testImplementation rootProject.ext.testDeps["junit"] 32 | androidTestImplementation rootProject.ext.testDeps["runner"] 33 | androidTestImplementation rootProject.ext.testDeps["espresso-core"] 34 | 35 | 36 | api"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 37 | api "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" 38 | 39 | api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinx_coroutines_version" 40 | api "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinx_coroutines_version" 41 | 42 | api rootProject.ext.supportDeps["design"] 43 | api rootProject.ext.supportDeps["cardview"] 44 | api rootProject.ext.supportDeps["appcompatv7"] 45 | 46 | api "com.squareup.okhttp3:logging-interceptor:3.8.0" 47 | api "com.squareup.okhttp3:okhttp:3.8.0" 48 | api "com.squareup.retrofit2:converter-gson:2.3.0" 49 | api "com.squareup.retrofit2:adapter-rxjava2:2.6.0" 50 | api "com.jakewharton.retrofit:retrofit2-kotlin-coroutines-experimental-adapter:1.0.0" 51 | api "io.reactivex.rxjava2:rxandroid:2.1.1" 52 | api "io.reactivex.rxjava2:rxjava:2.2.13" 53 | api 'com.github.bumptech.glide:glide:4.3.1' 54 | 55 | 56 | // Appcompat-v7 (Anko Layouts) 57 | // implementation "org.jetbrains.anko:anko-design-coroutines:$anko_version" 58 | api "org.jetbrains.anko:anko-appcompat-v7:$anko_version" 59 | // implementation "org.jetbrains.anko:anko-design-coroutines:$anko_version" 60 | api "org.jetbrains.anko:anko-coroutines:$anko_version" 61 | 62 | // CardView-v7 63 | api "org.jetbrains.anko:anko-cardview-v7:$anko_version" 64 | 65 | 66 | // GridLayout-v7 67 | api "org.jetbrains.anko:anko-gridlayout-v7:$anko_version" 68 | 69 | // Percent 70 | api "org.jetbrains.anko:anko-percent:$anko_version" 71 | 72 | // RecyclerView-v7 73 | api "org.jetbrains.anko:anko-recyclerview-v7:$anko_version" 74 | api "org.jetbrains.anko:anko-recyclerview-v7-coroutines:$anko_version" 75 | 76 | // Support-v4 (only Anko Commons) 77 | api "org.jetbrains.anko:anko-support-v4-commons:$anko_version" 78 | 79 | // Support-v4 (Anko Layouts) 80 | api "org.jetbrains.anko:anko-support-v4:$anko_version" 81 | 82 | // ConstraintLayout 83 | api "org.jetbrains.anko:anko-constraint-layout:$anko_version" 84 | 85 | 86 | // 左滑关闭 Activity https://github.com/enbandari/SwipeFinishableActivity 87 | api "com.bennyhuo.swipefinishable:swipefinishable:1.0-rc2" 88 | // Appcompat-v7 (only Anko Commons) 89 | api "org.jetbrains.anko:anko-appcompat-v7-commons:$anko_version" 90 | api "org.jetbrains.anko:anko-sdk15-coroutines:$anko_version" 91 | api "org.jetbrains.anko:anko-sdk15:$anko_version" 92 | 93 | api 'org.slf4j:slf4j-api:1.7.21' 94 | 95 | /* 源码地址:https://github.com/enbandari/RichText;这个项目是从 https://github.com/zzhoujay/RichText 修改而来的 96 |

原因:原框架使用了 Android 系统的 Html 解析器,对 pre 和 code 没有支持 97 | 框架中使用了修改自 https://github.com/Pixplicity/HtmlCompat 的 Html 解析器 98 | */ 99 | implementation('com.github.enbandari.RichText:richtext:e85882c698') { 100 | exclude(group: "com.android.support") 101 | } 102 | 103 | 104 | } 105 | -------------------------------------------------------------------------------- /common/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 | -------------------------------------------------------------------------------- /common/src/androidTest/java/com/devyk/common/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.devyk.common; 2 | 3 | import android.content.Context; 4 | import androidx.test.InstrumentationRegistry; 5 | import androidx.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.devyk.common.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /common/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /common/src/main/java/android/support/v7/widget/TintableToggleButton.kt: -------------------------------------------------------------------------------- 1 | package android.support.v7.widget 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Context 5 | import android.content.res.ColorStateList 6 | import android.graphics.PorterDuff.Mode 7 | import android.graphics.drawable.Drawable 8 | import android.support.annotation.DrawableRes 9 | import android.support.v4.view.TintableBackgroundView 10 | import android.util.AttributeSet 11 | import android.widget.ToggleButton 12 | 13 | @SuppressLint("RestrictedApi") 14 | class TintableToggleButton 15 | @JvmOverloads 16 | constructor(context: Context, attrs: AttributeSet?= null, defStyleAttr: Int = 0) 17 | : ToggleButton(TintContextWrapper.wrap(context), attrs, defStyleAttr), TintableBackgroundView { 18 | 19 | private var backgroundTintHelper: AppCompatBackgroundHelper? = null 20 | 21 | init { 22 | backgroundTintHelper = AppCompatBackgroundHelper(this) 23 | backgroundTintHelper?.loadFromAttributes(attrs, defStyleAttr) 24 | } 25 | 26 | override fun setSupportBackgroundTintList(tint: ColorStateList?) { 27 | backgroundTintHelper?.supportBackgroundTintList = tint 28 | } 29 | 30 | override fun getSupportBackgroundTintMode() = backgroundTintHelper?.supportBackgroundTintMode 31 | 32 | override fun setSupportBackgroundTintMode(tintMode: Mode?) { 33 | backgroundTintHelper?.supportBackgroundTintMode = tintMode 34 | } 35 | 36 | override fun getSupportBackgroundTintList() = backgroundTintHelper?.supportBackgroundTintList 37 | 38 | override fun setBackgroundResource(@DrawableRes resId: Int) { 39 | super.setBackgroundResource(resId) 40 | backgroundTintHelper?.onSetBackgroundResource(resId) 41 | } 42 | 43 | override fun setBackgroundDrawable(background: Drawable?) { 44 | super.setBackgroundDrawable(background) 45 | backgroundTintHelper?.onSetBackgroundDrawable(background) 46 | } 47 | } -------------------------------------------------------------------------------- /common/src/main/java/com/devyk/common/App.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.common 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import com.devyk.common.App.instance as instance1 6 | 7 | /** 8 | *

 9 |  *     author  : devyk on 2019-10-31 13:59
10 |  *     blog    : https://juejin.im/user/578259398ac2470061f3a3fb/posts
11 |  *     github  : https://github.com/yangkun19921001
12 |  *     mailbox : yang1001yk@gmail.com
13 |  *     desc    : This is App
14 |  * 
15 | */ 16 | object App { 17 | 18 | private lateinit var instance: Context 19 | 20 | 21 | fun init(context: Context) { 22 | instance = context 23 | } 24 | 25 | 26 | fun getInstance(): Context { 27 | return instance; 28 | } 29 | } -------------------------------------------------------------------------------- /common/src/main/java/com/devyk/common/anno/PoKo.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.common.anno 2 | 3 | 4 | @Retention(AnnotationRetention.BINARY)//注解被编译到二进制文件,但不会被加载到 JVM 中。 反射不可见。 5 | @Target(AnnotationTarget.CLASS)//用于类上 6 | annotation class PoKo //可以给一个类型进行注解,类、接口、对象、甚至注解类本身 -------------------------------------------------------------------------------- /common/src/main/java/com/devyk/common/compat/Tls12.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.github.network.compat 2 | 3 | import android.os.Build 4 | import android.util.Log 5 | import com.devyk.common.compat.Tls12SocketFactory 6 | import okhttp3.ConnectionSpec 7 | import okhttp3.OkHttpClient 8 | import okhttp3.TlsVersion 9 | import javax.net.ssl.SSLContext 10 | 11 | 12 | 13 | fun OkHttpClient.Builder.enableTls12OnPreLollipop(): OkHttpClient.Builder { 14 | if (Build.VERSION.SDK_INT in 16..21) { 15 | try { 16 | val sc = SSLContext.getInstance("TLSv1.2") 17 | sc.init(null, null, null) 18 | sslSocketFactory(Tls12SocketFactory(sc.getSocketFactory())) 19 | 20 | val cs = ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS) 21 | .tlsVersions(TlsVersion.TLS_1_2) 22 | .build() 23 | 24 | val specs = listOf(cs, ConnectionSpec.COMPATIBLE_TLS, ConnectionSpec.CLEARTEXT) 25 | connectionSpecs(specs) 26 | } catch (exc: Exception) { 27 | Log.e("OkHttpTLSCompat", "Error while setting TLS 1.2", exc) 28 | } 29 | 30 | } 31 | 32 | return this 33 | } -------------------------------------------------------------------------------- /common/src/main/java/com/devyk/common/compat/Tls12SocketFactory.java: -------------------------------------------------------------------------------- 1 | package com.devyk.common.compat; 2 | 3 | import javax.net.ssl.SSLSocket; 4 | import javax.net.ssl.SSLSocketFactory; 5 | import java.io.IOException; 6 | import java.net.InetAddress; 7 | import java.net.Socket; 8 | import java.net.UnknownHostException; 9 | 10 | /** 11 | * Enables TLS v1.2 when creating SSLSockets. 12 | *

13 | * For some reason, android supports TLS v1.2 from API 16, but enables it by 14 | * default only from API 20. 15 | * @link https://developer.android.com/reference/javax/net/ssl/SSLSocket.html 16 | * @see SSLSocketFactory 17 | */ 18 | public class Tls12SocketFactory extends SSLSocketFactory { 19 | private static final String[] TLS_V12_ONLY = {"TLSv1.2"}; 20 | 21 | final SSLSocketFactory delegate; 22 | 23 | public Tls12SocketFactory(SSLSocketFactory base) { 24 | this.delegate = base; 25 | } 26 | 27 | @Override 28 | public String[] getDefaultCipherSuites() { 29 | return delegate.getDefaultCipherSuites(); 30 | } 31 | 32 | @Override 33 | public String[] getSupportedCipherSuites() { 34 | return delegate.getSupportedCipherSuites(); 35 | } 36 | 37 | @Override 38 | public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException { 39 | return patch(delegate.createSocket(s, host, port, autoClose)); 40 | } 41 | 42 | @Override 43 | public Socket createSocket(String host, int port) throws IOException, UnknownHostException { 44 | return patch(delegate.createSocket(host, port)); 45 | } 46 | 47 | @Override 48 | public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException { 49 | return patch(delegate.createSocket(host, port, localHost, localPort)); 50 | } 51 | 52 | @Override 53 | public Socket createSocket(InetAddress host, int port) throws IOException { 54 | return patch(delegate.createSocket(host, port)); 55 | } 56 | 57 | @Override 58 | public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { 59 | return patch(delegate.createSocket(address, port, localAddress, localPort)); 60 | } 61 | 62 | private Socket patch(Socket s) { 63 | if (s instanceof SSLSocket) { 64 | ((SSLSocket) s).setEnabledProtocols(TLS_V12_ONLY); 65 | } 66 | return s; 67 | } 68 | } -------------------------------------------------------------------------------- /common/src/main/java/com/devyk/common/config/UserInfo.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.common.config 2 | 3 | import com.devyk.common.ext.pref 4 | 5 | /** 6 | *

 7 |  *     author  : devyk on 2019-11-01 09:47
 8 |  *     blog    : https://juejin.im/user/578259398ac2470061f3a3fb/posts
 9 |  *     github  : https://github.com/yangkun19921001
10 |  *     mailbox : yang1001yk@gmail.com
11 |  *     desc    : This is UserInfo
12 |  * 
13 | */ 14 | object UserInfo { 15 | /** 16 | * 属性委托代理执行 17 | */ 18 | var authID by pref(-1) 19 | var username by pref("") 20 | var password by pref("") 21 | var token by pref("") 22 | 23 | /** 24 | * 判断是否登录 25 | * 26 | */ 27 | fun isLoginIn(): Boolean = token.isNotEmpty() 28 | } -------------------------------------------------------------------------------- /common/src/main/java/com/devyk/common/ext/BooleanExp.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.common.ext 2 | 3 | /** 4 | *
 5 |  *     author  : devyk on 2019-10-29 15:54
 6 |  *     blog    : https://juejin.im/user/578259398ac2470061f3a3fb/posts
 7 |  *     github  : https://github.com/yangkun19921001
 8 |  *     mailbox : yang1001yk@gmail.com
 9 |  *     desc    : This is BooleanExp
10 |  * 
11 | */ 12 | 13 | /** 14 | * sealed: 声明一个密封类(限制子类化的类)@see http://www.kotlincn.net/docs/reference/sealed-classes.html 15 | * out : 将类型参数标记为型变 16 | */ 17 | sealed class BooleanExp 18 | 19 | /** 20 | * 声明一个类及其实例,相当于声明了一个单例 21 | */ 22 | object Otherwise : BooleanExp() 23 | 24 | class WithData(val data: T) : BooleanExp() 25 | 26 | inline fun Boolean.yes(block: () -> T) = 27 | //kotlin 中的 when 表达式 28 | when { 29 | this -> { 30 | WithData(block()) 31 | } 32 | else -> { 33 | Otherwise 34 | } 35 | } 36 | 37 | 38 | inline fun Boolean.no(block: () -> T) = 39 | when { 40 | this -> Otherwise 41 | else 42 | -> { 43 | WithData(block()) 44 | } 45 | 46 | } 47 | 48 | inline fun BooleanExp.otherwise(block: () -> T): T = 49 | when (this) { 50 | is Otherwise -> block(); 51 | is WithData -> this.data 52 | 53 | } -------------------------------------------------------------------------------- /common/src/main/java/com/devyk/common/ext/ConfirmDialog.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.common.ext 2 | 3 | import android.content.Context 4 | import org.jetbrains.anko.alert 5 | import kotlin.coroutines.resume 6 | import kotlin.coroutines.suspendCoroutine 7 | 8 | suspend fun Context.confirm(title: String, message: String = "") = suspendCoroutine { 9 | continuation -> 10 | alert { 11 | this.title = title 12 | this.message = message 13 | 14 | negativeButton("Cancel"){ 15 | continuation.resume(false) 16 | } 17 | positiveButton("OK"){ 18 | continuation.resume(true) 19 | } 20 | onCancelled { 21 | continuation.resume(false) 22 | } 23 | }.show() 24 | } -------------------------------------------------------------------------------- /common/src/main/java/com/devyk/common/ext/CoroutinesExt.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.common.ext 2 | 3 | import kotlinx.coroutines.* 4 | 5 | 6 | 7 | suspend fun Deferred.awaitOrError(): Result{ 8 | return try { 9 | Result.of(await()) 10 | }catch(e: Exception){ 11 | Result.of(e) 12 | } 13 | } -------------------------------------------------------------------------------- /common/src/main/java/com/devyk/common/ext/DeviceId.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.common.ext 2 | 3 | import android.content.Context 4 | import android.provider.Settings 5 | 6 | 7 | val Context.deviceId: String 8 | get() = Settings.Secure.getString( 9 | contentResolver, 10 | Settings.Secure.ANDROID_ID 11 | ) -------------------------------------------------------------------------------- /common/src/main/java/com/devyk/common/ext/FileExt.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.common.ext 2 | 3 | import android.util.Log 4 | import java.io.File 5 | 6 | private const val TAG = "FileExt" 7 | 8 | 9 | fun File.ensureDir(): Boolean { 10 | try { 11 | isDirectory.no { 12 | isFile.yes { 13 | delete() 14 | } 15 | return mkdirs() 16 | } 17 | } catch (e: Exception) { 18 | Log.w(TAG, e.message) 19 | } 20 | return false 21 | } -------------------------------------------------------------------------------- /common/src/main/java/com/devyk/common/ext/FragmentContainerExt.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.common.ext 2 | 3 | import android.os.Bundle 4 | import android.support.v4.app.Fragment 5 | import android.support.v7.app.AppCompatActivity 6 | 7 | 8 | fun AppCompatActivity.showFragment(containerId: Int, clazz: Class, vararg args: Pair){ 9 | supportFragmentManager.beginTransaction() 10 | .replace(containerId, clazz.newInstance().apply { arguments = Bundle().apply { args.forEach { putString(it.first, it.second) } } }, clazz.name) 11 | .commitAllowingStateLoss() 12 | } 13 | 14 | fun AppCompatActivity.showFragment(containerId: Int, clazz: Class, args: Bundle){ 15 | supportFragmentManager.beginTransaction() 16 | .replace(containerId, clazz.newInstance().apply { arguments = args }, clazz.name) 17 | .commitAllowingStateLoss() 18 | } -------------------------------------------------------------------------------- /common/src/main/java/com/devyk/common/ext/GlideExt.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.common.ext 2 | 3 | import com.bumptech.glide.Glide 4 | import com.bumptech.glide.load.engine.DiskCacheStrategy 5 | import com.bumptech.glide.request.RequestOptions 6 | import com.devyk.common.weiget.AppCompatAvatarImageView 7 | 8 | 9 | fun AppCompatAvatarImageView.loadWithGlide(url: String, textPlaceHolder: Char, requestOptions: RequestOptions = RequestOptions().diskCacheStrategy( 10 | DiskCacheStrategy.RESOURCE)){ 11 | textPlaceHolder.toString().let { 12 | setTextAndColorSeed(it.toUpperCase(), it.hashCode().toString()) 13 | } 14 | 15 | Glide.with(this) 16 | .load(url) 17 | .apply(requestOptions) 18 | .into(this) 19 | 20 | } -------------------------------------------------------------------------------- /common/src/main/java/com/devyk/common/ext/GsonExt.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.common.ext 2 | 3 | import com.google.gson.Gson 4 | 5 | 6 | inline fun Gson.fromJson(json: String) = fromJson(json, T::class.java) -------------------------------------------------------------------------------- /common/src/main/java/com/devyk/common/ext/InputMethod.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.common.ext 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import android.view.View 6 | import android.view.inputmethod.InputMethodManager 7 | import org.jetbrains.anko.inputMethodManager 8 | 9 | 10 | fun Context.toggleSoftInput() { 11 | inputMethodManager.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS) 12 | } 13 | 14 | fun View.showSoftInput(): Boolean { 15 | return context.inputMethodManager.showSoftInput(this, InputMethodManager.SHOW_FORCED) 16 | } 17 | 18 | fun Activity.showSoftInput(): Boolean { 19 | return currentFocus?.showSoftInput()?: false 20 | } 21 | 22 | fun View.hideSoftInput(): Boolean { 23 | return context.inputMethodManager.hideSoftInputFromWindow(windowToken, 0) 24 | } 25 | 26 | fun Activity.hideSoftInput(): Boolean { 27 | return currentFocus?.hideSoftInput()?: false 28 | } 29 | 30 | fun Context.isActive(): Boolean { 31 | return inputMethodManager.isActive 32 | } -------------------------------------------------------------------------------- /common/src/main/java/com/devyk/common/ext/LogExt.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.common.log 2 | 3 | import org.slf4j.Logger 4 | import org.slf4j.LoggerFactory 5 | 6 | 7 | val loggerMap = HashMap, Logger>() 8 | 9 | inline val T.logger: Logger 10 | get() { 11 | return loggerMap[T::class.java]?: LoggerFactory.getLogger(T::class.java).apply { loggerMap[T::class.java] = this } 12 | } -------------------------------------------------------------------------------- /common/src/main/java/com/devyk/common/ext/NumberExt.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.common.ext 2 | 3 | /** 4 | *
 5 |  *     author  : devyk on 2019-11-05 13:57
 6 |  *     blog    : https://juejin.im/user/578259398ac2470061f3a3fb/posts
 7 |  *     github  : https://github.com/yangkun19921001
 8 |  *     mailbox : yang1001yk@gmail.com
 9 |  *     desc    : This is NumberExt
10 |  * 
11 | */ 12 | fun Int.toKilo(): String{ 13 | return if(this > 700){ 14 | "${(Math.round(this / 100f) / 10f)}k" 15 | }else{ 16 | "$this" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /common/src/main/java/com/devyk/common/ext/PrefExt.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.common.ext 2 | 3 | import com.devyk.common.App 4 | import kotlin.reflect.jvm.jvmName 5 | 6 | 7 | inline fun R.pref(default: T) = Preference(App.getInstance(), "", default, R::class.jvmName) -------------------------------------------------------------------------------- /common/src/main/java/com/devyk/common/ext/PreferencesExp.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.common.ext 2 | 3 | import android.content.Context 4 | import java.nio.channels.spi.AbstractSelectionKey 5 | import kotlin.properties.ReadWriteProperty 6 | import kotlin.reflect.KProperty 7 | 8 | /** 9 | *
10 |  *     author  : devyk on 2019-10-29 16:13
11 |  *     blog    : https://juejin.im/user/578259398ac2470061f3a3fb/posts
12 |  *     github  : https://github.com/yangkun19921001
13 |  *     mailbox : yang1001yk@gmail.com
14 |  *     desc    : This is PreferencesExp  SharedpreFerences  扩展
15 |  * 
16 | */ 17 | 18 | class Preference(var context: Context, val name: String, val default: T, val prefName: String = "defaut") : 19 | ReadWriteProperty { 20 | 21 | 22 | /** 23 | * lazy 变量委托 24 | */ 25 | private val prefs by lazy { 26 | context.getSharedPreferences(prefName, Context.MODE_PRIVATE) 27 | } 28 | 29 | 30 | override fun getValue(thisRef: Any?, property: KProperty<*>): T { 31 | return findPreference(findProperName(property)); 32 | } 33 | 34 | /** 35 | * 根据 default 来判断存入的类型 36 | */ 37 | private fun findPreference(key: String): T { 38 | return when (default) { 39 | is Long -> prefs.getLong(key, default) 40 | is Int -> prefs.getInt(key, default) 41 | is Boolean -> prefs.getBoolean(key, default) 42 | is String -> prefs.getString(key, default) 43 | else -> throw IllegalAccessException("Unsupported type.") 44 | } as T 45 | } 46 | 47 | /** 48 | * find 查询名称 49 | */ 50 | private fun findProperName(property: KProperty<*>): String = if (name.isEmpty()) property.name else name 51 | 52 | override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { 53 | putPreference(findProperName(property), value); 54 | } 55 | 56 | /** 57 | * 根据 value 类型来获取数据 58 | * with: @see https://www.jianshu.com/p/87b338516358 59 | */ 60 | private fun putPreference(key: String, value: T) { 61 | //定义 with 代表内部通过 this能够调用到 prefs.edit 返回的对象 62 | with(prefs.edit()) { 63 | when (value) { 64 | is Long -> putLong(key, value) 65 | is Int -> putInt(key, value) 66 | is Boolean -> putBoolean(key, value) 67 | is String -> putString(key, value) 68 | else -> throw IllegalAccessException("Unsupported type.") 69 | }.apply() 70 | 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /common/src/main/java/com/devyk/common/ext/PropertiesDelegate.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.common.ext 2 | 3 | import java.io.File 4 | import java.net.URL 5 | import java.util.* 6 | import kotlin.reflect.KClass 7 | import kotlin.reflect.KProperty 8 | import kotlin.reflect.full.isSubclassOf 9 | 10 | /** 11 | *
12 |  *     author  : devyk on 2019-10-30 18:21
13 |  *     blog    : https://juejin.im/user/578259398ac2470061f3a3fb/posts
14 |  *     github  : https://github.com/yangkun19921001
15 |  *     mailbox : yang1001yk@gmail.com
16 |  *     desc    : This is PropertiesDelegate 读取配置文件封装
17 |  * 
18 | */ 19 | class PropertiesDelegate(private val path: String) { 20 | 21 | 22 | 23 | 24 | 25 | /** 26 | * 利用 lateinit 关键字,允许初始化一个非空属性 27 | */ 28 | private lateinit var url :URL 29 | 30 | 31 | 32 | 33 | /** 34 | * 将 Properties 对象委托给 properties 属性 ,通过 by lazy 来延迟加载 35 | */ 36 | private val properties :Properties by lazy { 37 | val pro = Properties() 38 | /** 39 | * run 函数 40 | */ 41 | url.run { 42 | 43 | /** 44 | * use 是 Closeable 的扩展函数,内部已经调用 try 捕获异常了 45 | */ 46 | javaClass.getResourceAsStream(path).use { 47 | pro.load(it) 48 | } 49 | javaClass.getResource(path) 50 | } 51 | 52 | //要求最后一行返回 Properties 对象 53 | pro 54 | } 55 | 56 | 57 | 58 | operator fun getValue(thisRef:Any , property : KProperty<*>):T{ 59 | 60 | /** 61 | * 根据 key 拿到 properties 值 62 | */ 63 | val value = properties[property.name] 64 | 65 | /** 66 | * 拿到当前的描述符号类型 67 | */ 68 | val classOfT =property.returnType.classifier as KClass<*> 69 | 70 | return when { 71 | Boolean::class == classOfT -> value.toString().toBoolean() 72 | Number::class.isSubclassOf(classOfT) -> { 73 | classOfT.javaObjectType.getDeclaredMethod("parse${classOfT.simpleName}",String::class.java).invoke(null,value) 74 | } 75 | String::class == classOfT ->value 76 | else -> throw IllegalAccessException("Unsupported type.") 77 | } as T // as 78 | } 79 | 80 | operator fun setValue(thisRef:Any,property:KProperty<*>,value:T){ 81 | properties[property.name] = value.toString() 82 | File(url.toURI()).outputStream().use { 83 | properties.store(it,""); 84 | } 85 | } 86 | 87 | 88 | /** 89 | * 定义一个抽象管理配置关键 90 | */ 91 | abstract class AbsProperties(path: String) { 92 | protected val PROPERTIES = PropertiesDelegate(path) 93 | } 94 | 95 | } -------------------------------------------------------------------------------- /common/src/main/java/com/devyk/common/ext/Result.java: -------------------------------------------------------------------------------- 1 | package com.devyk.common.ext; 2 | 3 | 4 | import org.jetbrains.annotations.Nullable; 5 | 6 | public class Result { 7 | 8 | private T value; 9 | private Throwable error; 10 | 11 | public T getValue() { 12 | return value; 13 | } 14 | 15 | @Nullable 16 | public Throwable getError() { 17 | return error; 18 | } 19 | 20 | public T component1() { 21 | return value; 22 | } 23 | 24 | @Nullable 25 | public Throwable component2() { 26 | return error; 27 | } 28 | 29 | public static Result of(Throwable error){ 30 | Result result = new Result<>(); 31 | result.error = error; 32 | return result; 33 | } 34 | 35 | public static Result of(T value){ 36 | Result result = new Result<>(); 37 | result.value = value; 38 | return result; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /common/src/main/java/com/devyk/common/ext/RxJavaExt.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.common.ext 2 | 3 | import io.reactivex.Observable 4 | import io.reactivex.disposables.Disposable 5 | import org.reactivestreams.Subscription 6 | 7 | 8 | fun Observable.subscribeIgnoreError(onNext: (T) -> Unit): Disposable? 9 | = subscribe(onNext, Throwable::printStackTrace) -------------------------------------------------------------------------------- /common/src/main/java/com/devyk/common/ext/ViewExt.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.common.ext 2 | 3 | import android.content.Context 4 | import android.view.ViewManager 5 | import android.widget.TextView 6 | import com.devyk.common.weiget.AppCompatAvatarImageView 7 | import com.zzhoujay.richtext.RichText 8 | import org.jetbrains.anko.AnkoViewDslMarker 9 | import org.jetbrains.anko.custom.ankoView 10 | import org.w3c.dom.Text 11 | 12 | /** 13 | *
14 |  *     author  : devyk on 2019-11-05 11:07
15 |  *     blog    : https://juejin.im/user/578259398ac2470061f3a3fb/posts
16 |  *     github  : https://github.com/yangkun19921001
17 |  *     mailbox : yang1001yk@gmail.com
18 |  *     desc    : This is ViewExt
19 |  * 
20 | */ 21 | 22 | var TextView.markdownText: String 23 | set(value) { 24 | RichText.fromMarkdown(value).into(this) 25 | } 26 | get() = text.toString() 27 | 28 | var TextView.htmlText: String 29 | set(value) { 30 | RichText.fromHtml(value).into(this) 31 | } 32 | get() = text.toString() 33 | 34 | inline fun ViewManager.avatarImageView(): AppCompatAvatarImageView = avatarImageView() {} 35 | inline fun ViewManager.avatarImageView(init: (@AnkoViewDslMarker AppCompatAvatarImageView).() -> Unit): AppCompatAvatarImageView { 36 | return ankoView({ ctx: Context -> AppCompatAvatarImageView(ctx) }, theme = 0) { init() } 37 | } -------------------------------------------------------------------------------- /common/src/main/java/com/devyk/common/ext/bindExtra.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.common.ext 2 | 3 | import android.app.Activity 4 | import android.support.v4.app.Fragment 5 | import kotlin.properties.ReadOnlyProperty 6 | import kotlin.reflect.KProperty 7 | 8 | fun Activity.bindExtra(key: String) = BindLoader(key) 9 | 10 | fun Fragment.bindArgument(key: String) = BindLoader(key) 11 | 12 | fun android.app.Fragment.bindArgument(key: String) = BindLoader(key) 13 | 14 | private class IntentDelegate(private val key: String) : ReadOnlyProperty { 15 | override fun getValue(thisRef: U, property: KProperty<*>): T { 16 | @Suppress("UNCHECKED_CAST") 17 | return when (thisRef) { 18 | is Fragment -> thisRef.arguments?.get(key) as T 19 | is android.app.Fragment -> thisRef.arguments?.get(key) as T 20 | else -> (thisRef as Activity).intent?.extras?.get(key) as T 21 | } 22 | } 23 | 24 | 25 | 26 | } 27 | 28 | class BindLoader(private val key: String) { 29 | operator fun provideDelegate(thisRef: U, prop: KProperty<*>): ReadOnlyProperty { 30 | // 创建委托 31 | return IntentDelegate(key) 32 | } 33 | 34 | 35 | 36 | } 37 | -------------------------------------------------------------------------------- /common/src/main/java/com/devyk/common/mvp/ILifecycle.kt: -------------------------------------------------------------------------------- 1 | 2 | import android.content.res.Configuration 3 | import android.os.Bundle 4 | 5 | interface ILifecycle { 6 | 7 | fun onCreate(savedInstanceState: Bundle?) 8 | 9 | fun onSaveInstanceState(outState: Bundle) 10 | 11 | fun onViewStateRestored(savedInstanceState: Bundle?) 12 | 13 | fun onConfigurationChanged(newConfig: Configuration) 14 | 15 | fun onDestroy() 16 | 17 | fun onStart() 18 | 19 | fun onStop() 20 | 21 | fun onResume() 22 | 23 | fun onPause() 24 | 25 | } 26 | -------------------------------------------------------------------------------- /common/src/main/java/com/devyk/common/mvp/impl/BaseActivity.kt: -------------------------------------------------------------------------------- 1 | 2 | import android.os.Bundle 3 | import android.support.v7.app.AppCompatActivity 4 | import java.lang.reflect.ParameterizedType 5 | import java.lang.reflect.Type 6 | import kotlin.reflect.KClass 7 | import kotlin.reflect.full.isSubclassOf 8 | import kotlin.reflect.full.primaryConstructor 9 | import kotlin.reflect.jvm.jvmErasure 10 | 11 | abstract class BaseActivity>> : AppCompatActivity(), IMvpView

{ 12 | 13 | protected val TAG = javaClass.simpleName; 14 | 15 | final override val p: P 16 | 17 | init { 18 | p = createPresenterKt() 19 | p.v = this 20 | } 21 | 22 | private fun createPresenterKt(): P { 23 | sequence { 24 | var thisClass: KClass<*> = this@BaseActivity::class 25 | while (true) { 26 | yield(thisClass.supertypes) 27 | thisClass = thisClass.supertypes.firstOrNull()?.jvmErasure ?: break 28 | } 29 | }.flatMap { 30 | it.flatMap { it.arguments }.asSequence() 31 | }.first { 32 | it.type?.jvmErasure?.isSubclassOf(IPresenter::class) ?: false 33 | }.let { 34 | return it.type!!.jvmErasure.primaryConstructor!!.call() as P 35 | } 36 | } 37 | 38 | private fun createPresenter(): P { 39 | sequence{ 40 | var thisClass: Class<*> = this@BaseActivity.javaClass 41 | while (true) { 42 | yield(thisClass.genericSuperclass) 43 | thisClass = thisClass.superclass ?: break 44 | } 45 | }.filter { 46 | it is ParameterizedType 47 | }.flatMap { 48 | (it as ParameterizedType).actualTypeArguments.asSequence() 49 | }.first { 50 | it is Class<*> && IPresenter::class.java.isAssignableFrom(it) 51 | }.let { 52 | return (it as Class

).newInstance() 53 | } 54 | } 55 | 56 | override fun onCreate(savedInstanceState: Bundle?) { 57 | super.onCreate(savedInstanceState) 58 | p.onCreate(savedInstanceState) 59 | } 60 | 61 | override fun onViewStateRestored(savedInstanceState: Bundle?) {} 62 | 63 | override fun onStart() { 64 | super.onStart() 65 | p.onStart() 66 | } 67 | 68 | override fun onResume() { 69 | super.onResume() 70 | p.onResume() 71 | } 72 | 73 | override fun onPause() { 74 | super.onPause() 75 | p.onPause() 76 | } 77 | 78 | override fun onStop() { 79 | super.onStop() 80 | p.onStop() 81 | } 82 | 83 | override fun onDestroy() { 84 | p.onDestroy() 85 | super.onDestroy() 86 | } 87 | 88 | override fun onSaveInstanceState(outState: Bundle) { 89 | super.onSaveInstanceState(outState) 90 | p.onSaveInstanceState(outState) 91 | } 92 | 93 | override fun onRestoreInstanceState(savedInstanceState: Bundle?) { 94 | super.onRestoreInstanceState(savedInstanceState) 95 | onViewStateRestored(savedInstanceState) 96 | p.onViewStateRestored(savedInstanceState) 97 | } 98 | 99 | 100 | } 101 | -------------------------------------------------------------------------------- /common/src/main/java/com/devyk/common/mvp/impl/BaseFragment.kt: -------------------------------------------------------------------------------- 1 | 2 | import android.os.Bundle 3 | import android.support.v4.app.Fragment 4 | import java.lang.reflect.ParameterizedType 5 | import java.lang.reflect.Type 6 | import kotlin.reflect.KClass 7 | import kotlin.reflect.full.isSubclassOf 8 | import kotlin.reflect.full.primaryConstructor 9 | import kotlin.reflect.jvm.jvmErasure 10 | 11 | abstract class BaseFragment>>: IMvpView

, Fragment() { 12 | override val p: P 13 | 14 | init { 15 | p = createpKt() 16 | p.v = this 17 | } 18 | 19 | private fun createpKt(): P { 20 | sequence { 21 | var thisClass: KClass<*> = this@BaseFragment::class 22 | while (true){ 23 | yield(thisClass.supertypes) 24 | thisClass = thisClass.supertypes.firstOrNull()?.jvmErasure?: break 25 | } 26 | }.flatMap { 27 | it.flatMap { it.arguments }.asSequence() 28 | }.first { 29 | it.type?.jvmErasure?.isSubclassOf(IPresenter::class) ?: false 30 | }.let { 31 | return it.type!!.jvmErasure.primaryConstructor!!.call() as P 32 | } 33 | } 34 | 35 | private fun createp(): P { 36 | sequence { 37 | var thisClass: Class<*> = this@BaseFragment.javaClass 38 | while (true) { 39 | yield(thisClass.genericSuperclass!!) 40 | thisClass = thisClass.superclass ?: break 41 | } 42 | }.filter { 43 | it is ParameterizedType 44 | }.flatMap { 45 | (it as ParameterizedType).actualTypeArguments.asSequence() 46 | }.first { 47 | it is Class<*> && IPresenter::class.java.isAssignableFrom(it) 48 | }.let { 49 | return (it as Class

).newInstance() 50 | } 51 | } 52 | 53 | override fun onCreate(savedInstanceState: Bundle?) { 54 | super.onCreate(savedInstanceState) 55 | p.onCreate(savedInstanceState) 56 | } 57 | 58 | override fun onStart() { 59 | super.onStart() 60 | p.onStart() 61 | } 62 | 63 | override fun onResume() { 64 | super.onResume() 65 | p.onResume() 66 | } 67 | 68 | override fun onPause() { 69 | super.onPause() 70 | p.onPause() 71 | } 72 | 73 | override fun onStop() { 74 | super.onStop() 75 | p.onStop() 76 | } 77 | 78 | override fun onDestroy() { 79 | p.onDestroy() 80 | super.onDestroy() 81 | } 82 | 83 | override fun onSaveInstanceState(outState: Bundle) { 84 | super.onSaveInstanceState(outState) 85 | p.onSaveInstanceState(outState) 86 | } 87 | 88 | override fun onViewStateRestored(savedInstanceState: Bundle?) { 89 | super.onViewStateRestored(savedInstanceState) 90 | p.onViewStateRestored(savedInstanceState) 91 | } 92 | } -------------------------------------------------------------------------------- /common/src/main/java/com/devyk/common/mvp/impl/BasePresenter.kt: -------------------------------------------------------------------------------- 1 | 2 | import android.content.res.Configuration 3 | import android.os.Bundle 4 | 5 | abstract class BasePresenter>>: IPresenter { 6 | 7 | override lateinit var v: @UnsafeVariance V 8 | 9 | override fun onCreate(savedInstanceState: Bundle?) = Unit 10 | override fun onSaveInstanceState(outState: Bundle) = Unit 11 | override fun onViewStateRestored(savedInstanceState: Bundle?) = Unit 12 | override fun onConfigurationChanged(newConfig: Configuration) = Unit 13 | override fun onDestroy() = Unit 14 | override fun onStart() = Unit 15 | override fun onStop() = Unit 16 | override fun onResume() = Unit 17 | override fun onPause() = Unit 18 | 19 | 20 | protected val TAG = javaClass.simpleName 21 | } -------------------------------------------------------------------------------- /common/src/main/java/com/devyk/common/mvp/mvps.kt: -------------------------------------------------------------------------------- 1 | 2 | interface IPresenter>> : ILifecycle { 3 | val v: View 4 | } 5 | 6 | interface IMvpView>> : ILifecycle { 7 | val p: Presenter 8 | 9 | fun onRequest() 10 | 11 | fun onSuccess() 12 | 13 | fun onError(error: Throwable) 14 | } -------------------------------------------------------------------------------- /common/src/main/java/com/devyk/common/network/RESTFulService.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.common.network 2 | 3 | import com.bennyhuo.github.network.compat.enableTls12OnPreLollipop 4 | import com.devyk.common.App 5 | import com.devyk.common.ext.ensureDir 6 | import com.devyk.common.network.interceptor.AcceptInterceptor 7 | import com.devyk.common.network.interceptor.AuthInterceptor 8 | import com.devyk.common.network.interceptor.CacheInterceptor 9 | import io.reactivex.Scheduler 10 | import io.reactivex.android.schedulers.AndroidSchedulers 11 | import io.reactivex.schedulers.Schedulers 12 | import okhttp3.Cache 13 | import okhttp3.OkHttpClient 14 | import okhttp3.logging.HttpLoggingInterceptor 15 | import retrofit2.Retrofit 16 | import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory 17 | import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory2 18 | import retrofit2.converter.gson.GsonConverterFactory 19 | import java.io.File 20 | import java.util.concurrent.TimeUnit 21 | 22 | /** 23 | *

24 |  *     author  : devyk on 2019-10-31 12:53
25 |  *     blog    : https://juejin.im/user/578259398ac2470061f3a3fb/posts
26 |  *     github  : https://github.com/yangkun19921001
27 |  *     mailbox : yang1001yk@gmail.com
28 |  *     desc    : This is RESTFulService
29 |  * 
30 | */ 31 | 32 | private const val BASE_URL = "https://api.github.com" 33 | 34 | 35 | //通过一个 QueryParameter 让 CacheInterceptor 添加 no-cache 36 | public const val FORCE_NETWORK = "forceNetwork" 37 | /** 38 | * 缓存大小 39 | */ 40 | private const val OKHTTP_CACHE_SIZE = 1024 * 1024 * 10L 41 | 42 | /** 43 | * 定义一个 cache 44 | */ 45 | private val cacheFile by lazy { 46 | File(App.getInstance().cacheDir,"Kotlin_GitHub").apply { ensureDir() } 47 | } 48 | 49 | /** 50 | * 定义 retrofit 对象 51 | */ 52 | val RETROFIT by lazy { 53 | Retrofit.Builder() 54 | //支持 Rxjava2 默认添加 子线程 + 主线程 55 | .addCallAdapterFactory(RxJava2CallAdapterFactory2.createWithScheduler(Schedulers.io(),AndroidSchedulers.mainThread())) 56 | //支持数据转换 57 | .addConverterFactory(GsonConverterFactory.create()) 58 | .client(OKHTTPCLIENT) 59 | .baseUrl(BASE_URL) 60 | .build() 61 | 62 | 63 | } 64 | 65 | 66 | 67 | /** 68 | * 定义 OKHttpClient 69 | */ 70 | private val OKHTTPCLIENT by lazy { 71 | OkHttpClient.Builder() 72 | .addInterceptor(AcceptInterceptor()) 73 | .addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)) 74 | .addInterceptor(CacheInterceptor()) 75 | .addInterceptor(AuthInterceptor()) 76 | .connectTimeout(60,TimeUnit.SECONDS) 77 | .readTimeout(60,TimeUnit.SECONDS) 78 | .writeTimeout(60,TimeUnit.SECONDS) 79 | .cache(Cache(cacheFile,OKHTTP_CACHE_SIZE)) 80 | .enableTls12OnPreLollipop() 81 | .build() 82 | } -------------------------------------------------------------------------------- /common/src/main/java/com/devyk/common/network/interceptor/AcceptInterceptor.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.common.network.interceptor 2 | 3 | import okhttp3.Interceptor 4 | import okhttp3.Interceptor.Chain 5 | import okhttp3.Response 6 | 7 | class AcceptInterceptor : Interceptor { 8 | override fun intercept(chain: Chain): Response { 9 | val original = chain.request() 10 | return chain.proceed(original.newBuilder() 11 | .apply { 12 | header("accept", "application/vnd.github.v3.full+json, ${original.header("accept") ?: ""}") 13 | } 14 | .build()) 15 | } 16 | } -------------------------------------------------------------------------------- /common/src/main/java/com/devyk/common/network/interceptor/AuthInterceptor.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.common.network.interceptor 2 | 3 | import android.accounts.AccountManager 4 | import android.util.Base64 5 | import com.devyk.common.config.UserInfo 6 | import okhttp3.Interceptor 7 | import okhttp3.Interceptor.Chain 8 | import okhttp3.Response 9 | 10 | class AuthInterceptor: Interceptor{ 11 | override fun intercept(chain: Chain): Response { 12 | val original = chain.request() 13 | return chain.proceed(original.newBuilder() 14 | .apply { 15 | when{ 16 | original.url().pathSegments().contains("authorizations") ->{ 17 | val userCredentials = UserInfo.username + ":" + UserInfo.password 18 | val auth = "Basic " + String(Base64.encode(userCredentials.toByteArray(), Base64.DEFAULT)).trim() 19 | header("Authorization", auth) 20 | } 21 | UserInfo.isLoginIn() -> { 22 | val auth = "Token " + UserInfo.token 23 | header("Authorization", auth) 24 | } 25 | else -> removeHeader("Authorization") 26 | } 27 | } 28 | .build()) 29 | return chain.proceed(chain.request()) 30 | } 31 | } -------------------------------------------------------------------------------- /common/src/main/java/com/devyk/common/network/interceptor/CacheInterceptor.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.common.network.interceptor 2 | 3 | 4 | import com.bennyhuo.common.log.logger 5 | import com.devyk.common.App 6 | import com.devyk.common.ext.no 7 | import com.devyk.common.ext.otherwise 8 | import com.devyk.common.ext.yes 9 | import com.devyk.common.network.FORCE_NETWORK 10 | import com.devyk.common.utils.Network 11 | import okhttp3.CacheControl 12 | import okhttp3.Interceptor 13 | import okhttp3.Interceptor.Chain 14 | import okhttp3.Response 15 | import java.util.concurrent.TimeUnit 16 | 17 | 18 | class CacheInterceptor : Interceptor { 19 | override fun intercept(chain: Chain): Response { 20 | var request = chain.request() 21 | request = Network.isAvailable() 22 | .no { 23 | request.newBuilder() 24 | .cacheControl(CacheControl.FORCE_CACHE) 25 | .build() 26 | } 27 | .otherwise { 28 | request.url().queryParameter(FORCE_NETWORK)?.toBoolean()?.let { 29 | it.yes { 30 | //注意 noCache | noStore,前者不会读缓存;后者既不读缓存,也不对响应进行缓存 31 | //尽管看上去 noCache 比较符合我们的需求,但服务端仍然可能返回服务端的缓存 32 | //request.newBuilder().cacheControl(CacheControl.Builder().noCache().build()).build() 33 | request.newBuilder().cacheControl(CacheControl.Builder().maxAge(0, TimeUnit.SECONDS).build()).build() 34 | }.otherwise { 35 | request 36 | } 37 | } ?: request 38 | } 39 | 40 | request = request.newBuilder().url(request.url().newBuilder().removeAllQueryParameters(FORCE_NETWORK).build()).build() 41 | return chain.proceed(request).also { response -> 42 | logger.error("Cache: ${response.cacheResponse()}, Network: ${response.networkResponse()}") 43 | 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /common/src/main/java/com/devyk/common/utils/AdapterList.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.common.utils 2 | 3 | import android.support.v7.widget.RecyclerView 4 | 5 | 6 | class AdapterList(val adapter: RecyclerView.Adapter<*>): ArrayList(){ 7 | override fun removeAt(index: Int): T { 8 | return super.removeAt(index).apply { adapter.notifyItemRemoved(index) } 9 | } 10 | 11 | override fun remove(element: T): Boolean { 12 | val index = indexOf(element) 13 | return super.remove(element).apply { adapter.notifyItemRemoved(index) } 14 | } 15 | 16 | override fun removeRange(fromIndex: Int, toIndex: Int) { 17 | super.removeRange(fromIndex, toIndex).apply { adapter.notifyItemRangeRemoved(fromIndex, toIndex) } 18 | } 19 | 20 | override fun removeAll(elements: Collection): Boolean { 21 | return super.removeAll(elements).apply { adapter.notifyDataSetChanged() } 22 | } 23 | 24 | override fun add(index: Int, element: T) { 25 | super.add(index, element) 26 | adapter.notifyItemInserted(index) 27 | } 28 | 29 | override fun add(element: T): Boolean { 30 | return super.add(element).apply { 31 | adapter.notifyItemInserted(size - 1) 32 | } 33 | } 34 | 35 | override fun addAll(elements: Collection): Boolean { 36 | return super.addAll(elements).apply { adapter.notifyDataSetChanged() } 37 | } 38 | 39 | override fun addAll(index: Int, elements: Collection): Boolean { 40 | return super.addAll(index, elements).apply { adapter.notifyDataSetChanged() } 41 | } 42 | 43 | fun update(elements: Collection){ 44 | super.clear() 45 | super.addAll(elements) 46 | adapter.notifyDataSetChanged() 47 | } 48 | 49 | override fun set(index: Int, element: T): T { 50 | return super.set(index, element).also { 51 | adapter.notifyDataSetChanged() 52 | } 53 | } 54 | 55 | override fun clear() { 56 | super.clear() 57 | adapter.notifyDataSetChanged() 58 | } 59 | } -------------------------------------------------------------------------------- /common/src/main/java/com/devyk/common/utils/Network.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.common.utils 2 | 3 | import com.devyk.common.App 4 | import org.jetbrains.anko.connectivityManager 5 | 6 | 7 | object Network { 8 | fun isAvailable(): Boolean = App.getInstance().connectivityManager.activeNetworkInfo?.isAvailable ?: false 9 | } -------------------------------------------------------------------------------- /common/src/main/java/com/devyk/common/utils/ViewPagerAdapterList.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.common.utils 2 | 3 | import android.support.v4.view.PagerAdapter 4 | 5 | /** 6 | *
 7 |  *     author  : devyk on 2019-11-01 18:05
 8 |  *     blog    : https://juejin.im/user/578259398ac2470061f3a3fb/posts
 9 |  *     github  : https://github.com/yangkun19921001
10 |  *     mailbox : yang1001yk@gmail.com
11 |  *     desc    : This is ViewPagerAdapterList
12 |  * 
13 | */ 14 | class ViewPagerAdapterList(val adapter: PagerAdapter) : ArrayList() { 15 | 16 | override fun removeAt(index: Int): T { 17 | return super.removeAt(index).apply { adapter.notifyDataSetChanged() } 18 | } 19 | 20 | override fun remove(element: T): Boolean { 21 | return super.remove(element).apply { adapter.notifyDataSetChanged() } 22 | } 23 | 24 | override fun removeRange(fromIndex: Int, toIndex: Int) { 25 | super.removeRange(fromIndex, toIndex).apply { adapter.notifyDataSetChanged() } 26 | } 27 | 28 | override fun removeAll(elements: Collection): Boolean { 29 | return super.removeAll(elements).apply { adapter.notifyDataSetChanged() } 30 | } 31 | 32 | override fun add(element: T): Boolean { 33 | return super.add(element).apply { adapter.notifyDataSetChanged() } 34 | } 35 | 36 | override fun add(index: Int, element: T) { 37 | super.add(index, element).apply { adapter.notifyDataSetChanged() } 38 | } 39 | 40 | override fun addAll(elements: Collection): Boolean { 41 | return super.addAll(elements).apply { adapter.notifyDataSetChanged() } 42 | } 43 | 44 | override fun addAll(index: Int, elements: Collection): Boolean { 45 | return super.addAll(index, elements).apply { adapter.notifyDataSetChanged() } 46 | } 47 | 48 | fun update(elements: Collection) { 49 | super.clear() 50 | super.addAll(elements).let { adapter.notifyDataSetChanged() } 51 | } 52 | } -------------------------------------------------------------------------------- /common/src/main/java/com/devyk/common/utils/Weak.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.common.utils 2 | 3 | import android.util.Log 4 | import java.lang.ref.WeakReference 5 | import kotlin.reflect.KProperty 6 | 7 | /** 8 | *
 9 |  *     author  : devyk on 2019-11-04 10:13
10 |  *     blog    : https://juejin.im/user/578259398ac2470061f3a3fb/posts
11 |  *     github  : https://github.com/yangkun19921001
12 |  *     mailbox : yang1001yk@gmail.com
13 |  *     desc    : This is Weak
14 |  * 
15 | */ 16 | class Weak (initializer: () -> T?) { 17 | var weakReference = WeakReference(initializer()) 18 | 19 | constructor():this({ 20 | null 21 | }) 22 | 23 | operator fun getValue(thisRef: Any?, property: KProperty<*>): T? { 24 | Log.d("Weak Delegate","-----------getValue") 25 | return weakReference.get() 26 | } 27 | 28 | operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) { 29 | Log.d("Weak Delegate","-----------setValue") 30 | weakReference = WeakReference(value) 31 | } 32 | 33 | } 34 | 35 | -------------------------------------------------------------------------------- /common/src/main/java/com/devyk/common/utils/githubDateFormatter.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.common.utils 2 | 3 | import java.text.SimpleDateFormat 4 | import java.util.* 5 | 6 | 7 | 8 | /** 9 | * ISO 8601 10 | * https://stackoverflow.com/questions/19112357/java-simpledateformatyyyy-mm-ddthhmmssz-gives-timezone-as-ist 11 | */ 12 | val githubDateFormatter by lazy { 13 | SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.CHINA) 14 | .apply { 15 | timeZone = TimeZone.getTimeZone("GMT") 16 | } 17 | } 18 | 19 | val outputDateFormatter by lazy { 20 | SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.CHINA) 21 | } 22 | 23 | fun Date.format(pattern: String) = SimpleDateFormat(pattern, Locale.CHINA).format(this) 24 | 25 | fun githubTimeToDate(time: String) = githubDateFormatter.parse(time) 26 | 27 | fun Date.view(): String{ 28 | val now = System.currentTimeMillis() 29 | val seconds = (now - this.time) / 1000 30 | val minutes = seconds / 60 31 | return if(minutes >= 60){ 32 | val hours = minutes / 60 33 | if(hours in 24..47){ 34 | "Yesterday" 35 | } else if(hours >= 48 ){ 36 | outputDateFormatter.format(this) 37 | } else { 38 | "$hours hours ago" 39 | } 40 | } else if(minutes < 1){ 41 | "$seconds seconds ago" 42 | } else { 43 | "$minutes minutes ago" 44 | } 45 | } -------------------------------------------------------------------------------- /common/src/main/java/com/devyk/common/weiget/ErrorInfoView.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.common.weiget 2 | 3 | import android.animation.Animator 4 | import android.animation.AnimatorListenerAdapter 5 | import android.graphics.Color 6 | import android.support.annotation.StringRes 7 | import android.view.ViewGroup 8 | import android.widget.TextView 9 | import org.jetbrains.anko.* 10 | 11 | 12 | class ErrorInfoView(val parentView: ViewGroup) : _RelativeLayout(parentView.context) { 13 | 14 | private var textView: TextView 15 | 16 | var isShowing = false 17 | 18 | init { 19 | backgroundColor = Color.WHITE 20 | textView = textView { 21 | textSize = 18f 22 | textColor = Color.BLACK 23 | padding = dip(5) 24 | }.lparams { 25 | centerInParent() 26 | } 27 | } 28 | 29 | fun show(text: String) { 30 | if (!isShowing) { 31 | parentView.addView(this, matchParent, matchParent) 32 | alpha = 0f 33 | animate().alpha(1f).setDuration(100).start() 34 | isShowing = true 35 | } 36 | textView.text = text 37 | } 38 | 39 | fun dismiss() { 40 | if (isShowing) { 41 | parentView.startViewTransition(this) 42 | parentView.removeView(this) 43 | animate().alpha(0f).setDuration(100).setListener(object : AnimatorListenerAdapter() { 44 | override fun onAnimationEnd(animation: Animator?) { 45 | parentView.endViewTransition(this@ErrorInfoView) 46 | } 47 | }).start() 48 | isShowing = false 49 | } 50 | } 51 | 52 | fun show(@StringRes textRes: Int) { 53 | show(context.getString(textRes)) 54 | } 55 | } -------------------------------------------------------------------------------- /common/src/main/java/retrofit2/adapter/rxjava2/RxJava2CallAdapter2.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Jake Wharton 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package retrofit2.adapter.rxjava2; 17 | 18 | import io.reactivex.BackpressureStrategy; 19 | import io.reactivex.Observable; 20 | import io.reactivex.Scheduler; 21 | import io.reactivex.annotations.Nullable; 22 | import io.reactivex.plugins.RxJavaPlugins; 23 | import retrofit2.Call; 24 | import retrofit2.CallAdapter; 25 | import retrofit2.Response; 26 | 27 | import java.lang.reflect.Type; 28 | 29 | final class RxJava2CallAdapter2 implements CallAdapter { 30 | private final Type responseType; 31 | private final @Nullable Scheduler scheduler; 32 | private final @Nullable Scheduler mainScheduler; 33 | private final boolean isAsync; 34 | private final boolean isResult; 35 | private final boolean isBody; 36 | private final boolean isFlowable; 37 | private final boolean isSingle; 38 | private final boolean isMaybe; 39 | private final boolean isCompletable; 40 | 41 | RxJava2CallAdapter2(Type responseType, @Nullable Scheduler scheduler, @Nullable Scheduler mainThread, boolean isAsync, 42 | boolean isResult, boolean isBody, boolean isFlowable, boolean isSingle, boolean isMaybe, 43 | boolean isCompletable) { 44 | this.responseType = responseType; 45 | this.scheduler = scheduler; 46 | this.mainScheduler = mainThread; 47 | this.isAsync = isAsync; 48 | this.isResult = isResult; 49 | this.isBody = isBody; 50 | this.isFlowable = isFlowable; 51 | this.isSingle = isSingle; 52 | this.isMaybe = isMaybe; 53 | this.isCompletable = isCompletable; 54 | } 55 | 56 | @Override public Type responseType() { 57 | return responseType; 58 | } 59 | 60 | @Override public Object adapt(Call call) { 61 | Observable> responseObservable = isAsync 62 | ? new CallEnqueueObservable<>(call) 63 | : new CallExecuteObservable<>(call); 64 | 65 | Observable observable; 66 | if (isResult) { 67 | observable = new ResultObservable<>(responseObservable); 68 | } else if (isBody) { 69 | observable = new BodyObservable<>(responseObservable); 70 | } else { 71 | observable = responseObservable; 72 | } 73 | 74 | if (scheduler != null) { 75 | observable = observable.subscribeOn(scheduler); 76 | } 77 | 78 | if (mainScheduler != null) { 79 | observable = observable.observeOn(mainScheduler); 80 | } 81 | 82 | if (isFlowable) { 83 | return observable.toFlowable(BackpressureStrategy.LATEST); 84 | } 85 | if (isSingle) { 86 | return observable.singleOrError(); 87 | } 88 | if (isMaybe) { 89 | return observable.singleElement(); 90 | } 91 | if (isCompletable) { 92 | return observable.ignoreElements(); 93 | } 94 | return RxJavaPlugins.onAssembly(observable); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /common/src/main/res/anim/left_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 10 | -------------------------------------------------------------------------------- /common/src/main/res/anim/left_out.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 10 | -------------------------------------------------------------------------------- /common/src/main/res/anim/rignt_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 10 | -------------------------------------------------------------------------------- /common/src/main/res/anim/rignt_out.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 10 | -------------------------------------------------------------------------------- /common/src/main/res/drawable/side_nav_bar.xml: -------------------------------------------------------------------------------- 1 | 3 | 8 | -------------------------------------------------------------------------------- /common/src/main/res/drawable/side_nav_bar_night.xml: -------------------------------------------------------------------------------- 1 | 3 | 8 | -------------------------------------------------------------------------------- /common/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /common/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #24292E 4 | #333333 5 | #555555 6 | 7 | #FFFFFF 8 | #F0F0F0 9 | #AAAAAA 10 | 11 | #FF0000 12 | 13 | #2332B5 14 | #F3F3F3 15 | #EEEEEE 16 | 17 | #24292E 18 | #333333 19 | #3F51B5 20 | 21 | 22 | #333333 23 | #333333 24 | 25 | #ea4235 26 | #fabc05 27 | #34a853 28 | #4286f5 29 | 30 | -------------------------------------------------------------------------------- /common/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | common 3 | 4 | -------------------------------------------------------------------------------- /common/src/test/java/com/devyk/common/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.devyk.common; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /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=false 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=false 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official 22 | 23 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangkun19921001/Kotlin_GitHub/989761f90db567a817b7135b2da158cb98440e5a/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Oct 29 15:11:24 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.1.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /screenMatch.properties: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | # Start with '#' is annotate. # 3 | # In front of '=' is key, cannot be modified. # 4 | # More information to visit: # 5 | # http://blog.csdn.net/fesdgasdgasdg/article/details/52325590 # 6 | # http://download.csdn.net/detail/fesdgasdgasdg/9913744 # 7 | # https://github.com/mengzhinan/PhoneScreenMatch # 8 | ############################################################################ 9 | # 10 | # You need to refresh or reopen the project every time you modify the configuration, 11 | # or you can't get the latest configuration parameters. 12 | # 13 | ############################################################################# 14 | # 15 | # Base dp value for screen match. Cut the screen into [base_dp] parts. 16 | # Data type is double. System default value is 360. 17 | # I advise you not to modify the value, be careful !!!!!!!!! _^_ *_* 18 | base_dp=360 19 | # Also need to match the phone screen of [match_dp]. 20 | # If you have another dp values. 21 | # System default values is 384,392,400,410,411,480,533,592,600,640,662,720,768,800,811,820,960,961,1024,1280,1365 22 | match_dp=384,392,400,410,411,480 23 | # If you not wanna to match dp values above. Write some above values here, append value with "," . 24 | # For example: 811,961,1365 25 | ignore_dp=811,961,1365 26 | # They're not android module name. If has more��split with , Symbol. 27 | # If you set, it will not show in SelectDialog. 28 | # If you have, write here and append value with "," . 29 | # For example: testLibrary,commonModule 30 | # System default values is .gradle, gradle, .idea, build, .git 31 | ignore_module_name= 32 | # Use which module under the values/dimen.xml file to do the base file, 33 | # and generated dimen.xml file store in this module? 34 | # Default value is 'app'. 35 | match_module=app 36 | # Don't show select dialog again when use this plugin. 37 | # System screen match will use the last selected module name or default module name. 38 | # You can give value true or false. Default value is false. 39 | not_show_dialog=false 40 | # Do you want to generate the default example dimens.xml file? 41 | # In path of .../projectName/screenMatch_example_dimens.xml, It does not affect your project code. 42 | # You can give value true or false. Default value is false. 43 | not_create_default_dimens=false 44 | # Does the font scale the same size as the DP? May not be accuracy. 45 | # You can give value true or false. Default value is true. Also need scaled. 46 | is_match_font_sp=true 47 | # Do you want to create values-wXXXdp folder or values-swXXXdp folder ? 48 | # I suggest you create values-swXXXdp folder, 49 | # because I had a problem when I was working on the horizontal screen adapter. 50 | # values-swXXXdp folder can solve my problem. 51 | # If you want create values-swXXXdp folder, set "create_values_sw_folder=true", 52 | # otherwise set "create_values_sw_folder=true". 53 | # Default values is true. 54 | create_values_sw_folder=true 55 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':common' 2 | --------------------------------------------------------------------------------