├── .gitignore ├── .travis.yml ├── LICENSE.md ├── Monkey-1.0.0.apk ├── README.md ├── app ├── .gitignore ├── build.gradle ├── google-services.json ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── yeungeek │ │ └── monkeyandroid │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ ├── langs.json │ │ └── markdown_css_themes │ │ │ ├── alt.css │ │ │ ├── classic.css │ │ │ ├── github.css │ │ │ └── paperwhite.css │ ├── java │ │ └── com │ │ │ └── yeungeek │ │ │ └── monkeyandroid │ │ │ ├── MonkeyApplication.java │ │ │ ├── data │ │ │ ├── DataManager.java │ │ │ ├── local │ │ │ │ ├── DatabaseHelper.java │ │ │ │ ├── Db.java │ │ │ │ ├── DbOpenHelper.java │ │ │ │ ├── LanguageHelper.java │ │ │ │ └── PreferencesHelper.java │ │ │ ├── meta │ │ │ │ └── LibRxJava.java │ │ │ ├── model │ │ │ │ ├── AccessTokenResp.java │ │ │ │ ├── Language.java │ │ │ │ ├── Pair.java │ │ │ │ ├── Repo.java │ │ │ │ ├── RepoContent.java │ │ │ │ ├── User.java │ │ │ │ ├── WrapList.java │ │ │ │ └── WrapUser.java │ │ │ └── remote │ │ │ │ ├── GithubApi.java │ │ │ │ ├── SimpleApi.java │ │ │ │ ├── TokenInterceptor.java │ │ │ │ ├── UnauthorisedInterceptor.java │ │ │ │ └── retrofit2 │ │ │ │ └── StringConverterFactory.java │ │ │ ├── injection │ │ │ ├── ActivityContext.java │ │ │ ├── ApplicationContext.java │ │ │ ├── PerActivity.java │ │ │ ├── component │ │ │ │ ├── ActivityComponent.java │ │ │ │ ├── ApplicationComponent.java │ │ │ │ └── ReposComponent.java │ │ │ └── module │ │ │ │ ├── ActivityModule.java │ │ │ │ └── ApplicationModule.java │ │ │ ├── rxbus │ │ │ ├── RxBus.java │ │ │ └── event │ │ │ │ ├── BusEvent.java │ │ │ │ └── SignInEvent.java │ │ │ ├── ui │ │ │ ├── AboutActivity.java │ │ │ ├── LauncherActivity.java │ │ │ ├── base │ │ │ │ ├── adapter │ │ │ │ │ ├── EndlessRecyclerOnScrollListener.java │ │ │ │ │ ├── ExStaggeredGridLayoutManager.java │ │ │ │ │ ├── HeaderAndFooterRecyclerViewAdapter.java │ │ │ │ │ ├── HeaderSpanSizeLookup.java │ │ │ │ │ ├── LoadingFooter.java │ │ │ │ │ ├── OnListLoadNextPageListener.java │ │ │ │ │ ├── RecyclerViewStateUtils.java │ │ │ │ │ └── RecyclerViewUtils.java │ │ │ │ ├── presenter │ │ │ │ │ └── MvpLceRxPresenter.java │ │ │ │ └── view │ │ │ │ │ ├── BaseActivity.java │ │ │ │ │ ├── BaseFragment.java │ │ │ │ │ ├── BaseLceActivity.java │ │ │ │ │ ├── BaseLceFragment.java │ │ │ │ │ ├── BasePageFragment.java │ │ │ │ │ └── BaseToolbarFragment.java │ │ │ ├── detail │ │ │ │ ├── DetailActivity.java │ │ │ │ ├── FollowUserFragment.java │ │ │ │ ├── FollowUserMvpView.java │ │ │ │ ├── FollowUserPresenter.java │ │ │ │ ├── RepoDetailFragment.java │ │ │ │ ├── RepoDetailMvpView.java │ │ │ │ ├── RepoDetailPresenter.java │ │ │ │ ├── UserDetailFragment.java │ │ │ │ ├── UserDetailMvpView.java │ │ │ │ └── UserDetailPresenter.java │ │ │ ├── main │ │ │ │ ├── MainActivity.java │ │ │ │ ├── MainMvpView.java │ │ │ │ └── MainPresenter.java │ │ │ ├── repos │ │ │ │ ├── HotRepoFragment.java │ │ │ │ ├── RepoAdapter.java │ │ │ │ ├── RepoListFragment.java │ │ │ │ ├── RepoMvpView.java │ │ │ │ └── RepoPresenter.java │ │ │ ├── signin │ │ │ │ └── SignInDialogFragment.java │ │ │ ├── trending │ │ │ │ ├── TrendingFragment.java │ │ │ │ ├── TrendingListFragment.java │ │ │ │ ├── TrendingMvpView.java │ │ │ │ └── TrendingPresenter.java │ │ │ └── users │ │ │ │ ├── FamousUserFragment.java │ │ │ │ ├── UserAdapter.java │ │ │ │ ├── UserListFragment.java │ │ │ │ ├── UserMvpView.java │ │ │ │ └── UserPresenter.java │ │ │ ├── util │ │ │ ├── AppCst.java │ │ │ ├── EncodingUtil.java │ │ │ ├── HttpStatus.java │ │ │ └── ImageSize.java │ │ │ └── views │ │ │ └── widget │ │ │ ├── ScrollAwareFABBehavior.java │ │ │ ├── ScrollOffBottomBehavior.java │ │ │ └── TriangleLabelView.java │ └── res │ │ ├── drawable-xhdpi │ │ ├── ic_about.png │ │ ├── ic_arrow_back.png │ │ ├── ic_arrow_drop.png │ │ ├── ic_arrow_upward.png │ │ ├── ic_avatar.png │ │ ├── ic_favorite.png │ │ ├── ic_favorite_border.png │ │ ├── ic_menu.png │ │ ├── ic_more_vert.png │ │ ├── ic_person.png │ │ ├── ic_person_add.png │ │ ├── ic_repo.png │ │ ├── ic_retry.png │ │ ├── ic_trending.png │ │ └── ic_users.png │ │ ├── layout │ │ ├── activity_about.xml │ │ ├── activity_detail.xml │ │ ├── activity_main.xml │ │ ├── activity_repos.xml │ │ ├── fragment_hot_repos.xml │ │ ├── fragment_pull_refresh_list.xml │ │ ├── fragment_repo_detail.xml │ │ ├── fragment_user_detail.xml │ │ ├── item_repo.xml │ │ ├── item_user.xml │ │ ├── layout_appbar.xml │ │ ├── layout_common_list_footer.xml │ │ ├── layout_common_list_footer_end.xml │ │ ├── layout_common_list_footer_loading.xml │ │ ├── layout_common_list_footer_network_error.xml │ │ ├── layout_drawer_header.xml │ │ ├── layout_spinner_item_actionbar.xml │ │ ├── layout_spinner_item_dropdown.xml │ │ ├── layout_trending_repos.xml │ │ ├── view_error.xml │ │ └── view_loading.xml │ │ ├── menu │ │ ├── menu_drawer.xml │ │ └── menu_main.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ ├── ic_cloud_off.png │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-v21 │ │ └── styles.xml │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── attrs.xml │ │ ├── attrs_triangleLabelview.xml │ │ ├── colors.xml │ │ ├── colors_material.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── yeungeek │ └── monkeyandroid │ ├── DataManagerTest.java │ ├── EncodingUtilTest.java │ ├── PreferencesHelperTest.java │ └── util │ └── DefaultConfig.java ├── build.gradle ├── buildsystem ├── artifacts.gradle ├── dependencies.gradle ├── jacoco.gradle ├── maven-push-java-lib.gradle └── maven-push.gradle ├── config ├── keystore │ ├── debug.keystore │ └── release.keystore └── quality │ ├── checkstyle │ └── checkstyle-config.xml │ ├── findbugs │ └── android-exclude-filter.xml │ ├── pmd │ └── pmd-ruleset.xml │ └── quality.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── images ├── fir.png ├── monkey.gif ├── preview1.png ├── preview2.png └── preview3.png ├── mvp ├── .gitignore ├── README.md ├── build.gradle ├── proguard-rules.pro └── src │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── yeungeek │ │ │ └── mvp │ │ │ ├── MvpActivity.java │ │ │ ├── MvpFragment.java │ │ │ ├── common │ │ │ ├── Defaults.java │ │ │ ├── MvpBasePresenter.java │ │ │ ├── MvpNullObjectBasePresenter.java │ │ │ ├── MvpPresenter.java │ │ │ ├── MvpView.java │ │ │ ├── NoOp.java │ │ │ └── lce │ │ │ │ └── MvpLceView.java │ │ │ └── core │ │ │ ├── delegate │ │ │ ├── ActivityMvpDelegate.java │ │ │ ├── ActivityMvpDelegateCallback.java │ │ │ ├── ActivityMvpDelegateImpl.java │ │ │ ├── ActivityMvpNonConfigurationInstances.java │ │ │ ├── BaseMvpDelegateCallback.java │ │ │ ├── FragmentMvpDelegate.java │ │ │ ├── FragmentMvpDelegateImpl.java │ │ │ └── MvpInternalDelegate.java │ │ │ └── lce │ │ │ ├── LceAnimator.java │ │ │ ├── MvpLceActivity.java │ │ │ └── MvpLceFragment.java │ └── res │ │ └── values │ │ ├── ids.xml │ │ └── strings.xml │ └── test │ └── java │ └── com │ └── yeungeek │ └── mvp │ └── ExampleUnitTest.java └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | ## Directory-based project format: 3 | .idea/ 4 | .gradle/ 5 | 6 | ### Android ### 7 | # Built application files 8 | *.apk 9 | *.ap_ 10 | local.properties 11 | .DS_Store 12 | build 13 | /captures 14 | *.txt 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | branches: 2 | only: 3 | - master 4 | 5 | env: 6 | matrix: 7 | - ANDROID_TARGET=android-23 ANDROID_ABI=armeabi-v7a 8 | 9 | language: android 10 | sudo: false 11 | 12 | android: 13 | components: 14 | - platform-tools 15 | - tools 16 | - android-23 17 | - build-tools-23.0.2 18 | - extra-android-m2repository 19 | - extra-android-support 20 | - sys-img-armeabi-v7a-android-23 21 | 22 | before_script: 23 | - echo no | android create avd --force -n test -t $ANDROID_TARGET --abi $ANDROID_ABI 24 | - emulator -avd test -no-skin -no-audio -no-window & 25 | - android-wait-for-emulator 26 | - adb shell input keyevent 82 & 27 | 28 | before_install: 29 | - chmod +x gradlew 30 | 31 | after_success: 32 | - ./gradlew testInternalDebugUnitTestCoverage coveralls -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 yeungeek 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Monkey-1.0.0.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeungeek/monkey-android/f1bc5132b7571766b26d1b07645ffe9f0de5ab81/Monkey-1.0.0.apk -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Monkey for GitHub, Android Version 2 | A GitHub third party client, show the rank of users and repositories,trending. 3 | Base on Material Design,use MVP pattern 4 | iOS version:[Monkey](https://github.com/coderyi/Monkey) 5 | 6 | [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-Monkey%20for%20GitHub-brightgreen.svg?style=flat)](http://android-arsenal.com/details/3/3717) 7 | 8 | ## Status 9 | [![Build Status](https://travis-ci.org/yeungeek/monkey-android.svg?branch=master)](https://travis-ci.org/yeungeek/monkey-android) 10 | [![Coverage Status](https://coveralls.io/repos/github/yeungeek/monkey-android/badge.svg?branch=master)](https://coveralls.io/github/yeungeek/monkey-android?branch=master) 11 | 12 | ## Download 13 | [![Get it on Google Play](http://www.android.com/images/brand/get_it_on_play_logo_large.png)](https://play.google.com/store/apps/details?id=com.yeungeek.monkeyandroid) 14 | 15 | fir.im: http://fir.im/monkeyandroid 16 | 17 | ![](images/fir.png) 18 | 19 | ## API 20 | GitHub interface: https://developer.github.com/v3/ 21 | 22 | ## Function 23 | - [x] sign in 24 | - [x] user list 25 | - [x] repo list 26 | - [x] trending(daily,weekly,monthly) 27 | - [x] user detail 28 | - [x] repo detail 29 | - [x] follow user 30 | - [x] star repo 31 | 32 | ## TO DO 33 | - [ ] search 34 | - [ ] open url(scheme) 35 | - [ ] showcases 36 | 37 | ## App Preview 38 | ![](images/monkey.gif) 39 | 40 | ![](images/preview1.png) 41 | ![](images/preview2.png) 42 | ![](images/preview3.png) 43 | 44 | ## Open source projects 45 | * [RxJava](https://github.com/ReactiveX/RxJava/) 46 | * [RxAndroid](https://github.com/ReactiveX/RxAndroid/) 47 | * [retrofit](https://github.com/square/retrofit/) 48 | * [OkHttp](https://github.com/square/okhttp) 49 | * [Dagger2](https://github.com/google/dagger/) 50 | * [glide](https://github.com/bumptech/glide/) 51 | * [ButterKnife](https://github.com/JakeWharton/butterknife/) 52 | * [Stetho](https://github.com/facebook/stetho) 53 | * [Sqlbrite](https://github.com/square/sqlbrite) 54 | * [Timber](https://github.com/JakeWharton/timber/) 55 | * [android-Ultra-Pull-To-Refresh](https://github.com/liaohuqiu/android-Ultra-Pull-To-Refresh) 56 | * [PreLollipopTransition](https://github.com/takahirom/PreLollipopTransition) 57 | 58 | ## References 59 | * [ribot-app-android](https://github.com/ribot/ribot-app-android) 60 | * [Monkey](https://github.com/coderyi/Monkey) 61 | * [GithubTrends](https://github.com/laowch/GithubTrends) 62 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/google-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_info": { 3 | "project_number": "423857193954", 4 | "firebase_url": "https://monkey-android-7aa77.firebaseio.com", 5 | "project_id": "monkey-android-7aa77", 6 | "storage_bucket": "monkey-android-7aa77.appspot.com" 7 | }, 8 | "client": [ 9 | { 10 | "client_info": { 11 | "mobilesdk_app_id": "1:423857193954:android:540ddf433cc50b06", 12 | "android_client_info": { 13 | "package_name": "com.yeungeek.monkeyandroid" 14 | } 15 | }, 16 | "oauth_client": [ 17 | { 18 | "client_id": "423857193954-99shkcq9omsambusgjo3den5pqsqtihb.apps.googleusercontent.com", 19 | "client_type": 3 20 | } 21 | ], 22 | "api_key": [ 23 | { 24 | "current_key": "AIzaSyDuo6NGWj9m6C8Hx5Fd7qFc-puIiZBABoE" 25 | } 26 | ], 27 | "services": { 28 | "analytics_service": { 29 | "status": 1 30 | }, 31 | "appinvite_service": { 32 | "status": 1, 33 | "other_platform_oauth_client": [] 34 | }, 35 | "ads_service": { 36 | "status": 2 37 | } 38 | } 39 | } 40 | ], 41 | "configuration_version": "1" 42 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/yeungeek/monkeyandroid/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 23 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 36 | 37 | 39 | 40 | 42 | 43 | 44 | 48 | 49 | 50 | 53 | 54 | 57 | 58 | 61 | 62 | 65 | 66 | 67 | 70 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /app/src/main/assets/markdown_css_themes/alt.css: -------------------------------------------------------------------------------- 1 | body { 2 | line-height: 1.4em; 3 | color: black; 4 | padding: 1em; 5 | margin: auto; 6 | max-width: 42em; 7 | } 8 | 9 | li { 10 | color: black; 11 | } 12 | 13 | h1,h2,h3,h4,h5,h6 { 14 | border: 0 none !important; 15 | } 16 | 17 | h1 { 18 | margin-top: 0.5em; 19 | margin-bottom: 0.5em; 20 | border-bottom: 2px solid #000080 !important; 21 | } 22 | 23 | h2 { 24 | margin-top: 1em; 25 | margin-bottom: 0.5em; 26 | border-bottom: 2px solid #000080 !important; 27 | } 28 | 29 | pre { 30 | background-color: #f8f8f8; 31 | border: 1px solid #2f6fab; 32 | border-radius: 3px; 33 | overflow: auto; 34 | padding: 5px; 35 | } 36 | 37 | pre code { 38 | background-color: inherit; 39 | border: none; 40 | padding: 0; 41 | } 42 | 43 | code { 44 | background-color: #ffffe0; 45 | border: 1px solid orange; 46 | border-radius: 3px; 47 | padding: 0 0.2em; 48 | } 49 | 50 | a { 51 | text-decoration: underline; 52 | } 53 | 54 | ul,ol { 55 | padding-left: 30px; 56 | } 57 | 58 | li { 59 | margin: 0.2em 0 0 0em; 60 | padding: 0px; 61 | } 62 | 63 | em { 64 | color: #b05000; 65 | } 66 | 67 | table.text th,table.text td { 68 | vertical-align: top; 69 | border-top: 1px solid #ccc; 70 | padding: 5px; 71 | } -------------------------------------------------------------------------------- /app/src/main/assets/markdown_css_themes/classic.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | /** 4 | * markdown.css 5 | * 6 | * This program is free software: you can redistribute it and/or modify it under 7 | * the terms of the GNU Lesser General Public License as published by the Free 8 | * Software Foundation, either version 3 of the License, or (at your option) any 9 | * later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, but WITHOUT 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 14 | * details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program. If not, see http://gnu.org/licenses/lgpl.txt. 18 | * 19 | * @project Weblog and Open Source Projects of Florian Wolters 20 | * @version GIT: $Id$ 21 | * @package xhtml-css 22 | * @author Florian Wolters 23 | * @copyright 2012 Florian Wolters 24 | * @cssdoc version 1.0-pre 25 | * @license http://gnu.org/licenses/lgpl.txt GNU Lesser General Public License 26 | * @link http://github.com/FlorianWolters/jekyll-bootstrap-theme 27 | * @media all 28 | * @valid true 29 | */ 30 | 31 | body { 32 | font-family: Helvetica, Arial, Freesans, clean, sans-serif; 33 | padding:1em; 34 | margin:auto; 35 | max-width:42em; 36 | background:#fefefe; 37 | } 38 | 39 | h1, h2, h3, h4, h5, h6 { 40 | font-weight: bold; 41 | } 42 | 43 | h1 { 44 | color: #000000; 45 | font-size: 28px; 46 | } 47 | 48 | h2 { 49 | border-bottom: 1px solid #CCCCCC; 50 | color: #000000; 51 | font-size: 24px; 52 | } 53 | 54 | h3 { 55 | font-size: 18px; 56 | } 57 | 58 | h4 { 59 | font-size: 16px; 60 | } 61 | 62 | h5 { 63 | font-size: 14px; 64 | } 65 | 66 | h6 { 67 | color: #777777; 68 | background-color: inherit; 69 | font-size: 14px; 70 | } 71 | 72 | hr { 73 | height: 0.2em; 74 | border: 0; 75 | color: #CCCCCC; 76 | background-color: #CCCCCC; 77 | } 78 | 79 | p, blockquote, ul, ol, dl, li, table, pre { 80 | margin: 15px 0; 81 | } 82 | 83 | code, pre { 84 | border-radius: 3px; 85 | background-color: #F8F8F8; 86 | color: inherit; 87 | } 88 | 89 | code { 90 | border: 1px solid #EAEAEA; 91 | margin: 0 2px; 92 | padding: 0 5px; 93 | } 94 | 95 | pre { 96 | border: 1px solid #CCCCCC; 97 | line-height: 1.25em; 98 | overflow: auto; 99 | padding: 6px 10px; 100 | } 101 | 102 | pre > code { 103 | border: 0; 104 | margin: 0; 105 | padding: 0; 106 | } 107 | 108 | a, a:visited { 109 | color: #4183C4; 110 | background-color: inherit; 111 | text-decoration: none; 112 | } 113 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/MonkeyApplication.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | 6 | import com.facebook.stetho.Stetho; 7 | import com.tencent.bugly.Bugly; 8 | import com.tencent.bugly.beta.Beta; 9 | import com.yeungeek.monkeyandroid.injection.component.ApplicationComponent; 10 | import com.yeungeek.monkeyandroid.injection.component.DaggerApplicationComponent; 11 | import com.yeungeek.monkeyandroid.injection.module.ApplicationModule; 12 | 13 | import timber.log.Timber; 14 | 15 | /** 16 | * Created by yeungeek on 2016/1/8. 17 | */ 18 | public class MonkeyApplication extends Application { 19 | @Override 20 | public void onCreate() { 21 | super.onCreate(); 22 | 23 | if (BuildConfig.DEBUG) { 24 | Timber.plant(new Timber.DebugTree()); 25 | } 26 | 27 | initComponent(); 28 | initStetho(); 29 | initUpgrade(); 30 | } 31 | 32 | private void initComponent() { 33 | if (BuildConfig.DEBUG) { 34 | Timber.d("dagger inject"); 35 | } 36 | mApplicationComponent = DaggerApplicationComponent.builder() 37 | .applicationModule(new ApplicationModule(this)) 38 | .build(); 39 | mApplicationComponent.inject(this); 40 | } 41 | 42 | private void initStetho() { 43 | Stetho.initializeWithDefaults(this); 44 | } 45 | 46 | private void initUpgrade() { 47 | Beta.autoInit = false; 48 | Beta.autoCheckUpgrade = true; 49 | Beta.upgradeCheckPeriod = 60 * 1000; 50 | 51 | Bugly.init(this, BuildConfig.BUGLY_APPID, false); 52 | } 53 | 54 | public static MonkeyApplication get(final Context context) { 55 | return (MonkeyApplication) context.getApplicationContext(); 56 | } 57 | 58 | public ApplicationComponent getComponent() { 59 | return mApplicationComponent; 60 | } 61 | 62 | ApplicationComponent mApplicationComponent; 63 | } 64 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/data/local/DatabaseHelper.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.data.local; 2 | 3 | import android.content.ContentValues; 4 | import android.database.Cursor; 5 | 6 | import com.squareup.sqlbrite.BriteDatabase; 7 | import com.squareup.sqlbrite.SqlBrite; 8 | import com.yeungeek.monkeyandroid.data.model.Repo; 9 | 10 | import java.util.List; 11 | 12 | import javax.inject.Inject; 13 | import javax.inject.Singleton; 14 | 15 | import rx.Observable; 16 | import rx.Subscriber; 17 | 18 | /** 19 | * Created by yeungeek on 2016/1/18. 20 | */ 21 | @Singleton 22 | public class DatabaseHelper { 23 | private final BriteDatabase mDb; 24 | 25 | @Inject 26 | public DatabaseHelper(DbOpenHelper dbOpenHelper) { 27 | mDb = SqlBrite.create().wrapDatabaseHelper(dbOpenHelper); 28 | } 29 | 30 | public BriteDatabase getBriteDb() { 31 | return mDb; 32 | } 33 | 34 | /** 35 | * Repo 36 | */ 37 | public Observable> addRepos(final List repos) { 38 | return Observable.create(new Observable.OnSubscribe>() { 39 | @Override 40 | public void call(Subscriber> subscriber) { 41 | BriteDatabase.Transaction transaction = mDb.newTransaction(); 42 | try { 43 | //clear 44 | mDb.delete(Db.RepoTable.TABLE_NAME,null); 45 | 46 | for (Repo repo : repos) { 47 | ContentValues contentValues = Db.RepoTable.toContentValues(repo); 48 | mDb.insert(Db.RepoTable.TABLE_NAME, contentValues); 49 | } 50 | 51 | transaction.markSuccessful(); 52 | // subscriber.onCompleted(); 53 | subscriber.onNext(repos); 54 | subscriber.onCompleted(); 55 | } finally { 56 | transaction.end(); 57 | } 58 | } 59 | }); 60 | } 61 | 62 | /** 63 | * Remove all the data from all the tables in the database. 64 | */ 65 | public Observable clearTables() { 66 | return Observable.create(new Observable.OnSubscribe() { 67 | @Override 68 | public void call(Subscriber subscriber) { 69 | BriteDatabase.Transaction transaction = mDb.newTransaction(); 70 | try { 71 | Cursor cursor = mDb.query("SELECT name FROM sqlite_master WHERE type='table'"); 72 | while (cursor.moveToNext()) { 73 | mDb.delete(cursor.getString(cursor.getColumnIndex("name")), null); 74 | } 75 | 76 | cursor.close(); 77 | transaction.markSuccessful(); 78 | subscriber.onCompleted(); 79 | } finally { 80 | transaction.end(); 81 | } 82 | } 83 | }); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/data/local/Db.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.data.local; 2 | 3 | import android.content.ContentValues; 4 | import android.database.Cursor; 5 | 6 | import com.yeungeek.monkeyandroid.data.model.Repo; 7 | 8 | /** 9 | * Created by yeungeek on 2016/1/18. 10 | */ 11 | public class Db { 12 | public Db() { 13 | } 14 | 15 | private static class BaseTable { 16 | public static final String COLUMN_INDEX = "_index"; 17 | } 18 | 19 | public static final class RepoTable extends BaseTable { 20 | public static final String TABLE_NAME = "repo"; 21 | 22 | public static final String COLUMN_ID = "_id"; 23 | public static final String COLUMN_NAME = "_name"; 24 | public static final String COLUMN_DESC = "_desc"; 25 | public static final String COLUMN_OWERID = "_owerId"; 26 | 27 | public static final String CREATE = 28 | "CREATE TABLE " + TABLE_NAME + " (" 29 | + COLUMN_INDEX + " INTEGER PRIMARY KEY AUTOINCREMENT," 30 | + COLUMN_ID + " INTEGER NOT NULL," 31 | + COLUMN_NAME + " TEXT," 32 | + COLUMN_DESC + " TEXT," 33 | + COLUMN_OWERID + " INTEGER" 34 | + " );"; 35 | 36 | public static ContentValues toContentValues(Repo repo) { 37 | ContentValues values = new ContentValues(); 38 | values.put(COLUMN_ID, repo.getId()); 39 | values.put(COLUMN_NAME, repo.getName()); 40 | values.put(COLUMN_DESC, repo.getDescription()); 41 | return values; 42 | } 43 | 44 | public static Repo parseCursor(Cursor cursor) { 45 | Repo repo = new Repo(); 46 | repo.setId(cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_ID))); 47 | repo.setName(cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_NAME))); 48 | repo.setDescription(cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_DESC))); 49 | return repo; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/data/local/DbOpenHelper.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.data.local; 2 | 3 | import android.content.Context; 4 | import android.database.sqlite.SQLiteDatabase; 5 | import android.database.sqlite.SQLiteOpenHelper; 6 | 7 | import com.yeungeek.monkeyandroid.injection.ApplicationContext; 8 | 9 | import javax.inject.Inject; 10 | 11 | /** 12 | * Created by yeungeek on 2016/1/18. 13 | */ 14 | public class DbOpenHelper extends SQLiteOpenHelper { 15 | public static final String DATABASE_NAME = "monkey_app.db"; 16 | public static final int DATABASE_VERSION = 1; 17 | 18 | @Inject 19 | public DbOpenHelper(@ApplicationContext Context context) { 20 | super(context, DATABASE_NAME, null, DATABASE_VERSION); 21 | } 22 | 23 | @Override 24 | public void onCreate(SQLiteDatabase db) { 25 | db.beginTransaction(); 26 | try { 27 | db.execSQL(Db.RepoTable.CREATE); 28 | db.setTransactionSuccessful(); 29 | } finally { 30 | db.endTransaction(); 31 | } 32 | } 33 | 34 | @Override 35 | public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 36 | 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/data/local/LanguageHelper.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.data.local; 2 | 3 | import android.content.Context; 4 | 5 | import com.google.gson.Gson; 6 | import com.yeungeek.monkeyandroid.data.model.Language; 7 | import com.yeungeek.monkeyandroid.injection.ApplicationContext; 8 | 9 | import java.io.BufferedReader; 10 | import java.io.InputStream; 11 | import java.io.InputStreamReader; 12 | import java.util.ArrayList; 13 | import java.util.HashMap; 14 | import java.util.List; 15 | 16 | import javax.inject.Inject; 17 | import javax.inject.Singleton; 18 | 19 | /** 20 | * Created by yeungeek on 2016/3/30. 21 | */ 22 | @Singleton 23 | public class LanguageHelper { 24 | private HashMap languageMap = new HashMap<>(); 25 | private List selectedLanguages = new ArrayList<>(); 26 | private Language[] allLanguages; 27 | private Context mContext; 28 | private Gson mGson; 29 | 30 | @Inject 31 | public LanguageHelper(@ApplicationContext Context context, Gson gson) { 32 | mContext = context; 33 | mGson = gson; 34 | selectedLanguages.addAll(getDefaultLanguage()); 35 | } 36 | 37 | public Language getAllLanguage() { 38 | return getLanguageByName("All Language"); 39 | } 40 | 41 | public List getLanguage() { 42 | return selectedLanguages; 43 | } 44 | 45 | private List getDefaultLanguage() { 46 | String[] defaultLanguagesName = new String[]{"Java", "JavaScript", "Go", "CSS", "Objective-C", "Python", "Swift", "HTML"}; 47 | 48 | List defaultLanguages = new ArrayList<>(); 49 | for (String langNAme : defaultLanguagesName) { 50 | defaultLanguages.add(getLanguageByName(langNAme)); 51 | } 52 | return defaultLanguages; 53 | } 54 | 55 | public Language getLanguageByName(String languageName) { 56 | if (languageMap.size() == 0) { 57 | 58 | for (Language language : getAllLanguages()) { 59 | languageMap.put(language.name, language); 60 | } 61 | } 62 | 63 | return languageMap.get(languageName); 64 | } 65 | 66 | public Language[] getAllLanguages() { 67 | if (allLanguages != null) { 68 | return allLanguages; 69 | } 70 | 71 | try { 72 | StringBuilder buf = new StringBuilder(); 73 | 74 | InputStream inputStream = mContext.getAssets().open("langs.json"); 75 | BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8")); 76 | String str; 77 | while ((str = in.readLine()) != null) { 78 | buf.append(str); 79 | } 80 | in.close(); 81 | 82 | allLanguages = mGson.fromJson(buf.toString(), Language[].class); 83 | } catch (Exception e) { 84 | } 85 | return allLanguages; 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/data/local/PreferencesHelper.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.data.local; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | import android.support.annotation.Nullable; 6 | 7 | import com.yeungeek.monkeyandroid.injection.ApplicationContext; 8 | 9 | import javax.inject.Inject; 10 | import javax.inject.Singleton; 11 | 12 | /** 13 | * Created by yeungeek on 2016/3/13. 14 | */ 15 | @Singleton 16 | public class PreferencesHelper { 17 | public static final String PREF_FILE_NAME = "monkey_app_pref_file"; 18 | 19 | private static final String PREF_KEY_ACCESS_TOKEN = "PREF_KEY_ACCESS_TOKEN"; 20 | private static final String PREF_KEY_USER_LOGIN = "PREF_KEY_USER_LOGIN"; 21 | private static final String PREF_KEY_USER_EMAIL = "PREF_KEY_USER_EMAIL"; 22 | private static final String PREF_KEY_USER_AVATAR = "PREF_KEY_USER_AVATAR"; 23 | 24 | private final SharedPreferences mPref; 25 | 26 | @Inject 27 | public PreferencesHelper(@ApplicationContext Context context) { 28 | mPref = context.getSharedPreferences(PREF_FILE_NAME, Context.MODE_PRIVATE); 29 | } 30 | 31 | public void putAccessToken(String accessToken) { 32 | mPref.edit().putString(PREF_KEY_ACCESS_TOKEN, accessToken).apply(); 33 | } 34 | 35 | public void putUserLogin(String login) { 36 | mPref.edit().putString(PREF_KEY_USER_LOGIN, login).apply(); 37 | } 38 | 39 | public void putUserEmail(String email) { 40 | mPref.edit().putString(PREF_KEY_USER_EMAIL, email).apply(); 41 | } 42 | 43 | public void putUserAvatar(String avatar) { 44 | mPref.edit().putString(PREF_KEY_USER_AVATAR, avatar).apply(); 45 | } 46 | 47 | @Nullable 48 | public String getAccessToken() { 49 | return mPref.getString(PREF_KEY_ACCESS_TOKEN, null); 50 | } 51 | 52 | @Nullable 53 | public String getUserLogin() { 54 | return mPref.getString(PREF_KEY_USER_LOGIN, null); 55 | } 56 | 57 | @Nullable 58 | public String getUserEmail() { 59 | return mPref.getString(PREF_KEY_USER_EMAIL, null); 60 | } 61 | 62 | @Nullable 63 | public String getUserAvatar() { 64 | return mPref.getString(PREF_KEY_USER_AVATAR, null); 65 | } 66 | 67 | public void clear() { 68 | mPref.edit().clear().apply(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/data/meta/LibRxJava.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.data.meta; 2 | 3 | import com.snilius.aboutit.L; 4 | import com.snilius.aboutit.LibSkeleton; 5 | 6 | /** 7 | * Created by yeungeek on 2016/4/11. 8 | */ 9 | public class LibRxJava extends LibSkeleton { 10 | public LibRxJava() { 11 | super("RxJava", "ReactiveX", L.AP2, "https://github.com/ReactiveX/RxJava"); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/data/model/AccessTokenResp.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.data.model; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | /** 6 | * Created by yeungeek on 2016/3/13. 7 | * Accept: application/json 8 | * {"access_token":"e72e16c7e42f292c6912e7710c838347ae178b4a", "scope":"repo,gist", "token_type":"bearer"} 9 | */ 10 | public class AccessTokenResp { 11 | @SerializedName(value = "access_token") 12 | private String accessToken; 13 | private String scope; 14 | @SerializedName(value = "token_type") 15 | private String tokenType; 16 | 17 | public String getAccessToken() { 18 | return accessToken; 19 | } 20 | 21 | public AccessTokenResp setAccessToken(String accessToken) { 22 | this.accessToken = accessToken; 23 | return this; 24 | } 25 | 26 | public String getScope() { 27 | return scope; 28 | } 29 | 30 | public AccessTokenResp setScope(String scope) { 31 | this.scope = scope; 32 | return this; 33 | } 34 | 35 | public String getTokenType() { 36 | return tokenType; 37 | } 38 | 39 | public AccessTokenResp setTokenType(String tokenType) { 40 | this.tokenType = tokenType; 41 | return this; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/data/model/Language.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.data.model; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | import java.io.Serializable; 6 | 7 | 8 | public class Language implements Serializable { 9 | public String name; 10 | public String path; 11 | @SerializedName("short_name") 12 | private String shortName; 13 | 14 | public String getShortName() { 15 | return shortName == null ? name : shortName; 16 | } 17 | 18 | public Language() { 19 | } 20 | 21 | public Language(String name, String path) { 22 | this.name = name; 23 | this.path = path; 24 | } 25 | 26 | @Override 27 | public boolean equals(Object o) { 28 | if (o instanceof Language) { 29 | return name.equals(((Language) o).name); 30 | } 31 | return false; 32 | } 33 | 34 | @Override 35 | public int hashCode() { 36 | return name.hashCode(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/data/model/Pair.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.data.model; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * Created by yeungeek on 2016/4/19. 7 | */ 8 | public class Pair implements Serializable{ 9 | public final F first; 10 | public final S second; 11 | 12 | /** 13 | * Constructor for a Pair. 14 | * 15 | * @param first the first object in the Pair 16 | * @param second the second object in the pair 17 | */ 18 | public Pair(F first, S second) { 19 | this.first = first; 20 | this.second = second; 21 | } 22 | 23 | /** 24 | * Checks the two objects for equality by delegating to their respective 25 | * {@link Object#equals(Object)} methods. 26 | * 27 | * @param o the {@link Pair} to which this one is to be checked for equality 28 | * @return true if the underlying objects of the Pair are both considered 29 | * equal 30 | */ 31 | @Override 32 | public boolean equals(Object o) { 33 | if (!(o instanceof Pair)) { 34 | return false; 35 | } 36 | Pair p = (Pair) o; 37 | return objectsEqual(p.first, first) && objectsEqual(p.second, second); 38 | } 39 | 40 | private static boolean objectsEqual(Object a, Object b) { 41 | return a == b || (a != null && a.equals(b)); 42 | } 43 | 44 | /** 45 | * Compute a hash code using the hash codes of the underlying objects 46 | * 47 | * @return a hashcode of the Pair 48 | */ 49 | @Override 50 | public int hashCode() { 51 | return (first == null ? 0 : first.hashCode()) ^ (second == null ? 0 : second.hashCode()); 52 | } 53 | 54 | /** 55 | * Convenience method for creating an appropriately typed pair. 56 | * 57 | * @param a the first object in the Pair 58 | * @param b the second object in the pair 59 | * @return a Pair that is templatized with the types of a and b 60 | */ 61 | public static Pair create(A a, B b) { 62 | return new Pair(a, b); 63 | } 64 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/data/model/RepoContent.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.data.model; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * Created by yeungeek on 2016/4/13. 7 | */ 8 | public class RepoContent implements Serializable{ 9 | private String name; 10 | private String path; 11 | private String sha; 12 | private int size; 13 | private String url; 14 | private String html_url; 15 | private String git_url; 16 | private String file; 17 | private String content; 18 | private String encoding; 19 | //links 20 | 21 | public String getName() { 22 | return name; 23 | } 24 | 25 | public RepoContent setName(String name) { 26 | this.name = name; 27 | return this; 28 | } 29 | 30 | public String getPath() { 31 | return path; 32 | } 33 | 34 | public RepoContent setPath(String path) { 35 | this.path = path; 36 | return this; 37 | } 38 | 39 | public String getSha() { 40 | return sha; 41 | } 42 | 43 | public RepoContent setSha(String sha) { 44 | this.sha = sha; 45 | return this; 46 | } 47 | 48 | public int getSize() { 49 | return size; 50 | } 51 | 52 | public RepoContent setSize(int size) { 53 | this.size = size; 54 | return this; 55 | } 56 | 57 | public String getUrl() { 58 | return url; 59 | } 60 | 61 | public RepoContent setUrl(String url) { 62 | this.url = url; 63 | return this; 64 | } 65 | 66 | public String getHtml_url() { 67 | return html_url; 68 | } 69 | 70 | public RepoContent setHtml_url(String html_url) { 71 | this.html_url = html_url; 72 | return this; 73 | } 74 | 75 | public String getGit_url() { 76 | return git_url; 77 | } 78 | 79 | public RepoContent setGit_url(String git_url) { 80 | this.git_url = git_url; 81 | return this; 82 | } 83 | 84 | public String getFile() { 85 | return file; 86 | } 87 | 88 | public RepoContent setFile(String file) { 89 | this.file = file; 90 | return this; 91 | } 92 | 93 | public String getContent() { 94 | return content; 95 | } 96 | 97 | public RepoContent setContent(String content) { 98 | this.content = content; 99 | return this; 100 | } 101 | 102 | public String getEncoding() { 103 | return encoding; 104 | } 105 | 106 | public RepoContent setEncoding(String encoding) { 107 | this.encoding = encoding; 108 | return this; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/data/model/WrapList.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.data.model; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Created by yeungeek on 2016/4/3. 7 | */ 8 | public class WrapList { 9 | private List items; 10 | private int total_count; 11 | private boolean incomplete_results; 12 | 13 | public List getItems() { 14 | return items; 15 | } 16 | 17 | public WrapList setItems(List items) { 18 | this.items = items; 19 | return this; 20 | } 21 | 22 | public int getTotal_count() { 23 | return total_count; 24 | } 25 | 26 | public WrapList setTotal_count(int total_count) { 27 | this.total_count = total_count; 28 | return this; 29 | } 30 | 31 | public boolean isIncomplete_results() { 32 | return incomplete_results; 33 | } 34 | 35 | public WrapList setIncomplete_results(boolean incomplete_results) { 36 | this.incomplete_results = incomplete_results; 37 | return this; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/data/model/WrapUser.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.data.model; 2 | 3 | /** 4 | * Created by yeungeek on 2016/4/18. 5 | */ 6 | public class WrapUser extends User { 7 | //check if is followed 8 | private boolean isFollowed; 9 | 10 | public boolean isFollowed() { 11 | return isFollowed; 12 | } 13 | 14 | public WrapUser setFollowed(boolean followed) { 15 | isFollowed = followed; 16 | return this; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/data/remote/SimpleApi.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.data.remote; 2 | 3 | import retrofit2.http.Body; 4 | import retrofit2.http.Header; 5 | import retrofit2.http.Headers; 6 | import retrofit2.http.POST; 7 | import rx.Observable; 8 | 9 | /** 10 | * Created by yeungeek on 2016/4/14. 11 | */ 12 | public interface SimpleApi { 13 | @Headers({ 14 | "Content-Type: text/plain" 15 | }) 16 | @POST("markdown/raw") 17 | Observable markdown(@Body String readme); 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/data/remote/TokenInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.data.remote; 2 | 3 | import android.content.Context; 4 | import android.text.TextUtils; 5 | 6 | import com.yeungeek.monkeyandroid.MonkeyApplication; 7 | import com.yeungeek.monkeyandroid.data.local.PreferencesHelper; 8 | 9 | import java.io.IOException; 10 | 11 | import javax.inject.Inject; 12 | 13 | import okhttp3.Interceptor; 14 | import okhttp3.Request; 15 | import okhttp3.Response; 16 | 17 | /** 18 | * Created by yeungeek on 2016/4/19. 19 | */ 20 | public class TokenInterceptor implements Interceptor { 21 | @Inject 22 | PreferencesHelper preferencesHelper; 23 | 24 | public TokenInterceptor(Context context) { 25 | MonkeyApplication.get(context).getComponent().inject(this); 26 | } 27 | 28 | @Override 29 | public Response intercept(Chain chain) throws IOException { 30 | Request original = chain.request(); 31 | Request.Builder requestBuilder = original.newBuilder() 32 | .method(original.method(), original.body()); 33 | if (null != preferencesHelper && !TextUtils.isEmpty(preferencesHelper.getAccessToken())) { 34 | requestBuilder.header(GithubApi.AUTH_HEADER, GithubApi.AUTH_TOKEN + preferencesHelper.getAccessToken()); 35 | } 36 | 37 | Request request = requestBuilder.build(); 38 | return chain.proceed(request); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/data/remote/UnauthorisedInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.data.remote; 2 | 3 | import android.content.Context; 4 | 5 | import com.yeungeek.monkeyandroid.MonkeyApplication; 6 | import com.yeungeek.monkeyandroid.rxbus.RxBus; 7 | import com.yeungeek.monkeyandroid.rxbus.event.BusEvent; 8 | import com.yeungeek.monkeyandroid.util.HttpStatus; 9 | 10 | import java.io.IOException; 11 | 12 | import javax.inject.Inject; 13 | 14 | import okhttp3.Interceptor; 15 | import okhttp3.Response; 16 | import timber.log.Timber; 17 | 18 | /** 19 | * Created by yeungeek on 2016/4/8. 20 | */ 21 | public class UnauthorisedInterceptor implements Interceptor { 22 | @Inject 23 | RxBus rxBus; 24 | 25 | public UnauthorisedInterceptor(Context context) { 26 | MonkeyApplication.get(context).getComponent().inject(this); 27 | } 28 | 29 | @Override 30 | public Response intercept(Chain chain) throws IOException { 31 | Response response = chain.proceed(chain.request()); 32 | if (response.code() == HttpStatus.HTTP_UNAUTHORIZED) { 33 | Timber.e("### unauthorized"); 34 | rxBus.send(new BusEvent.AuthenticationError()); 35 | } 36 | return response; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/data/remote/retrofit2/StringConverterFactory.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.data.remote.retrofit2; 2 | 3 | import java.io.IOException; 4 | import java.lang.annotation.Annotation; 5 | import java.lang.reflect.Type; 6 | 7 | import okhttp3.MediaType; 8 | import okhttp3.RequestBody; 9 | import okhttp3.ResponseBody; 10 | import retrofit2.Converter; 11 | import retrofit2.Retrofit; 12 | 13 | /** 14 | * Created by yeungeek on 2016/4/14. 15 | */ 16 | public class StringConverterFactory extends Converter.Factory { 17 | @Override 18 | public Converter requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) { 19 | return new Converter() { 20 | @Override 21 | public RequestBody convert(String value) throws IOException { 22 | return RequestBody.create(MediaType.parse("text/plain"), value); 23 | } 24 | }; 25 | } 26 | 27 | @Override 28 | public Converter responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) { 29 | return new Converter() { 30 | @Override 31 | public String convert(ResponseBody value) throws IOException { 32 | return value.string(); 33 | } 34 | }; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/injection/ActivityContext.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.injection; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | 6 | import javax.inject.Qualifier; 7 | 8 | /** 9 | * Created by yeungeek on 2016/1/14. 10 | */ 11 | @Qualifier 12 | @Retention(RetentionPolicy.RUNTIME) 13 | public @interface ActivityContext { 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/injection/ApplicationContext.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.injection; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | 6 | import javax.inject.Qualifier; 7 | 8 | /** 9 | * Created by yeungeek on 2016/1/14. 10 | */ 11 | @Qualifier 12 | @Retention(RetentionPolicy.RUNTIME) 13 | public @interface ApplicationContext { 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/injection/PerActivity.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.injection; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | 6 | import javax.inject.Scope; 7 | 8 | /** 9 | * A scoping annotation to permit objects whose lifetime should 10 | * conform to the life of the DataManager to be memorised in the 11 | * correct component. 12 | */ 13 | 14 | @Scope 15 | @Retention(RetentionPolicy.RUNTIME) 16 | public @interface PerActivity { 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/injection/component/ActivityComponent.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.injection.component; 2 | 3 | import com.yeungeek.monkeyandroid.injection.PerActivity; 4 | import com.yeungeek.monkeyandroid.injection.module.ActivityModule; 5 | import com.yeungeek.monkeyandroid.ui.AboutActivity; 6 | import com.yeungeek.monkeyandroid.ui.base.view.BaseToolbarFragment; 7 | import com.yeungeek.monkeyandroid.ui.detail.FollowUserFragment; 8 | import com.yeungeek.monkeyandroid.ui.detail.RepoDetailFragment; 9 | import com.yeungeek.monkeyandroid.ui.detail.UserDetailFragment; 10 | import com.yeungeek.monkeyandroid.ui.main.MainActivity; 11 | import com.yeungeek.monkeyandroid.ui.repos.RepoListFragment; 12 | import com.yeungeek.monkeyandroid.ui.signin.SignInDialogFragment; 13 | import com.yeungeek.monkeyandroid.ui.trending.TrendingListFragment; 14 | import com.yeungeek.monkeyandroid.ui.users.UserListFragment; 15 | 16 | import dagger.Component; 17 | 18 | /** 19 | * Created by yeungeek on 2016/1/17. 20 | */ 21 | @PerActivity 22 | @Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class) 23 | public interface ActivityComponent { 24 | 25 | void inject(MainActivity mainActivity); 26 | 27 | void inject(AboutActivity aboutActivity); 28 | 29 | void inject(SignInDialogFragment signInDialogFragment); 30 | 31 | void inject(BaseToolbarFragment baseToolbarFragment); 32 | 33 | void inject(RepoListFragment repoListFragment); 34 | 35 | void inject(FollowUserFragment followUserFragment); 36 | 37 | void inject(RepoDetailFragment repoDetailFragment); 38 | 39 | void inject(UserDetailFragment userDetailFragment); 40 | 41 | void inject(UserListFragment userListFragment); 42 | 43 | void inject(TrendingListFragment trendingListFragment); 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/injection/component/ApplicationComponent.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.injection.component; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | 6 | import com.google.gson.Gson; 7 | import com.yeungeek.monkeyandroid.MonkeyApplication; 8 | import com.yeungeek.monkeyandroid.data.DataManager; 9 | import com.yeungeek.monkeyandroid.data.local.DatabaseHelper; 10 | import com.yeungeek.monkeyandroid.data.local.PreferencesHelper; 11 | import com.yeungeek.monkeyandroid.data.remote.GithubApi; 12 | import com.yeungeek.monkeyandroid.data.remote.SimpleApi; 13 | import com.yeungeek.monkeyandroid.data.remote.TokenInterceptor; 14 | import com.yeungeek.monkeyandroid.data.remote.UnauthorisedInterceptor; 15 | import com.yeungeek.monkeyandroid.injection.ApplicationContext; 16 | import com.yeungeek.monkeyandroid.injection.module.ApplicationModule; 17 | import com.yeungeek.monkeyandroid.rxbus.RxBus; 18 | 19 | import javax.inject.Singleton; 20 | 21 | import dagger.Component; 22 | 23 | @Singleton 24 | @Component(modules = ApplicationModule.class) 25 | public interface ApplicationComponent { 26 | 27 | void inject(MonkeyApplication monkeyApplication); 28 | void inject(UnauthorisedInterceptor unauthorisedInterceptor); 29 | void inject(TokenInterceptor tokenInterceptor); 30 | 31 | //Exposed to sub-graphs. 32 | @ApplicationContext 33 | Context context(); 34 | 35 | Application application(); 36 | 37 | GithubApi githubApi(); 38 | SimpleApi simpleApi(); 39 | 40 | DatabaseHelper databaseHelper(); 41 | 42 | PreferencesHelper preferencesHelper(); 43 | 44 | DataManager dataManager(); 45 | 46 | RxBus rxBus(); 47 | 48 | Gson gson(); 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/injection/component/ReposComponent.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.injection.component; 2 | 3 | import com.yeungeek.monkeyandroid.ui.base.view.BaseToolbarFragment; 4 | 5 | /** 6 | * Created by yeungeek on 2016/1/18. 7 | */ 8 | public interface ReposComponent { 9 | void inject(BaseToolbarFragment baseToolbarFragment); 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/injection/module/ActivityModule.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.injection.module; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | 6 | import com.yeungeek.monkeyandroid.injection.ActivityContext; 7 | 8 | import dagger.Module; 9 | import dagger.Provides; 10 | 11 | /** 12 | * Created by yeungeek on 2016/1/17. 13 | */ 14 | @Module 15 | public class ActivityModule { 16 | private Activity mActivity; 17 | 18 | public ActivityModule(final Activity activity) { 19 | this.mActivity = activity; 20 | } 21 | 22 | /** 23 | * Expose the activity to dependents in the graph. 24 | */ 25 | @Provides 26 | Activity provideActivity() { 27 | return mActivity; 28 | } 29 | 30 | @Provides 31 | @ActivityContext 32 | Context providesContext(){ 33 | return mActivity; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/injection/module/ApplicationModule.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.injection.module; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | 6 | import com.facebook.stetho.okhttp3.StethoInterceptor; 7 | import com.google.gson.Gson; 8 | import com.yeungeek.monkeyandroid.BuildConfig; 9 | import com.yeungeek.monkeyandroid.data.remote.GithubApi; 10 | import com.yeungeek.monkeyandroid.data.remote.SimpleApi; 11 | import com.yeungeek.monkeyandroid.data.remote.TokenInterceptor; 12 | import com.yeungeek.monkeyandroid.data.remote.UnauthorisedInterceptor; 13 | import com.yeungeek.monkeyandroid.data.remote.retrofit2.StringConverterFactory; 14 | import com.yeungeek.monkeyandroid.injection.ApplicationContext; 15 | import com.yeungeek.monkeyandroid.rxbus.RxBus; 16 | 17 | import javax.inject.Singleton; 18 | 19 | import dagger.Module; 20 | import dagger.Provides; 21 | import okhttp3.OkHttpClient; 22 | import okhttp3.logging.HttpLoggingInterceptor; 23 | import retrofit2.Retrofit; 24 | import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory; 25 | import retrofit2.converter.gson.GsonConverterFactory; 26 | 27 | /** 28 | * Created by yeungeek on 2016/1/14. 29 | */ 30 | @Module 31 | public class ApplicationModule { 32 | protected final Application mApplication; 33 | 34 | public ApplicationModule(final Application application) { 35 | this.mApplication = application; 36 | } 37 | 38 | @Provides 39 | Application provideApplication() { 40 | return mApplication; 41 | } 42 | 43 | @Provides 44 | @ApplicationContext 45 | Context provideContext() { 46 | return mApplication; 47 | } 48 | 49 | @Provides 50 | @Singleton 51 | GithubApi provideGithubApi(Retrofit retrofit) { 52 | return retrofit.create(GithubApi.class); 53 | } 54 | 55 | @Provides 56 | @Singleton 57 | SimpleApi provideSimpleApi(OkHttpClient okHttpClient) { 58 | Retrofit retrofit = new Retrofit.Builder() 59 | .baseUrl(GithubApi.ENDPOINT) 60 | .client(okHttpClient) 61 | .addConverterFactory(new StringConverterFactory()) 62 | .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) 63 | .build(); 64 | 65 | return retrofit.create(SimpleApi.class); 66 | } 67 | 68 | @Provides 69 | @Singleton 70 | OkHttpClient provideOkHttpClient() { 71 | final OkHttpClient.Builder builder = new OkHttpClient.Builder(); 72 | HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); 73 | logging.setLevel(BuildConfig.DEBUG ? HttpLoggingInterceptor.Level.BODY : HttpLoggingInterceptor.Level.NONE); 74 | 75 | //@see https://github.com/square/okhttp/blob/master/okhttp-logging-interceptor 76 | final OkHttpClient okHttpClient = builder.addInterceptor(logging) 77 | .addInterceptor(new UnauthorisedInterceptor(mApplication)) 78 | .addInterceptor(new TokenInterceptor(mApplication)) 79 | .addNetworkInterceptor(new StethoInterceptor()) 80 | .build(); 81 | return okHttpClient; 82 | } 83 | 84 | @Provides 85 | @Singleton 86 | Retrofit provideRetrofit(OkHttpClient okHttpClient) { 87 | Retrofit retrofit = new Retrofit.Builder() 88 | .baseUrl(GithubApi.ENDPOINT) 89 | .client(okHttpClient) 90 | .addConverterFactory(GsonConverterFactory.create()) 91 | .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) 92 | .build(); 93 | return retrofit; 94 | } 95 | 96 | @Provides 97 | @Singleton 98 | RxBus provideRxBus() { 99 | return new RxBus(); 100 | } 101 | 102 | @Provides 103 | @Singleton 104 | Gson provideGson() { 105 | return new Gson(); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/rxbus/RxBus.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.rxbus; 2 | 3 | import rx.Observable; 4 | import rx.subjects.PublishSubject; 5 | import rx.subjects.SerializedSubject; 6 | import rx.subjects.Subject; 7 | 8 | /** 9 | * Created by yeungeek on 2016/3/11. 10 | * 11 | * @see https://gist.github.com/benjchristensen/04eef9ca0851f3a5d7bf 12 | */ 13 | public class RxBus { 14 | private final Subject bus = new SerializedSubject<>(PublishSubject.create()); 15 | 16 | public void send(final Object o){ 17 | bus.onNext(o); 18 | } 19 | 20 | public Observable toObservable(){ 21 | return bus; 22 | } 23 | 24 | public boolean hasObservers(){ 25 | return bus.hasObservers(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/rxbus/event/BusEvent.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.rxbus.event; 2 | 3 | /** 4 | * Created by yeungeek on 2016/3/13. 5 | */ 6 | public class BusEvent { 7 | public static class SignedOut{} 8 | public static class AuthenticationError {} 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/rxbus/event/SignInEvent.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.rxbus.event; 2 | 3 | import android.net.Uri; 4 | 5 | /** 6 | * Created by yeungeek on 2016/3/12. 7 | */ 8 | public class SignInEvent { 9 | private Uri uri; 10 | public SignInEvent(final Uri uri){ 11 | this.uri = uri; 12 | } 13 | 14 | public Uri getUri() { 15 | return uri; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/ui/LauncherActivity.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.ui; 2 | 3 | import com.viksaa.sssplash.lib.activity.AwesomeSplash; 4 | import com.viksaa.sssplash.lib.model.ConfigSplash; 5 | import com.yeungeek.monkeyandroid.R; 6 | import com.yeungeek.monkeyandroid.ui.main.MainActivity; 7 | import com.yeungeek.monkeyandroid.util.AppCst; 8 | 9 | /** 10 | * Created by yeungeek on 2016/3/16. 11 | */ 12 | public class LauncherActivity extends AwesomeSplash { 13 | 14 | @Override 15 | public void initSplash(ConfigSplash configSplash) { 16 | //Title 17 | configSplash.setTitleSplash(getString(R.string.title_app)); 18 | configSplash.setBackgroundColor(R.color.colorPrimary); 19 | configSplash.setTitleTextSize(30f); 20 | 21 | //path 22 | configSplash.setPathSplash(AppCst.GITHUB); //set path String 23 | configSplash.setOriginalHeight(512); //in relation to your svg (path) resource 24 | configSplash.setOriginalWidth(512); //in relation to your svg (path) resource 25 | configSplash.setAnimPathStrokeDrawingDuration(2000); 26 | configSplash.setPathSplashStrokeSize(3); //I advise value be <5 27 | configSplash.setPathSplashStrokeColor(R.color.colorAccent); //any color you want form colors.xml 28 | configSplash.setAnimPathFillingDuration(2500); 29 | configSplash.setPathSplashFillColor(R.color.blue_200); //path object filling color 30 | } 31 | 32 | @Override 33 | public void animationsFinished() { 34 | startActivity(MainActivity.getStartIntent(this)); 35 | finish(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/ui/base/adapter/ExStaggeredGridLayoutManager.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.ui.base.adapter; 2 | 3 | import android.support.v7.widget.GridLayoutManager; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.support.v7.widget.StaggeredGridLayoutManager; 6 | import android.view.View; 7 | 8 | /** 9 | * Created by cundong on 2015/10/9. 10 | *

11 | * 拓展的StaggeredGridLayoutManager,tks @Jack Tony 12 | */ 13 | public class ExStaggeredGridLayoutManager extends StaggeredGridLayoutManager { 14 | 15 | private final String TAG = getClass().getSimpleName(); 16 | 17 | GridLayoutManager.SpanSizeLookup mSpanSizeLookup; 18 | 19 | public ExStaggeredGridLayoutManager(int spanCount, int orientation) { 20 | super(spanCount, orientation); 21 | } 22 | 23 | /** 24 | * Returns the current used by the GridLayoutManager. 25 | * 26 | * @return The current used by the GridLayoutManager. 27 | */ 28 | public GridLayoutManager.SpanSizeLookup getSpanSizeLookup() { 29 | return mSpanSizeLookup; 30 | } 31 | 32 | /** 33 | * 设置某个位置的item的跨列程度,这里和GridLayoutManager有点不一样, 34 | * 如果你设置某个位置的item的span>1了,那么这个item会占据所有列 35 | * 36 | * @param spanSizeLookup instance to be used to query number of spans 37 | * occupied by each item 38 | */ 39 | public void setSpanSizeLookup(GridLayoutManager.SpanSizeLookup spanSizeLookup) { 40 | mSpanSizeLookup = spanSizeLookup; 41 | } 42 | 43 | @Override 44 | public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) { 45 | //Log.d(TAG, "item count = " + getItemCount()); 46 | for (int i = 0; i < getItemCount(); i++) { 47 | 48 | if (mSpanSizeLookup.getSpanSize(i) > 1) { 49 | //Log.d(TAG, "lookup > 1 = " + i); 50 | try { 51 | //fix 动态添加时报IndexOutOfBoundsException 52 | View view = recycler.getViewForPosition(i); 53 | if (view != null) { 54 | /** 55 | *占用所有的列 56 | * @see https://plus.google.com/+EtienneLawlor/posts/c5T7fu9ujqi 57 | */ 58 | StaggeredGridLayoutManager.LayoutParams lp = (StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams(); 59 | lp.setFullSpan(true); 60 | } 61 | // recycler.recycleView(view); 62 | } catch (Exception e) { 63 | e.printStackTrace(); 64 | } 65 | } 66 | } 67 | super.onMeasure(recycler, state, widthSpec, heightSpec); 68 | } 69 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/ui/base/adapter/HeaderSpanSizeLookup.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.ui.base.adapter; 2 | 3 | import android.support.v7.widget.GridLayoutManager; 4 | 5 | /** 6 | * Created by cundong on 2015/10/23. 7 | *

8 | * RecyclerView为GridLayoutManager时,设置了HeaderView,就会用到这个SpanSizeLookup 9 | */ 10 | public class HeaderSpanSizeLookup extends GridLayoutManager.SpanSizeLookup { 11 | 12 | private HeaderAndFooterRecyclerViewAdapter adapter; 13 | private int mSpanSize = 1; 14 | 15 | public HeaderSpanSizeLookup(HeaderAndFooterRecyclerViewAdapter adapter, int spanSize) { 16 | this.adapter = adapter; 17 | this.mSpanSize = spanSize; 18 | } 19 | 20 | @Override 21 | public int getSpanSize(int position) { 22 | boolean isHeaderOrFooter = adapter.isHeader(position) || adapter.isFooter(position); 23 | return isHeaderOrFooter ? mSpanSize : 1; 24 | } 25 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/ui/base/adapter/OnListLoadNextPageListener.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.ui.base.adapter; 2 | 3 | import android.view.View; 4 | 5 | /** 6 | * Created by cundong on 2015/10/9. 7 | * RecyclerView/ListView/GridView 滑动加载下一页时的回调接口 8 | */ 9 | public interface OnListLoadNextPageListener { 10 | 11 | /** 12 | * 开始加载下一页 13 | * 14 | * @param view 当前RecyclerView/ListView/GridView 15 | */ 16 | public void onLoadNextPage(View view); 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/ui/base/presenter/MvpLceRxPresenter.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.ui.base.presenter; 2 | 3 | import com.fernandocejas.frodo.annotation.RxLogSubscriber; 4 | import com.yeungeek.mvp.common.MvpBasePresenter; 5 | import com.yeungeek.mvp.common.MvpPresenter; 6 | import com.yeungeek.mvp.common.lce.MvpLceView; 7 | 8 | import rx.Observable; 9 | import rx.Subscriber; 10 | import rx.android.schedulers.AndroidSchedulers; 11 | import rx.schedulers.Schedulers; 12 | 13 | /** 14 | * A presenter for RxJava, that assumes that only one Observable is subscribed by this presenter. 15 | * The idea is, that you make your (chain of) Observable and pass it to {@link 16 | * #subscribe(Observable, boolean)}. The presenter internally subscribes himself as Subscriber to 17 | * the 18 | * observable 19 | * (which executes the observable). 20 | * 21 | * @author Hannes Dorfmann 22 | * @since 1.0.0 23 | */ 24 | public abstract class MvpLceRxPresenter, M> 25 | extends MvpBasePresenter implements MvpPresenter { 26 | 27 | protected Subscriber subscriber; 28 | 29 | /** 30 | * Unsubscribes the subscriber and set it to null 31 | */ 32 | protected void unsubscribe() { 33 | if (null != subscriber && !subscriber.isUnsubscribed()) { 34 | subscriber.unsubscribe(); 35 | } 36 | 37 | subscriber = null; 38 | } 39 | 40 | /** 41 | * Subscribes the presenter himself as subscriber on the observable 42 | * 43 | * @param observable The observable to subscribe 44 | * @param pullToRefresh Pull to refresh? 45 | */ 46 | public void subscribe(final Observable observable, final boolean pullToRefresh) { 47 | if (isViewAttached()) { 48 | getView().showLoading(pullToRefresh); 49 | } 50 | 51 | unsubscribe(); 52 | 53 | subscriber = new RxSubscriber(pullToRefresh); 54 | 55 | observable.subscribeOn(Schedulers.io()) 56 | .observeOn(AndroidSchedulers.mainThread()) 57 | .subscribe(subscriber); 58 | } 59 | 60 | @RxLogSubscriber 61 | private class RxSubscriber extends Subscriber { 62 | final private boolean ptr; 63 | 64 | public RxSubscriber(final boolean pullToRefresh) { 65 | this.ptr = pullToRefresh; 66 | } 67 | 68 | @Override 69 | public void onCompleted() { 70 | MvpLceRxPresenter.this.onCompleted(); 71 | } 72 | 73 | @Override 74 | public void onError(Throwable e) { 75 | MvpLceRxPresenter.this.onError(e, ptr); 76 | } 77 | 78 | @Override 79 | public void onNext(M m) { 80 | MvpLceRxPresenter.this.onNext(m); 81 | } 82 | } 83 | 84 | protected void onCompleted() { 85 | if (isViewAttached()) { 86 | getView().showContent(); 87 | } 88 | unsubscribe(); 89 | } 90 | 91 | protected void onError(Throwable e, boolean pullToRefresh) { 92 | if (isViewAttached()) { 93 | getView().showError(e, pullToRefresh); 94 | } 95 | unsubscribe(); 96 | } 97 | 98 | protected void onNext(M data) { 99 | if (isViewAttached()) { 100 | getView().setData(data); 101 | } 102 | } 103 | 104 | @Override 105 | public void detachView(boolean retainInstance) { 106 | super.detachView(retainInstance); 107 | if (!retainInstance) { 108 | unsubscribe(); 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/ui/base/view/BaseActivity.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.ui.base.view; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.view.View; 7 | 8 | import com.yeungeek.monkeyandroid.MonkeyApplication; 9 | import com.yeungeek.monkeyandroid.injection.component.ActivityComponent; 10 | import com.yeungeek.monkeyandroid.injection.component.ApplicationComponent; 11 | import com.yeungeek.monkeyandroid.injection.component.DaggerActivityComponent; 12 | import com.yeungeek.monkeyandroid.injection.module.ActivityModule; 13 | 14 | import butterknife.ButterKnife; 15 | 16 | /** 17 | * Created by yeungeek on 2016/1/8. 18 | */ 19 | public abstract class BaseActivity extends AppCompatActivity implements View.OnClickListener{ 20 | @Override 21 | protected void onCreate(@Nullable Bundle savedInstanceState) { 22 | injectDependencies(); 23 | super.onCreate(savedInstanceState); 24 | } 25 | 26 | @Override 27 | public void onContentChanged() { 28 | super.onContentChanged(); 29 | ButterKnife.bind(this); 30 | } 31 | 32 | protected void injectDependencies() { 33 | 34 | } 35 | 36 | public ActivityComponent activityComponent() { 37 | if (null == mActivityComponent) { 38 | mActivityComponent = DaggerActivityComponent.builder() 39 | .activityModule(getActivityModule()) 40 | .applicationComponent(getApplicationComponent()) 41 | .build(); 42 | } 43 | return mActivityComponent; 44 | } 45 | 46 | protected ApplicationComponent getApplicationComponent() { 47 | return MonkeyApplication.get(this).getComponent(); 48 | } 49 | 50 | protected ActivityModule getActivityModule() { 51 | return new ActivityModule(this); 52 | } 53 | 54 | private ActivityComponent mActivityComponent; 55 | 56 | @Override 57 | public void onClick(View v) { 58 | 59 | } 60 | 61 | @Override 62 | protected void onDestroy() { 63 | super.onDestroy(); 64 | ButterKnife.unbind(this); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/ui/base/view/BaseFragment.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.ui.base.view; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.LayoutRes; 5 | import android.support.annotation.Nullable; 6 | import android.support.v4.app.Fragment; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | 11 | import butterknife.ButterKnife; 12 | 13 | /** 14 | * Created by yeungeek on 2016/1/10. 15 | */ 16 | public abstract class BaseFragment extends Fragment { 17 | @Override 18 | public void onCreate(@Nullable Bundle savedInstanceState) { 19 | super.onCreate(savedInstanceState); 20 | init(); 21 | } 22 | 23 | @Nullable 24 | @Override 25 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 26 | return inflater.inflate(getLayoutRes(), container, false); 27 | } 28 | 29 | @Override 30 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { 31 | injectDependencies(); 32 | super.onViewCreated(view, savedInstanceState); 33 | ButterKnife.bind(this, view); 34 | initViews(); 35 | } 36 | 37 | @Override 38 | public void onDestroyView() { 39 | super.onDestroyView(); 40 | ButterKnife.unbind(this); 41 | } 42 | 43 | @LayoutRes 44 | protected abstract int getLayoutRes(); 45 | 46 | /** 47 | * Inject the dependencies 48 | */ 49 | protected void injectDependencies() { 50 | 51 | } 52 | 53 | protected void init() { 54 | } 55 | 56 | protected void initViews() { 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/ui/base/view/BaseLceActivity.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.ui.base.view; 2 | 3 | import android.os.Bundle; 4 | import android.view.View; 5 | 6 | import com.yeungeek.monkeyandroid.MonkeyApplication; 7 | import com.yeungeek.monkeyandroid.injection.component.ActivityComponent; 8 | import com.yeungeek.monkeyandroid.injection.component.ApplicationComponent; 9 | import com.yeungeek.monkeyandroid.injection.component.DaggerActivityComponent; 10 | import com.yeungeek.monkeyandroid.injection.module.ActivityModule; 11 | import com.yeungeek.mvp.common.MvpPresenter; 12 | import com.yeungeek.mvp.common.lce.MvpLceView; 13 | import com.yeungeek.mvp.core.lce.MvpLceActivity; 14 | 15 | import butterknife.ButterKnife; 16 | 17 | /** 18 | * Created by yeungeek on 2016/1/10. 19 | */ 20 | public abstract class BaseLceActivity, P extends MvpPresenter> 21 | extends MvpLceActivity implements View.OnClickListener{ 22 | @Override 23 | public void onCreate(Bundle savedInstanceState) { 24 | injectDependencies(); 25 | super.onCreate(savedInstanceState); 26 | } 27 | 28 | @Override 29 | public void onContentChanged() { 30 | super.onContentChanged(); 31 | ButterKnife.bind(this); 32 | } 33 | 34 | @Override 35 | protected void onDestroy() { 36 | super.onDestroy(); 37 | ButterKnife.unbind(this); 38 | } 39 | 40 | @Override 41 | public void onClick(View v) { 42 | 43 | } 44 | 45 | /** 46 | * mvp 47 | */ 48 | @Override 49 | protected String getErrorMessage(Throwable e, boolean pullToRefresh) { 50 | return null; 51 | } 52 | 53 | @Override 54 | public void loadData(boolean pullToRefresh) { 55 | 56 | } 57 | 58 | protected void injectDependencies() { 59 | 60 | } 61 | 62 | public ActivityComponent activityComponent() { 63 | if (null == mActivityComponent) { 64 | mActivityComponent = DaggerActivityComponent.builder() 65 | .activityModule(getActivityModule()) 66 | .applicationComponent(getApplicationComponent()) 67 | .build(); 68 | } 69 | return mActivityComponent; 70 | } 71 | 72 | protected ApplicationComponent getApplicationComponent() { 73 | return MonkeyApplication.get(this).getComponent(); 74 | } 75 | 76 | protected ActivityModule getActivityModule() { 77 | return new ActivityModule(this); 78 | } 79 | 80 | private ActivityComponent mActivityComponent; 81 | } 82 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/ui/base/view/BaseLceFragment.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.ui.base.view; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.LayoutRes; 5 | import android.support.annotation.Nullable; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | 10 | import com.yeungeek.mvp.common.MvpPresenter; 11 | import com.yeungeek.mvp.common.lce.MvpLceView; 12 | import com.yeungeek.mvp.core.lce.MvpLceFragment; 13 | 14 | import butterknife.ButterKnife; 15 | 16 | /** 17 | * Created by yeungeek on 2016/1/10. 18 | */ 19 | public abstract class BaseLceFragment, P extends MvpPresenter> 20 | extends MvpLceFragment { 21 | @Override 22 | public void onCreate(Bundle savedInstanceState) { 23 | super.onCreate(savedInstanceState); 24 | init(); 25 | } 26 | 27 | @Nullable 28 | @Override 29 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 30 | return inflater.inflate(getLayoutRes(), container, false); 31 | } 32 | 33 | @Override 34 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { 35 | injectDependencies(); 36 | super.onViewCreated(view, savedInstanceState); 37 | ButterKnife.bind(this, view); 38 | initViews(); 39 | } 40 | 41 | @Override 42 | public void onDestroyView() { 43 | super.onDestroyView(); 44 | ButterKnife.unbind(this); 45 | } 46 | 47 | @LayoutRes 48 | protected abstract int getLayoutRes(); 49 | 50 | /** 51 | * Inject the dependencies 52 | */ 53 | protected void injectDependencies() { 54 | 55 | } 56 | 57 | protected void init() { 58 | } 59 | protected void initViews(){} 60 | } 61 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/ui/base/view/BaseToolbarFragment.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.ui.base.view; 2 | 3 | import android.support.design.widget.AppBarLayout; 4 | import android.support.design.widget.FloatingActionButton; 5 | import android.support.design.widget.TabLayout; 6 | import android.support.v4.view.ViewPager; 7 | import android.support.v7.app.ActionBar; 8 | import android.support.v7.app.AppCompatActivity; 9 | import android.support.v7.widget.Toolbar; 10 | 11 | import com.yeungeek.monkeyandroid.R; 12 | import com.yeungeek.monkeyandroid.data.DataManager; 13 | 14 | import javax.inject.Inject; 15 | 16 | import butterknife.Bind; 17 | import timber.log.Timber; 18 | 19 | /** 20 | * Created by yeungeek on 2016/3/29. 21 | */ 22 | public abstract class BaseToolbarFragment extends BaseFragment { 23 | @Bind(R.id.toolbar) 24 | Toolbar toolbar; 25 | @Bind(R.id.tabs) 26 | TabLayout tabLayout; 27 | @Bind(R.id.pager) 28 | ViewPager viewPager; 29 | @Bind(R.id.fab) 30 | FloatingActionButton floatingActionButton; 31 | @Bind(R.id.app_bar) 32 | AppBarLayout appbar; 33 | 34 | @Inject 35 | protected DataManager dataManager; 36 | protected ViewPager.OnPageChangeListener onPageChangeListener; 37 | protected int mCurrentPosition = 0; 38 | private ActionBar actionBar; 39 | 40 | protected int appbarOffset; 41 | 42 | @Override 43 | protected int getLayoutRes() { 44 | return R.layout.layout_appbar; 45 | } 46 | 47 | @Override 48 | protected void initViews() { 49 | super.initViews(); 50 | 51 | if (null != toolbar) { 52 | ((AppCompatActivity) getActivity()).setSupportActionBar(toolbar); 53 | actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar(); 54 | if (null != actionBar) { 55 | actionBar.setDisplayHomeAsUpEnabled(true); 56 | actionBar.setHomeAsUpIndicator(R.drawable.ic_menu); 57 | initToolbar(); 58 | } 59 | } 60 | 61 | getViewPager().addOnPageChangeListener(onPageChangeListener = new ViewPager.OnPageChangeListener() { 62 | @Override 63 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 64 | 65 | } 66 | 67 | @Override 68 | public void onPageSelected(int position) { 69 | Timber.d("### onItemSelected onPageSelected position: %d", position); 70 | mCurrentPosition = position; 71 | } 72 | 73 | @Override 74 | public void onPageScrollStateChanged(int state) { 75 | 76 | } 77 | }); 78 | 79 | appbar.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() { 80 | @Override 81 | public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) { 82 | Timber.d("### onOffsetChanged verticalOffset:%s,", verticalOffset); 83 | appbarOffset = verticalOffset; 84 | } 85 | }); 86 | } 87 | 88 | @Override 89 | protected void injectDependencies() { 90 | super.injectDependencies(); 91 | if (getActivity() instanceof BaseLceActivity) { 92 | ((BaseLceActivity) getActivity()).activityComponent().inject(this); 93 | } 94 | } 95 | 96 | protected abstract void initToolbar(); 97 | 98 | public ActionBar getActionBar() { 99 | return actionBar; 100 | } 101 | 102 | public Toolbar getToolbar() { 103 | return toolbar; 104 | } 105 | 106 | public TabLayout getTabLayout() { 107 | return tabLayout; 108 | } 109 | 110 | public ViewPager getViewPager() { 111 | return viewPager; 112 | } 113 | 114 | public FloatingActionButton getFloatingActionButton() { 115 | return floatingActionButton; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/ui/detail/DetailActivity.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.ui.detail; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.support.annotation.Nullable; 7 | import android.support.v4.app.FragmentManager; 8 | import android.support.v4.app.FragmentTransaction; 9 | import android.view.MenuItem; 10 | 11 | import com.yeungeek.monkeyandroid.R; 12 | import com.yeungeek.monkeyandroid.data.model.Repo; 13 | import com.yeungeek.monkeyandroid.data.model.User; 14 | import com.yeungeek.monkeyandroid.ui.base.view.BaseActivity; 15 | 16 | import java.io.Serializable; 17 | 18 | /** 19 | * Created by yeungeek on 2016/4/10. 20 | */ 21 | public class DetailActivity extends BaseActivity { 22 | private static final String EXTRA_DETAIL = "EXTRA_DETAIL"; 23 | 24 | private Serializable serializable; 25 | 26 | public static Intent getStartIntent(Context context, Serializable serializable) { 27 | Intent intent = new Intent(context, DetailActivity.class); 28 | intent.putExtra(EXTRA_DETAIL, serializable); 29 | return intent; 30 | } 31 | 32 | @Override 33 | protected void onCreate(@Nullable Bundle savedInstanceState) { 34 | super.onCreate(savedInstanceState); 35 | setContentView(R.layout.activity_detail); 36 | 37 | serializable = getIntent().getSerializableExtra(EXTRA_DETAIL); 38 | initViews(); 39 | } 40 | 41 | @Override 42 | public boolean onOptionsItemSelected(MenuItem item) { 43 | int id = item.getItemId(); 44 | switch (id) { 45 | case android.R.id.home: 46 | finish(); 47 | return true; 48 | } 49 | return super.onOptionsItemSelected(item); 50 | } 51 | 52 | private void initViews() { 53 | FragmentManager fm = getSupportFragmentManager(); 54 | FragmentTransaction transaction = fm.beginTransaction(); 55 | 56 | if (null != serializable) { 57 | if (serializable instanceof Repo) { 58 | transaction.replace(R.id.id_detail, RepoDetailFragment.newInstance(this, (Repo) serializable)); 59 | } else if (serializable instanceof User) { 60 | transaction.replace(R.id.id_detail, UserDetailFragment.newInstance(this, (User) serializable)); 61 | } 62 | } 63 | 64 | transaction.commit(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/ui/detail/FollowUserFragment.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.ui.detail; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.support.annotation.Nullable; 6 | import android.support.v4.app.Fragment; 7 | import android.support.v7.widget.LinearLayoutManager; 8 | 9 | import com.yeungeek.monkeyandroid.data.model.Pair; 10 | import com.yeungeek.monkeyandroid.data.model.User; 11 | import com.yeungeek.monkeyandroid.data.model.WrapUser; 12 | import com.yeungeek.monkeyandroid.ui.base.adapter.HeaderAndFooterRecyclerViewAdapter; 13 | import com.yeungeek.monkeyandroid.ui.base.view.BaseActivity; 14 | import com.yeungeek.monkeyandroid.ui.base.view.BaseLceActivity; 15 | import com.yeungeek.monkeyandroid.ui.base.view.BasePageFragment; 16 | import com.yeungeek.monkeyandroid.ui.users.UserAdapter; 17 | import com.yeungeek.monkeyandroid.util.AppCst; 18 | 19 | import java.util.List; 20 | 21 | import javax.inject.Inject; 22 | 23 | /** 24 | * Created by yeungeek on 2016/4/18. 25 | */ 26 | public class FollowUserFragment extends BasePageFragment, FollowUserMvpView, FollowUserPresenter> implements FollowUserMvpView { 27 | @Inject 28 | FollowUserPresenter followUserPresenter; 29 | 30 | private Pair pair; 31 | 32 | private UserAdapter adapter; 33 | private HeaderAndFooterRecyclerViewAdapter mHeaderAdapter; 34 | 35 | public static Fragment newInstance(Context context, final Pair pair) { 36 | Bundle bundle = new Bundle(); 37 | bundle.putSerializable(AppCst.EXTRA_USER_TYPE, pair); 38 | return Fragment.instantiate(context, FollowUserFragment.class.getName(), bundle); 39 | } 40 | 41 | @Override 42 | public FollowUserPresenter createPresenter() { 43 | return followUserPresenter; 44 | } 45 | 46 | @Override 47 | public void onCreate(@Nullable Bundle savedInstanceState) { 48 | super.onCreate(savedInstanceState); 49 | pair = (Pair) getArguments().getSerializable(AppCst.EXTRA_USER_TYPE); 50 | } 51 | 52 | @Override 53 | protected void initAdapter() { 54 | adapter = new UserAdapter(getActivity()); 55 | mHeaderAdapter = new HeaderAndFooterRecyclerViewAdapter(adapter); 56 | recyclerView.setAdapter(mHeaderAdapter); 57 | recyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); 58 | } 59 | 60 | @Override 61 | protected String getErrorMessage(Throwable e, boolean pullToRefresh) { 62 | return e.getMessage(); 63 | } 64 | 65 | @Override 66 | public void setData(List data) { 67 | switch (pair.first) { 68 | case AppCst.TITLE_FOLLOWERS: 69 | mCount = pair.second.getFollowers(); 70 | break; 71 | case AppCst.TITLE_FOLLOWING: 72 | mCount = pair.second.getFollowing(); 73 | break; 74 | } 75 | 76 | if (!mLoadMore) { 77 | adapter.addTopAll(data); 78 | mCurrentSize = data.size(); 79 | } else { 80 | adapter.addAll(data); 81 | mCurrentSize += data.size(); 82 | } 83 | 84 | checkViewState(); 85 | } 86 | 87 | @Override 88 | public void loadData(boolean pullToRefresh) { 89 | getPresenter().getUsers(pair.first, pair.second.getLogin(), mPage, pullToRefresh); 90 | } 91 | 92 | @Override 93 | protected void injectDependencies() { 94 | super.injectDependencies(); 95 | if (getActivity() instanceof BaseActivity) { 96 | ((BaseActivity) getActivity()).activityComponent().inject(this); 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/ui/detail/FollowUserMvpView.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.ui.detail; 2 | 3 | import com.yeungeek.monkeyandroid.data.model.User; 4 | import com.yeungeek.mvp.common.lce.MvpLceView; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * Created by yeungeek on 2016/4/18. 10 | */ 11 | public interface FollowUserMvpView extends MvpLceView> { 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/ui/detail/FollowUserPresenter.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.ui.detail; 2 | 3 | import com.yeungeek.monkeyandroid.data.DataManager; 4 | import com.yeungeek.monkeyandroid.data.model.User; 5 | import com.yeungeek.monkeyandroid.ui.base.presenter.MvpLceRxPresenter; 6 | import com.yeungeek.monkeyandroid.util.AppCst; 7 | import com.yeungeek.mvp.common.MvpPresenter; 8 | 9 | import java.util.List; 10 | 11 | import javax.inject.Inject; 12 | 13 | import rx.Observable; 14 | 15 | /** 16 | * Created by yeungeek on 2016/4/18. 17 | */ 18 | public class FollowUserPresenter extends MvpLceRxPresenter> implements MvpPresenter { 19 | private final DataManager dataManager; 20 | 21 | @Inject 22 | public FollowUserPresenter(final DataManager dataManager) { 23 | this.dataManager = dataManager; 24 | } 25 | 26 | public void getUsers(final String type, final String username, final int page, final boolean pullToRefresh) { 27 | Observable> users = null; 28 | switch (type) { 29 | case AppCst.TITLE_FOLLOWING: 30 | users = dataManager.getFollowing(username, page); 31 | break; 32 | case AppCst.TITLE_FOLLOWERS: 33 | users = dataManager.getFollowers(username, page); 34 | break; 35 | } 36 | 37 | if (null == users) { 38 | return; 39 | } 40 | subscribe(users, pullToRefresh); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/ui/detail/RepoDetailMvpView.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.ui.detail; 2 | 3 | import com.yeungeek.mvp.common.lce.MvpLceView; 4 | 5 | /** 6 | * Created by yeungeek on 2016/4/13. 7 | */ 8 | public interface RepoDetailMvpView extends MvpLceView { 9 | void starStatus(boolean isStaring); 10 | void notLogined(); 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/ui/detail/UserDetailMvpView.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.ui.detail; 2 | 3 | import com.yeungeek.monkeyandroid.data.model.WrapUser; 4 | import com.yeungeek.mvp.common.lce.MvpLceView; 5 | 6 | /** 7 | * Created by yeungeek on 2016/4/13. 8 | */ 9 | public interface UserDetailMvpView extends MvpLceView { 10 | void followStatus(boolean isFollowing); 11 | void notLogined(); 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/ui/detail/UserDetailPresenter.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.ui.detail; 2 | 3 | import android.text.TextUtils; 4 | 5 | import com.fernandocejas.frodo.annotation.RxLogSubscriber; 6 | import com.yeungeek.monkeyandroid.data.DataManager; 7 | import com.yeungeek.monkeyandroid.data.model.WrapUser; 8 | import com.yeungeek.monkeyandroid.ui.base.presenter.MvpLceRxPresenter; 9 | import com.yeungeek.monkeyandroid.util.HttpStatus; 10 | import com.yeungeek.mvp.common.MvpPresenter; 11 | 12 | import javax.inject.Inject; 13 | 14 | import retrofit2.Response; 15 | import rx.Subscriber; 16 | import rx.android.schedulers.AndroidSchedulers; 17 | import rx.schedulers.Schedulers; 18 | 19 | /** 20 | * Created by yeungeek on 2016/4/18. 21 | */ 22 | public class UserDetailPresenter extends MvpLceRxPresenter implements MvpPresenter { 23 | private final DataManager dataManager; 24 | 25 | private FollowSubscriber mFollow; 26 | 27 | @Inject 28 | public UserDetailPresenter(final DataManager dataManager) { 29 | this.dataManager = dataManager; 30 | } 31 | 32 | public void getSingleUser(final String login, final boolean pullToRefresh) { 33 | subscribe(dataManager.getSingleUser(login), pullToRefresh); 34 | } 35 | 36 | public void followUser(final String login) { 37 | mFollow = new FollowSubscriber(true); 38 | dataManager.followUser(login) 39 | .subscribeOn(Schedulers.io()) 40 | .observeOn(AndroidSchedulers.mainThread()) 41 | .subscribe(mFollow); 42 | } 43 | 44 | public void unfollowUser(final String login) { 45 | mFollow = new FollowSubscriber(false); 46 | dataManager.unfollowUser(login) 47 | .subscribeOn(Schedulers.io()) 48 | .observeOn(AndroidSchedulers.mainThread()) 49 | .subscribe(mFollow); 50 | } 51 | 52 | @Override 53 | protected void unsubscribe() { 54 | super.unsubscribe(); 55 | if (null != mFollow && mFollow.isUnsubscribed()) { 56 | mFollow.unsubscribe(); 57 | } 58 | 59 | mFollow = null; 60 | } 61 | 62 | public boolean checkLogin() { 63 | if(TextUtils.isEmpty(dataManager.getPreferencesHelper().getAccessToken())){ 64 | // dataManager.getRxBus().send(new BusEvent.AuthenticationError()); 65 | getView().notLogined(); 66 | return false; 67 | } 68 | 69 | return true; 70 | } 71 | 72 | @RxLogSubscriber 73 | private class FollowSubscriber extends Subscriber> { 74 | private boolean isFollow; 75 | 76 | public FollowSubscriber(final boolean isFollow) { 77 | this.isFollow = isFollow; 78 | } 79 | 80 | @Override 81 | public void onCompleted() { 82 | if (isViewAttached()) { 83 | getView().showContent(); 84 | } 85 | unsubscribe(); 86 | } 87 | 88 | @Override 89 | public void onError(Throwable e) { 90 | if (isViewAttached()) { 91 | getView().showError(e, true); 92 | } 93 | unsubscribe(); 94 | } 95 | 96 | @Override 97 | public void onNext(Response response) { 98 | if (null != response && response.code() == HttpStatus.HTTP_NO_CONTENT) { 99 | if (isFollow) { 100 | getView().followStatus(true); 101 | } else { 102 | getView().followStatus(false); 103 | } 104 | } else { 105 | if (isFollow) { 106 | getView().followStatus(false); 107 | } else { 108 | getView().followStatus(true); 109 | } 110 | } 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/ui/main/MainMvpView.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.ui.main; 2 | 3 | import com.yeungeek.monkeyandroid.data.model.User; 4 | import com.yeungeek.mvp.common.lce.MvpLceView; 5 | 6 | /** 7 | * Created by yeungeek on 2016/3/13. 8 | */ 9 | public interface MainMvpView extends MvpLceView { 10 | void unauthorized(); 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/ui/main/MainPresenter.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.ui.main; 2 | 3 | import com.yeungeek.monkeyandroid.data.DataManager; 4 | import com.yeungeek.monkeyandroid.data.model.User; 5 | import com.yeungeek.monkeyandroid.rxbus.event.BusEvent; 6 | import com.yeungeek.monkeyandroid.rxbus.event.SignInEvent; 7 | import com.yeungeek.monkeyandroid.ui.base.presenter.MvpLceRxPresenter; 8 | import com.yeungeek.mvp.common.MvpPresenter; 9 | 10 | import javax.inject.Inject; 11 | 12 | import rx.android.schedulers.AndroidSchedulers; 13 | import rx.functions.Action1; 14 | import rx.schedulers.Schedulers; 15 | import rx.subscriptions.CompositeSubscription; 16 | import timber.log.Timber; 17 | 18 | /** 19 | * Created by yeungeek on 2016/3/12. 20 | */ 21 | public class MainPresenter extends MvpLceRxPresenter implements MvpPresenter { 22 | private final DataManager dataManager; 23 | private CompositeSubscription mSubscriptions; 24 | 25 | @Inject 26 | public MainPresenter(final DataManager dataManager) { 27 | this.dataManager = dataManager; 28 | } 29 | 30 | @Override 31 | public void attachView(MainMvpView view) { 32 | super.attachView(view); 33 | mSubscriptions = new CompositeSubscription(); 34 | mSubscriptions.add(dataManager.getRxBus().toObservable() 35 | .subscribeOn(Schedulers.io()) 36 | .observeOn(AndroidSchedulers.mainThread()) 37 | .subscribe(new Action1() { 38 | @Override 39 | public void call(Object o) { 40 | if (o instanceof SignInEvent) { 41 | SignInEvent event = (SignInEvent) o; 42 | if (null != event.getUri().getQueryParameter("code")) { 43 | String code = event.getUri().getQueryParameter("code"); 44 | Timber.d("### receive event code: %s", code); 45 | getAccessToken(code); 46 | } 47 | } else if (o instanceof BusEvent.AuthenticationError) { 48 | dataManager.clearWebCache(); 49 | getView().unauthorized(); 50 | } 51 | } 52 | })); 53 | } 54 | 55 | @Override 56 | public void detachView(boolean retainInstance) { 57 | super.detachView(retainInstance); 58 | mSubscriptions.unsubscribe(); 59 | } 60 | 61 | public void getAccessToken(String code) { 62 | subscribe(dataManager.getAccessToken(code), false); 63 | } 64 | 65 | public void checkUserStatus() { 66 | User user = null; 67 | if (null != dataManager.getPreferencesHelper().getAccessToken()) { 68 | //if token is exist, user is exist 69 | user = new User(); 70 | user.setLogin(dataManager.getPreferencesHelper().getUserLogin()); 71 | user.setEmail(dataManager.getPreferencesHelper().getUserEmail()); 72 | user.setAvatar_url(dataManager.getPreferencesHelper().getUserAvatar()); 73 | } 74 | 75 | getView().setData(user); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/ui/repos/HotRepoFragment.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.ui.repos; 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.view.View; 7 | 8 | import com.yeungeek.monkeyandroid.R; 9 | import com.yeungeek.monkeyandroid.data.model.Language; 10 | import com.yeungeek.monkeyandroid.ui.base.view.BasePageFragment; 11 | import com.yeungeek.monkeyandroid.ui.base.view.BaseToolbarFragment; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | /** 17 | * Created by yeungeek on 2016/3/29. 18 | */ 19 | public class HotRepoFragment extends BaseToolbarFragment { 20 | private LanguagesPagerAdapter mPagerAdapter; 21 | 22 | @Override 23 | protected void initToolbar() { 24 | getActionBar().setTitle(R.string.menu_title_repo); 25 | } 26 | 27 | @Override 28 | protected void initViews() { 29 | super.initViews(); 30 | 31 | mPagerAdapter = new LanguagesPagerAdapter(getChildFragmentManager()); 32 | getViewPager().setAdapter(mPagerAdapter); 33 | getTabLayout().setupWithViewPager(getViewPager()); 34 | 35 | getFloatingActionButton().setOnClickListener(new View.OnClickListener() { 36 | @Override 37 | public void onClick(View v) { 38 | Fragment fragment = getChildFragmentManager().findFragmentByTag(mPagerAdapter.getFragmentTag(R.id.pager, mCurrentPosition)); 39 | if (null != fragment && fragment instanceof BasePageFragment) { 40 | ((BasePageFragment) fragment).scrollToTop(); 41 | } 42 | } 43 | }); 44 | } 45 | 46 | public class LanguagesPagerAdapter extends FragmentPagerAdapter { 47 | List languagesArray = new ArrayList<>(); 48 | 49 | public LanguagesPagerAdapter(FragmentManager fm) { 50 | super(fm); 51 | languagesArray.addAll(dataManager.getLanguageHelper().getLanguage()); 52 | } 53 | 54 | @Override 55 | public Fragment getItem(int position) { 56 | return RepoListFragment.newInstance(getContext(), languagesArray.get(position)); 57 | } 58 | 59 | @Override 60 | public int getCount() { 61 | return languagesArray.size(); 62 | } 63 | 64 | @Override 65 | public CharSequence getPageTitle(int position) { 66 | return languagesArray.get(position).name; 67 | } 68 | 69 | public String getFragmentTag(int viewPagerId, int fragmentPosition) { 70 | return "android:switcher:" + viewPagerId + ":" + fragmentPosition; 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/ui/repos/RepoListFragment.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.ui.repos; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.support.annotation.Nullable; 6 | import android.support.v4.app.Fragment; 7 | import android.support.v7.widget.LinearLayoutManager; 8 | 9 | import com.yeungeek.monkeyandroid.R; 10 | import com.yeungeek.monkeyandroid.data.model.Language; 11 | import com.yeungeek.monkeyandroid.data.model.Repo; 12 | import com.yeungeek.monkeyandroid.data.model.WrapList; 13 | import com.yeungeek.monkeyandroid.ui.base.adapter.HeaderAndFooterRecyclerViewAdapter; 14 | import com.yeungeek.monkeyandroid.ui.base.view.BaseLceActivity; 15 | import com.yeungeek.monkeyandroid.ui.base.view.BasePageFragment; 16 | import com.yeungeek.monkeyandroid.util.AppCst; 17 | 18 | import javax.inject.Inject; 19 | 20 | /** 21 | * Created by yeungeek on 2016/3/30. 22 | */ 23 | public class RepoListFragment extends BasePageFragment, RepoMvpView, RepoPresenter> implements RepoMvpView { 24 | @Inject 25 | RepoPresenter repoPresenter; 26 | 27 | Language language; 28 | private RepoAdapter adapter; 29 | private HeaderAndFooterRecyclerViewAdapter mHeaderAdapter; 30 | 31 | public static Fragment newInstance(Context context, Language language) { 32 | Bundle bundle = new Bundle(); 33 | bundle.putSerializable(AppCst.EXTRA_LANGUAGE, language); 34 | return Fragment.instantiate(context, RepoListFragment.class.getName(), bundle); 35 | } 36 | 37 | @Override 38 | public RepoPresenter createPresenter() { 39 | return repoPresenter; 40 | } 41 | 42 | @Override 43 | public void onCreate(@Nullable Bundle savedInstanceState) { 44 | super.onCreate(savedInstanceState); 45 | language = (Language) getArguments().getSerializable(AppCst.EXTRA_LANGUAGE); 46 | } 47 | 48 | @Override 49 | protected void initAdapter() { 50 | adapter = new RepoAdapter(getActivity(), language); 51 | 52 | mHeaderAdapter = new HeaderAndFooterRecyclerViewAdapter(adapter); 53 | recyclerView.setAdapter(mHeaderAdapter); 54 | recyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); 55 | } 56 | 57 | @Override 58 | protected String getErrorMessage(Throwable e, boolean pullToRefresh) { 59 | return getString(R.string.error_repositories) + "\n" + e.getMessage(); 60 | } 61 | 62 | @Override 63 | public void setData(WrapList data) { 64 | mCount = data.getTotal_count(); 65 | if (!mLoadMore) { 66 | adapter.addTopAll(data.getItems()); 67 | mCurrentSize = data.getItems().size(); 68 | } else { 69 | adapter.addAll(data.getItems()); 70 | mCurrentSize += data.getItems().size(); 71 | } 72 | 73 | checkViewState(); 74 | } 75 | 76 | @Override 77 | public void loadData(boolean pullToRefresh) { 78 | getPresenter().listRepos(AppCst.LANGUAGE_PREFIX + language.path, mPage, pullToRefresh); 79 | } 80 | 81 | @Override 82 | protected void injectDependencies() { 83 | super.injectDependencies(); 84 | if (getActivity() instanceof BaseLceActivity) { 85 | ((BaseLceActivity) getActivity()).activityComponent().inject(this); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/ui/repos/RepoMvpView.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.ui.repos; 2 | 3 | import com.yeungeek.monkeyandroid.data.model.Repo; 4 | import com.yeungeek.monkeyandroid.data.model.WrapList; 5 | import com.yeungeek.mvp.common.lce.MvpLceView; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * Created by yeungeek on 2016/3/31. 11 | */ 12 | public interface RepoMvpView extends MvpLceView> { 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/ui/repos/RepoPresenter.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.ui.repos; 2 | 3 | import com.yeungeek.monkeyandroid.data.DataManager; 4 | import com.yeungeek.monkeyandroid.data.model.Repo; 5 | import com.yeungeek.monkeyandroid.data.model.WrapList; 6 | import com.yeungeek.monkeyandroid.ui.base.presenter.MvpLceRxPresenter; 7 | import com.yeungeek.mvp.common.MvpPresenter; 8 | 9 | import javax.inject.Inject; 10 | 11 | import timber.log.Timber; 12 | 13 | /** 14 | * Created by yeungeek on 2016/3/31. 15 | */ 16 | public class RepoPresenter extends MvpLceRxPresenter> implements MvpPresenter { 17 | private final DataManager dataManager; 18 | 19 | @Inject 20 | public RepoPresenter(final DataManager dataManager) { 21 | this.dataManager = dataManager; 22 | } 23 | 24 | public void listRepos(final String query, final int page, final boolean pullToRefresh) { 25 | Timber.d("### get list repos query:%s, page: %d", query, page); 26 | subscribe(dataManager.getRepos(query, page), pullToRefresh); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/ui/signin/SignInDialogFragment.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.ui.signin; 2 | 3 | import android.app.AlertDialog; 4 | import android.app.Dialog; 5 | import android.graphics.Bitmap; 6 | import android.graphics.Rect; 7 | import android.net.Uri; 8 | import android.os.Bundle; 9 | import android.support.annotation.NonNull; 10 | import android.support.v4.app.DialogFragment; 11 | import android.view.View; 12 | import android.webkit.WebView; 13 | import android.webkit.WebViewClient; 14 | 15 | import com.yeungeek.monkeyandroid.rxbus.RxBus; 16 | import com.yeungeek.monkeyandroid.rxbus.event.SignInEvent; 17 | import com.yeungeek.monkeyandroid.ui.base.view.BaseLceActivity; 18 | 19 | import javax.inject.Inject; 20 | 21 | import timber.log.Timber; 22 | 23 | 24 | public class SignInDialogFragment extends DialogFragment { 25 | private String url; 26 | private final static String EXTRA_URL = "extra_url"; 27 | 28 | @Inject 29 | RxBus rxBus; 30 | 31 | public static SignInDialogFragment newInstance(final String url) { 32 | SignInDialogFragment fragment = new SignInDialogFragment(); 33 | Bundle bundle = new Bundle(); 34 | bundle.putString(EXTRA_URL, url); 35 | fragment.setArguments(bundle); 36 | return fragment; 37 | } 38 | 39 | @Override 40 | public void onCreate(Bundle savedInstanceState) { 41 | super.onCreate(savedInstanceState); 42 | ((BaseLceActivity) getActivity()).activityComponent().inject(this); 43 | url = getArguments().getString(EXTRA_URL); 44 | } 45 | 46 | @NonNull 47 | @Override 48 | public Dialog onCreateDialog(Bundle savedInstanceState) { 49 | AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); 50 | final WebView webView = new WebView(this.getActivity()) { 51 | boolean layoutChangedOnce = false; 52 | @Override 53 | protected void onLayout(boolean changed, int l, int t, int r, int b) { 54 | if (!layoutChangedOnce) { 55 | super.onLayout(changed, l, t, r, b); 56 | layoutChangedOnce = true; 57 | } 58 | } 59 | 60 | @Override 61 | protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { 62 | super.onFocusChanged(true, direction, previouslyFocusedRect); 63 | } 64 | 65 | @Override 66 | public boolean onCheckIsTextEditor() { 67 | return true; 68 | } 69 | }; 70 | 71 | webView.loadUrl(url); 72 | webView.setFocusable(true); 73 | webView.setFocusableInTouchMode(true); 74 | webView.requestFocus(View.FOCUS_DOWN); 75 | webView.setWebViewClient(new WebViewClient() { 76 | @Override 77 | public void onPageStarted(WebView view, String url, Bitmap favicon) { 78 | super.onPageStarted(view, url, favicon); 79 | } 80 | 81 | @Override 82 | public void onPageFinished(WebView view, String url) { 83 | super.onPageFinished(view, url); 84 | } 85 | 86 | @Override 87 | public boolean shouldOverrideUrlLoading(WebView view, String url) { 88 | Uri uri = Uri.parse(url); 89 | if (uri.getScheme().equals("http")) { 90 | Timber.d("### get url %s",url); 91 | if(null != rxBus && rxBus.hasObservers()){ 92 | rxBus.send(new SignInEvent(uri)); 93 | } 94 | 95 | SignInDialogFragment.this.dismiss(); 96 | return true; 97 | } 98 | return super.shouldOverrideUrlLoading(view, url); 99 | } 100 | }); 101 | 102 | webView.requestFocus(View.FOCUS_DOWN); 103 | builder.setView(webView); 104 | return builder.create(); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/ui/trending/TrendingMvpView.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.ui.trending; 2 | 3 | import com.yeungeek.monkeyandroid.data.model.Repo; 4 | import com.yeungeek.mvp.common.lce.MvpLceView; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * Created by yeungeek on 2016/3/31. 10 | */ 11 | public interface TrendingMvpView extends MvpLceView> { 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/ui/trending/TrendingPresenter.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.ui.trending; 2 | 3 | import com.yeungeek.monkeyandroid.data.DataManager; 4 | import com.yeungeek.monkeyandroid.data.model.Repo; 5 | import com.yeungeek.monkeyandroid.ui.base.presenter.MvpLceRxPresenter; 6 | import com.yeungeek.mvp.common.MvpPresenter; 7 | 8 | import java.util.List; 9 | 10 | import javax.inject.Inject; 11 | 12 | import timber.log.Timber; 13 | 14 | /** 15 | * Created by yeungeek on 2016/3/31. 16 | */ 17 | public class TrendingPresenter extends MvpLceRxPresenter> implements MvpPresenter { 18 | private final DataManager dataManager; 19 | 20 | @Inject 21 | public TrendingPresenter(final DataManager dataManager) { 22 | this.dataManager = dataManager; 23 | } 24 | 25 | public void listTrending(final String language, final String since, final boolean pullToRefresh) { 26 | Timber.d("### get list repos language:%s, since: %s", language, since); 27 | subscribe(dataManager.getTrending(language, since), pullToRefresh); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/ui/users/FamousUserFragment.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.ui.users; 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.view.View; 7 | 8 | import com.yeungeek.monkeyandroid.R; 9 | import com.yeungeek.monkeyandroid.data.model.Language; 10 | import com.yeungeek.monkeyandroid.ui.base.view.BasePageFragment; 11 | import com.yeungeek.monkeyandroid.ui.base.view.BaseToolbarFragment; 12 | import com.yeungeek.monkeyandroid.util.AppCst; 13 | 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | 17 | /** 18 | * Created by yeungeek on 2016/4/8. 19 | */ 20 | public class FamousUserFragment extends BaseToolbarFragment { 21 | private LanguagesPagerAdapter mPagerAdapter; 22 | 23 | @Override 24 | protected void initToolbar() { 25 | getActionBar().setTitle(R.string.menu_title_user); 26 | } 27 | 28 | @Override 29 | protected void initViews() { 30 | super.initViews(); 31 | 32 | mPagerAdapter = new LanguagesPagerAdapter(getChildFragmentManager()); 33 | getViewPager().setAdapter(mPagerAdapter); 34 | getTabLayout().setupWithViewPager(getViewPager()); 35 | 36 | getFloatingActionButton().setOnClickListener(new View.OnClickListener() { 37 | @Override 38 | public void onClick(View v) { 39 | Fragment fragment = getChildFragmentManager().findFragmentByTag(mPagerAdapter.getFragmentTag(R.id.pager, mCurrentPosition)); 40 | if (null != fragment && fragment instanceof BasePageFragment) { 41 | ((BasePageFragment) fragment).scrollToTop(); 42 | } 43 | } 44 | }); 45 | } 46 | 47 | public class LanguagesPagerAdapter extends FragmentPagerAdapter { 48 | List languagesArray = new ArrayList<>(); 49 | 50 | public LanguagesPagerAdapter(FragmentManager fm) { 51 | super(fm); 52 | languagesArray.addAll(dataManager.getLanguageHelper().getLanguage()); 53 | Language language = new Language(AppCst.USER_CHINA_ALL, AppCst.USER_LOCATION_CHINA); 54 | languagesArray.add(0, language); 55 | } 56 | 57 | @Override 58 | public Fragment getItem(int position) { 59 | return UserListFragment.newInstance(getContext(), languagesArray.get(position)); 60 | } 61 | 62 | @Override 63 | public int getCount() { 64 | return languagesArray.size(); 65 | } 66 | 67 | @Override 68 | public CharSequence getPageTitle(int position) { 69 | return languagesArray.get(position).name; 70 | } 71 | 72 | public String getFragmentTag(int viewPagerId, int fragmentPosition) { 73 | return "android:switcher:" + viewPagerId + ":" + fragmentPosition; 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/ui/users/UserAdapter.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.ui.users; 2 | 3 | import android.content.Context; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.ImageView; 9 | import android.widget.TextView; 10 | 11 | import com.bumptech.glide.Glide; 12 | import com.yeungeek.monkeyandroid.R; 13 | import com.yeungeek.monkeyandroid.data.model.User; 14 | import com.yeungeek.monkeyandroid.ui.detail.DetailActivity; 15 | 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | 19 | import butterknife.Bind; 20 | import butterknife.ButterKnife; 21 | import butterknife.OnClick; 22 | 23 | /** 24 | * Created by yeungeek on 2016/4/8. 25 | */ 26 | public class UserAdapter extends RecyclerView.Adapter { 27 | private Context mContext; 28 | private List users = new ArrayList<>(); 29 | private OnItemClickListener mOnItemClickListener; 30 | 31 | public UserAdapter(final Context context) { 32 | this.mContext = context; 33 | } 34 | 35 | @Override 36 | public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 37 | View view = LayoutInflater.from(mContext).inflate(R.layout.item_user, parent, false); 38 | UserViewHolder viewHolder = new UserViewHolder(view); 39 | if (null != mOnItemClickListener) { 40 | viewHolder.itemView.setOnClickListener(new View.OnClickListener() { 41 | @Override 42 | public void onClick(View v) { 43 | mOnItemClickListener.onItemClick(v); 44 | } 45 | }); 46 | } 47 | return viewHolder; 48 | } 49 | 50 | @Override 51 | public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { 52 | final User user = users.get(position); 53 | UserViewHolder viewHolder = (UserViewHolder) holder; 54 | viewHolder.user = user; 55 | 56 | viewHolder.login.setText(user.getLogin()); 57 | Glide.with(mContext).load(user.getAvatarUrl()).into(viewHolder.userAvatar); 58 | } 59 | 60 | @Override 61 | public int getItemCount() { 62 | return users.size(); 63 | } 64 | 65 | public void addAll(final List list) { 66 | users.addAll(list); 67 | } 68 | 69 | public void addTopAll(final List list) { 70 | users.clear(); 71 | users.addAll(list); 72 | notifyDataSetChanged(); 73 | } 74 | 75 | public class UserViewHolder extends RecyclerView.ViewHolder { 76 | User user; 77 | 78 | @Bind(R.id.id_user_login) 79 | TextView login; 80 | @Bind(R.id.id_user_avatar) 81 | ImageView userAvatar; 82 | 83 | // @OnClick(R.id.id_user_card) 84 | // public void onItemClick() { 85 | // mContext.startActivity(DetailActivity.getStartIntent(mContext, user)); 86 | // } 87 | 88 | public UserViewHolder(View itemView) { 89 | super(itemView); 90 | ButterKnife.bind(this, itemView); 91 | } 92 | } 93 | 94 | public User getUser(int position) { 95 | return users.get(position); 96 | } 97 | 98 | public UserAdapter setOnItemClickListener(OnItemClickListener onItemClickListener) { 99 | this.mOnItemClickListener = onItemClickListener; 100 | return this; 101 | } 102 | 103 | public static interface OnItemClickListener { 104 | void onItemClick(View view); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/ui/users/UserMvpView.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.ui.users; 2 | 3 | import com.yeungeek.monkeyandroid.data.model.User; 4 | import com.yeungeek.monkeyandroid.data.model.WrapList; 5 | import com.yeungeek.mvp.common.lce.MvpLceView; 6 | 7 | /** 8 | * Created by yeungeek on 2016/4/8. 9 | */ 10 | public interface UserMvpView extends MvpLceView> { 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/ui/users/UserPresenter.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.ui.users; 2 | 3 | import com.yeungeek.monkeyandroid.data.DataManager; 4 | import com.yeungeek.monkeyandroid.data.model.User; 5 | import com.yeungeek.monkeyandroid.data.model.WrapList; 6 | import com.yeungeek.monkeyandroid.ui.base.presenter.MvpLceRxPresenter; 7 | import com.yeungeek.mvp.common.MvpPresenter; 8 | 9 | import javax.inject.Inject; 10 | 11 | import timber.log.Timber; 12 | 13 | /** 14 | * Created by yeungeek on 2016/4/8. 15 | */ 16 | public class UserPresenter extends MvpLceRxPresenter> implements MvpPresenter { 17 | private final DataManager dataManager; 18 | 19 | @Inject 20 | public UserPresenter(final DataManager dataManager) { 21 | this.dataManager = dataManager; 22 | } 23 | 24 | public void listUsers(final String query, final int page, final boolean pullToRefresh) { 25 | Timber.d("### get list users query:%s, page: %d", query, page); 26 | subscribe(dataManager.getUsers(query, page), pullToRefresh); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/util/EncodingUtil.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.util; 2 | 3 | import android.util.Base64; 4 | 5 | /** 6 | * Created by yeungeek on 2016/4/13. 7 | */ 8 | public class EncodingUtil { 9 | public static final byte[] fromBase64(String content) { 10 | return Base64.decode(content, Base64.DEFAULT); 11 | } 12 | 13 | public static final String toBase64(final byte[] content) { 14 | return Base64.encodeToString(content, Base64.DEFAULT); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/util/HttpStatus.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.util; 2 | 3 | /** 4 | * Created by yeungeek on 2016/4/8. 5 | */ 6 | public interface HttpStatus { 7 | int HTTP_SWITCHING_PROTOCOLS = 101; 8 | int HTTP_OK = 200; 9 | int HTTP_NO_CONTENT = 204; 10 | int HTTP_UNAUTHORIZED = 401; 11 | int HTTP_NOT_FOUND = 404; 12 | int HTTP_INTERNAL_SERVER_ERROR = 500; 13 | int HTTP_NOT_IMPLEMENTED = 501; 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/util/ImageSize.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.util; 2 | 3 | /** 4 | * Created by yeungeek on 2016/4/3. 5 | */ 6 | public interface ImageSize { 7 | String AVATAR_120 = "&s=120"; 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/views/widget/ScrollAwareFABBehavior.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.views.widget; 2 | 3 | import android.content.Context; 4 | import android.support.design.widget.CoordinatorLayout; 5 | import android.support.design.widget.FloatingActionButton; 6 | import android.support.v4.view.ViewCompat; 7 | import android.util.AttributeSet; 8 | import android.view.View; 9 | 10 | /** 11 | * Created by yeungeek on 2016/4/15. 12 | * 13 | * @see 'https://github.com/ianhanniballake/cheesesquare/blob/92bcf7c8b57459051424cd512a032c12d24a41b3/app/src/main/java/com/support/android/designlibdemo/ScrollAwareFABBehavior.java' 14 | */ 15 | public class ScrollAwareFABBehavior extends FloatingActionButton.Behavior { 16 | public ScrollAwareFABBehavior(Context context, AttributeSet attrs) { 17 | super(); 18 | } 19 | 20 | @Override 21 | public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child, View directTargetChild, View target, int nestedScrollAxes) { 22 | return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL || 23 | super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, 24 | nestedScrollAxes); 25 | } 26 | 27 | @Override 28 | public void onNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) { 29 | if (dyConsumed > 0 && child.getVisibility() == View.VISIBLE) { 30 | child.hide(); 31 | } else if (dyConsumed < 0 && child.getVisibility() != View.VISIBLE) { 32 | child.show(); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/yeungeek/monkeyandroid/views/widget/ScrollOffBottomBehavior.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.views.widget; 2 | 3 | import android.animation.ObjectAnimator; 4 | import android.content.Context; 5 | import android.support.design.widget.CoordinatorLayout; 6 | import android.support.design.widget.FloatingActionButton; 7 | import android.support.v4.view.ViewCompat; 8 | import android.util.AttributeSet; 9 | import android.view.View; 10 | 11 | /** 12 | * Created by yeungeek on 2016/4/15. 13 | */ 14 | public class ScrollOffBottomBehavior extends FloatingActionButton.Behavior { 15 | private int mViewHeight; 16 | private ObjectAnimator mAnimator; 17 | 18 | public ScrollOffBottomBehavior(Context context, AttributeSet attrs) { 19 | super(); 20 | } 21 | 22 | @Override 23 | public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child, View directTargetChild, View target, int nestedScrollAxes) { 24 | return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL || 25 | super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, 26 | nestedScrollAxes); 27 | } 28 | 29 | @Override 30 | public void onNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) { 31 | if (dyConsumed > 0 && child.getVisibility() == View.VISIBLE) { 32 | child.show(); 33 | } else if (dyConsumed < 0 && child.getVisibility() != View.VISIBLE) { 34 | child.hide(); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_about.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeungeek/monkey-android/f1bc5132b7571766b26d1b07645ffe9f0de5ab81/app/src/main/res/drawable-xhdpi/ic_about.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_arrow_back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeungeek/monkey-android/f1bc5132b7571766b26d1b07645ffe9f0de5ab81/app/src/main/res/drawable-xhdpi/ic_arrow_back.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_arrow_drop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeungeek/monkey-android/f1bc5132b7571766b26d1b07645ffe9f0de5ab81/app/src/main/res/drawable-xhdpi/ic_arrow_drop.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_arrow_upward.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeungeek/monkey-android/f1bc5132b7571766b26d1b07645ffe9f0de5ab81/app/src/main/res/drawable-xhdpi/ic_arrow_upward.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeungeek/monkey-android/f1bc5132b7571766b26d1b07645ffe9f0de5ab81/app/src/main/res/drawable-xhdpi/ic_avatar.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_favorite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeungeek/monkey-android/f1bc5132b7571766b26d1b07645ffe9f0de5ab81/app/src/main/res/drawable-xhdpi/ic_favorite.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_favorite_border.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeungeek/monkey-android/f1bc5132b7571766b26d1b07645ffe9f0de5ab81/app/src/main/res/drawable-xhdpi/ic_favorite_border.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeungeek/monkey-android/f1bc5132b7571766b26d1b07645ffe9f0de5ab81/app/src/main/res/drawable-xhdpi/ic_menu.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_more_vert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeungeek/monkey-android/f1bc5132b7571766b26d1b07645ffe9f0de5ab81/app/src/main/res/drawable-xhdpi/ic_more_vert.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_person.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeungeek/monkey-android/f1bc5132b7571766b26d1b07645ffe9f0de5ab81/app/src/main/res/drawable-xhdpi/ic_person.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_person_add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeungeek/monkey-android/f1bc5132b7571766b26d1b07645ffe9f0de5ab81/app/src/main/res/drawable-xhdpi/ic_person_add.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_repo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeungeek/monkey-android/f1bc5132b7571766b26d1b07645ffe9f0de5ab81/app/src/main/res/drawable-xhdpi/ic_repo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_retry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeungeek/monkey-android/f1bc5132b7571766b26d1b07645ffe9f0de5ab81/app/src/main/res/drawable-xhdpi/ic_retry.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_trending.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeungeek/monkey-android/f1bc5132b7571766b26d1b07645ffe9f0de5ab81/app/src/main/res/drawable-xhdpi/ic_trending.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_users.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeungeek/monkey-android/f1bc5132b7571766b26d1b07645ffe9f0de5ab81/app/src/main/res/drawable-xhdpi/ic_users.png -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_about.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | 23 | 24 | 31 | 32 | 39 | 40 | 41 | 42 | 43 | 47 | 48 | 53 | 54 | 55 | 62 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_detail.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 11 | 12 | 16 | 17 | 18 | 19 | 20 | 21 | 25 | 26 | 27 | 34 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_repos.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 15 | 16 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_hot_repos.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_pull_refresh_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 14 | 15 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_repo.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 16 | 17 | 24 | 25 | 31 | 32 | 37 | 38 | 44 | 45 | 51 | 52 | 53 | 68 | 69 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_user.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 17 | 18 | 24 | 25 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_appbar.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | 22 | 23 | 29 | 30 | 31 | 32 | 37 | 38 | 45 | 46 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_common_list_footer.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 | 20 | 21 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_common_list_footer_end.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_common_list_footer_loading.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_common_list_footer_network_error.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_drawer_header.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 14 | 15 | 21 | 22 | 28 | 29 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_spinner_item_actionbar.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_spinner_item_dropdown.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_trending_repos.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/layout/view_error.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/layout/view_loading.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_drawer.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 12 | 16 | 17 | 18 | 21 | 22 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeungeek/monkey-android/f1bc5132b7571766b26d1b07645ffe9f0de5ab81/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_cloud_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeungeek/monkey-android/f1bc5132b7571766b26d1b07645ffe9f0de5ab81/app/src/main/res/mipmap-xhdpi/ic_cloud_off.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeungeek/monkey-android/f1bc5132b7571766b26d1b07645ffe9f0de5ab81/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeungeek/monkey-android/f1bc5132b7571766b26d1b07645ffe9f0de5ab81/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeungeek/monkey-android/f1bc5132b7571766b26d1b07645ffe9f0de5ab81/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /app/src/main/res/values/attrs_triangleLabelview.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FF3F51B5 4 | #FF303F9F 5 | #FFFF4081 6 | 7 | 8 | #FF000000 9 | #FF27D34D 10 | #FF2A91BD 11 | #FFFF9D2F 12 | #FFFF432F 13 | #FFFFFFFF 14 | #FFC0C0C0 15 | 16 | 17 | @color/black 18 | @color/blue_300 19 | 20 | #8A000000 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 16dp 6 | 7 | 8 | 18sp 9 | 16sp 10 | 14sp 11 | 12sp 12 | 9sp 13 | 14 | 15 | 40dp 16 | 24dp 17 | 14dp 18 | 10dp 19 | 4dp 20 | 21 | 22 | 72dp 23 | 60dp 24 | 40dp 25 | 32dp 26 | 20dp 27 | 28 | 29 | 20dp 30 | 16dp 31 | 15dp 32 | 10dp 33 | 8dp 34 | 4dp 35 | 2dp 36 | 37 | 38 | 128dp 39 | 64dp 40 | 48dp 41 | 30dp 42 | 15dp 43 | 10dp 44 | 8dp 45 | 4dp 46 | 2dp 47 | 48 | 168dp 49 | 50 | 51 | 230dp 52 | 64dp 53 | 48dp 54 | 55 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Monkey 3 | Monkey For Android 4 | 5 | 6 | Network error 7 | Error while loading repositories 8 | Error while loading users 9 | Please login! 10 | 11 | 12 | win 13 | 14 | Click to reload 15 | Already ended 16 | Loading... 17 | 18 | username 19 | email 20 | Home 21 | Famous user 22 | Hot repo 23 | Today 24 | This Week 25 | This Month 26 | 27 | star: %1$d 28 | fork: %1$d 29 | 30 | Following(%1$d) 31 | Followers(%1$d) 32 | 33 | Enter 34 | Settings 35 | 36 | 37 | 38 | 39 | @string/title_user 40 | @string/title_repo 41 | Trending 42 | About 43 | 44 | Monkey android is a GitHub third party client, show the rank of users and repositories,trending. Base on Material Design,use MVP pattern 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 16 | 26 | 27 | 31 | 32 | 37 | 38 | 39 | 43 | 44 | 48 | 49 | 53 | 54 | 58 | 59 | 63 | 64 | 68 | 69 | 72 | 73 | 76 | 77 | -------------------------------------------------------------------------------- /app/src/test/java/com/yeungeek/monkeyandroid/DataManagerTest.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid; 2 | 3 | import com.yeungeek.monkeyandroid.data.DataManager; 4 | import com.yeungeek.monkeyandroid.data.local.DatabaseHelper; 5 | import com.yeungeek.monkeyandroid.data.local.LanguageHelper; 6 | import com.yeungeek.monkeyandroid.data.local.PreferencesHelper; 7 | import com.yeungeek.monkeyandroid.data.remote.GithubApi; 8 | import com.yeungeek.monkeyandroid.data.remote.SimpleApi; 9 | import com.yeungeek.monkeyandroid.rxbus.RxBus; 10 | 11 | import org.junit.Before; 12 | import org.junit.Test; 13 | import org.junit.runner.RunWith; 14 | import org.mockito.Mock; 15 | import org.mockito.Mockito; 16 | import org.mockito.runners.MockitoJUnitRunner; 17 | 18 | import rx.Observable; 19 | 20 | /** 21 | * Created by yeungeek on 2016/4/27. 22 | */ 23 | @RunWith(MockitoJUnitRunner.class) 24 | public class DataManagerTest { 25 | @Mock 26 | GithubApi mGithubApi; 27 | @Mock 28 | SimpleApi mSimpleApi; 29 | @Mock 30 | RxBus mRxBus; 31 | @Mock 32 | PreferencesHelper mPreferencesHelper; 33 | @Mock 34 | DatabaseHelper mDatabaseHelper; 35 | @Mock 36 | LanguageHelper mLanguageHelper; 37 | 38 | DataManager mDataManager; 39 | MonkeyApplication mApplication; 40 | 41 | @Before 42 | public void setUp() { 43 | mApplication = Mockito.mock(MonkeyApplication.class); 44 | mDataManager = new DataManager(mApplication, mGithubApi, mSimpleApi, mRxBus, mPreferencesHelper, mDatabaseHelper, mLanguageHelper); 45 | } 46 | 47 | @Test 48 | public void clearTables() { 49 | Mockito.doReturn(Observable.empty()) 50 | .when(mDatabaseHelper) 51 | .clearTables(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/src/test/java/com/yeungeek/monkeyandroid/EncodingUtilTest.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid; 2 | 3 | import com.yeungeek.monkeyandroid.util.EncodingUtil; 4 | 5 | import org.junit.Assert; 6 | import org.junit.Test; 7 | 8 | /** 9 | * Created by yeungeek on 2016/4/24. 10 | */ 11 | public class EncodingUtilTest { 12 | @Test 13 | public void testFromBase64() throws Exception { 14 | String str = "YW5kcm9pZA=="; 15 | Assert.assertEquals(null, EncodingUtil.fromBase64(str)); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/test/java/com/yeungeek/monkeyandroid/PreferencesHelperTest.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid; 2 | 3 | import com.yeungeek.monkeyandroid.data.local.PreferencesHelper; 4 | import com.yeungeek.monkeyandroid.util.DefaultConfig; 5 | 6 | import org.junit.Assert; 7 | import org.junit.Before; 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | import org.robolectric.RobolectricGradleTestRunner; 11 | import org.robolectric.RuntimeEnvironment; 12 | import org.robolectric.annotation.Config; 13 | 14 | /** 15 | * Created by yeungeek on 2016/4/25. 16 | */ 17 | 18 | @RunWith(RobolectricGradleTestRunner.class) 19 | @Config(constants = BuildConfig.class, sdk = DefaultConfig.EMULATE_SDK) 20 | public class PreferencesHelperTest { 21 | private PreferencesHelper mPreferencesHelper = 22 | new PreferencesHelper(RuntimeEnvironment.application); 23 | 24 | @Before 25 | public void setUp() { 26 | mPreferencesHelper.clear(); 27 | } 28 | 29 | @Test 30 | public void testPutGetAccessToken() { 31 | String token = "token"; 32 | mPreferencesHelper.putAccessToken(token); 33 | Assert.assertEquals(token, mPreferencesHelper.getAccessToken()); 34 | 35 | 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/src/test/java/com/yeungeek/monkeyandroid/util/DefaultConfig.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.monkeyandroid.util; 2 | 3 | /** 4 | * Created by yeungeek on 2016/4/25. 5 | */ 6 | public class DefaultConfig { 7 | //The api level that Roboelectric will use to run the unit tests 8 | public static final int EMULATE_SDK = 21; 9 | } 10 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | apply from: 'buildsystem/dependencies.gradle' 3 | 4 | buildscript { 5 | repositories { 6 | jcenter() 7 | maven { 8 | url 'https://maven.google.com/' 9 | name 'Google' 10 | } 11 | } 12 | dependencies { 13 | classpath 'com.android.tools.build:gradle:2.2.2' 14 | classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' 15 | 16 | // NOTE: Do not place your application dependencies here; they belong 17 | // in the individual module build.gradle files 18 | classpath "com.fernandocejas.frodo:frodo-plugin:0.8.2" 19 | classpath "net.rdrei.android.buildtimetracker:gradle-plugin:0.5.+" 20 | classpath 'com.tencent.bugly:symtabfileuploader:1.3.9' 21 | classpath 'org.kt3k.gradle.plugin:coveralls-gradle-plugin:2.6.3' 22 | //play 23 | classpath 'com.google.gms:google-services:3.0.0' 24 | } 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | 31 | task wrapper(type: Wrapper) { 32 | description 'Creates the gradle wrapper.' 33 | gradleVersion '2.10' 34 | } -------------------------------------------------------------------------------- /buildsystem/artifacts.gradle: -------------------------------------------------------------------------------- 1 | // applicationName property to gradle.properties 2 | android.applicationVariants.all { variant -> 3 | def appName 4 | if (project.hasProperty("applicationName")) { 5 | appName = applicationName 6 | } else { 7 | appName = project.name 8 | } 9 | 10 | variant.outputs.each { output -> 11 | def newApkName 12 | if (output.zipAlign) { 13 | newApkName = "${appName}-${output.baseName}-${variant.versionName}.apk" 14 | } else { 15 | newApkName = "${appName}-${output.baseName}-${variant.versionName}-unaligned.apk" 16 | } 17 | output.outputFile = new File(output.outputFile.parent, newApkName) 18 | } 19 | } -------------------------------------------------------------------------------- /buildsystem/jacoco.gradle: -------------------------------------------------------------------------------- 1 | //https://blog.gouline.net/code-coverage-on-android-with-jacoco-92ec90c9355e#.v34ksl11k 2 | //https://github.com/bumptech/glide 3 | //https://github.com/kt3k/coveralls-gradle-plugin 4 | apply plugin: 'jacoco' 5 | 6 | jacoco { 7 | toolVersion = "0.7.5.201505241946" 8 | } 9 | 10 | project.afterEvaluate { 11 | // Grab all build types and product flavors 12 | def buildTypes = android.buildTypes.collect { type -> type.name } 13 | def productFlavors = android.productFlavors.collect { flavor -> flavor.name } 14 | 15 | // When no product flavors defined, use empty 16 | if (!productFlavors) productFlavors.add('') 17 | 18 | productFlavors.each { productFlavorName -> 19 | buildTypes.each { buildTypeName -> 20 | def sourceName, sourcePath 21 | if (!productFlavorName) { 22 | sourceName = sourcePath = "${buildTypeName}" 23 | } else { 24 | sourceName = "${productFlavorName}${buildTypeName.capitalize()}" 25 | sourcePath = "${productFlavorName}/${buildTypeName}" 26 | } 27 | def testTaskName = "test${sourceName.capitalize()}UnitTest" 28 | 29 | // Create coverage task of form 'testFlavorTypeCoverage' depending on 'testFlavorTypeUnitTest' 30 | task "${testTaskName}Coverage"(type: JacocoReport, dependsOn: "$testTaskName") { 31 | group = "Reporting" 32 | description = "Generate Jacoco coverage reports on the ${sourceName.capitalize()} build." 33 | 34 | classDirectories = fileTree( 35 | dir: "${project.buildDir}/intermediates/classes/${sourcePath}", 36 | excludes: ['**/R.class', 37 | '**/R$*.class', 38 | '**/*$ViewInjector*.*', 39 | '**/*$ViewBinder*.*', 40 | '**/BuildConfig.*', 41 | '**/Manifest*.*'] 42 | ) 43 | 44 | def coverageSourceDirs = [ 45 | "src/main/java", 46 | "src/$productFlavorName/java", 47 | "src/$buildTypeName/java" 48 | ] 49 | additionalSourceDirs = files(coverageSourceDirs) 50 | sourceDirectories = files(coverageSourceDirs) 51 | executionData = files("${project.buildDir}/jacoco/${testTaskName}.exec") 52 | 53 | reports { 54 | xml.enabled = true 55 | html.enabled = true 56 | } 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /buildsystem/maven-push-java-lib.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Chris Banes 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 | 17 | apply plugin: 'maven' 18 | apply plugin: 'signing' 19 | 20 | def isReleaseBuild() { 21 | return VERSION_NAME.contains("SNAPSHOT") == false 22 | } 23 | 24 | def getReleaseRepositoryUrl() { 25 | return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL : 26 | "https://oss.sonatype.org/service/local/staging/deploy/maven2/" 27 | } 28 | 29 | def getSnapshotRepositoryUrl() { 30 | return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL : 31 | "https://oss.sonatype.org/content/repositories/snapshots/" 32 | } 33 | 34 | def getRepositoryUsername() { 35 | return hasProperty('NEXUS_USERNAME') ? NEXUS_USERNAME : "" 36 | } 37 | 38 | def getRepositoryPassword() { 39 | return hasProperty('NEXUS_PASSWORD') ? NEXUS_PASSWORD : "" 40 | } 41 | 42 | afterEvaluate { project -> 43 | uploadArchives { 44 | repositories { 45 | mavenDeployer { 46 | beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } 47 | 48 | pom.groupId = GROUP 49 | pom.artifactId = POM_ARTIFACT_ID 50 | pom.version = VERSION_NAME 51 | 52 | repository(url: getReleaseRepositoryUrl()) { 53 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) 54 | } 55 | snapshotRepository(url: getSnapshotRepositoryUrl()) { 56 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) 57 | } 58 | 59 | pom.project { 60 | name POM_NAME 61 | packaging POM_PACKAGING 62 | description POM_DESCRIPTION 63 | url POM_URL 64 | 65 | scm { 66 | url POM_SCM_URL 67 | connection POM_SCM_CONNECTION 68 | developerConnection POM_SCM_DEV_CONNECTION 69 | } 70 | 71 | licenses { 72 | license { 73 | name POM_LICENCE_NAME 74 | url POM_LICENCE_URL 75 | distribution POM_LICENCE_DIST 76 | } 77 | } 78 | 79 | developers { 80 | developer { 81 | id POM_DEVELOPER_ID 82 | name POM_DEVELOPER_NAME 83 | } 84 | } 85 | } 86 | 87 | 88 | // Resolve dependencies to other modules 89 | pom.whenConfigured { pom -> 90 | pom.dependencies.findAll { dep -> dep.groupId == rootProject.name }.collect { dep -> 91 | dep.groupId = pom.groupId = project.GROUP 92 | dep.version = pom.version = project.VERSION_NAME 93 | } 94 | } 95 | } 96 | } 97 | } 98 | 99 | signing { 100 | required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") } 101 | sign configurations.archives 102 | } 103 | 104 | /* 105 | task javadocJar(type: Jar) { 106 | classifier = 'javadoc' 107 | from javadoc 108 | 109 | if (JavaVersion.current().isJava8Compatible()) { 110 | allprojects { 111 | tasks.withType(Javadoc) { 112 | options.addStringOption('Xdoclint:none', '-quiet') 113 | } 114 | } 115 | } 116 | 117 | } 118 | 119 | task sourceJar (type : Jar) { 120 | classifier = 'sources' 121 | from sourceSets.main.allSource 122 | } 123 | 124 | artifacts { 125 | archives javadocJar 126 | archives sourceJar 127 | } 128 | 129 | */ 130 | 131 | } -------------------------------------------------------------------------------- /config/keystore/debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeungeek/monkey-android/f1bc5132b7571766b26d1b07645ffe9f0de5ab81/config/keystore/debug.keystore -------------------------------------------------------------------------------- /config/keystore/release.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeungeek/monkey-android/f1bc5132b7571766b26d1b07645ffe9f0de5ab81/config/keystore/release.keystore -------------------------------------------------------------------------------- /config/quality/findbugs/android-exclude-filter.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /config/quality/pmd/pmd-ruleset.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | Custom ruleset for ribot Android application 8 | 9 | .*/R.java 10 | .*/gen/.* 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /config/quality/quality.gradle: -------------------------------------------------------------------------------- 1 | /** 2 | * Set up Checkstyle, Findbugs and PMD to perform extensive code analysis. 3 | * 4 | * Gradle tasks added: 5 | * - checkstyle 6 | * - findbugs 7 | * - pmd 8 | * 9 | * The three tasks above are added as dependencies of the check task so running check will 10 | * run all of them. 11 | * 12 | * @see https://github.com/checkstyle/checkstyle 13 | */ 14 | 15 | apply plugin: 'checkstyle' 16 | apply plugin: 'findbugs' 17 | apply plugin: 'pmd' 18 | 19 | dependencies { 20 | checkstyle 'com.puppycrawl.tools:checkstyle:6.15' 21 | } 22 | 23 | def qualityConfigDir = "$project.rootDir/config/quality"; 24 | def reportsDir = "$project.buildDir/reports" 25 | 26 | check.dependsOn 'checkstyle', 'findbugs'//, 'pmd' 27 | 28 | task checkstyle(type: Checkstyle, group: 'Verification', description: 'Runs code style checks') { 29 | configFile file("$qualityConfigDir/checkstyle/checkstyle-config.xml") 30 | source 'src' 31 | include '**/*.java' 32 | 33 | reports { 34 | xml.enabled = true 35 | xml { 36 | destination "$reportsDir/checkstyle/checkstyle.xml" 37 | } 38 | } 39 | 40 | classpath = files( ) 41 | } 42 | 43 | task findbugs(type: FindBugs, 44 | group: 'Verification', 45 | description: 'Inspect java bytecode for bugs', 46 | dependsOn: ['compileInternalDebugSources','compileInternalReleaseSources']) { 47 | 48 | ignoreFailures = false 49 | effort = "max" 50 | reportLevel = "high" 51 | excludeFilter = new File("$qualityConfigDir/findbugs/android-exclude-filter.xml") 52 | classes = files("$project.rootDir/app/build/intermediates/classes") 53 | 54 | source 'src' 55 | include '**/*.java' 56 | exclude '**/gen/**' 57 | 58 | reports { 59 | xml.enabled = true 60 | html.enabled = false 61 | xml { 62 | destination "$reportsDir/findbugs/findbugs.xml" 63 | } 64 | html { 65 | destination "$reportsDir/findbugs/findbugs.html" 66 | } 67 | } 68 | 69 | classpath = files() 70 | } 71 | 72 | 73 | task pmd(type: Pmd, group: 'Verification', description: 'Inspect sourcecode for bugs') { 74 | ruleSetFiles = files("$qualityConfigDir/pmd/pmd-ruleset.xml") 75 | ignoreFailures = false 76 | ruleSets = [] 77 | 78 | source 'src' 79 | include '**/*.java' 80 | exclude '**/gen/**' 81 | 82 | reports { 83 | xml.enabled = true 84 | html.enabled = true 85 | xml { 86 | destination "$reportsDir/pmd/pmd.xml" 87 | } 88 | html { 89 | destination "$reportsDir/pmd/pmd.html" 90 | } 91 | } 92 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | 20 | #http://tools.android.com/tech-docs/new-build-system/gradle-experimental 21 | android.useDeprecatedNdk=true 22 | 23 | VERSION_NAME=1.0 24 | VERSION_CODE=1 25 | GROUP=com.yeungeek.monkeyandroid 26 | applicationName= Monkey 27 | 28 | POM_DESCRIPTION=Monkey for Android 29 | POM_URL=https://github.com/yeungeek/monkey-android 30 | POM_SCM_URL=https://github.com/yeungeek/monkey-android 31 | POM_SCM_CONNECTION=scm:git@github.com:yeungeek/monkey-android.git 32 | POM_SCM_DEV_CONNECTION=scm:git@github.com:yeungeek/monkey-android.git 33 | POM_LICENCE_NAME=The Apache Software License, Version 2.0 34 | POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt 35 | POM_LICENCE_DIST=repo 36 | POM_DEVELOPER_ID=yeungeek 37 | POM_DEVELOPER_NAME=yeungeek 38 | 39 | # bugly 40 | BUGLY_APPID=900018605 41 | BUGLY_APPKEY=pxz0zlygwYavlJ4y 42 | 43 | # ads 44 | BANNER_AD_UNIT_ID_DEBUG=ca-app-pub-4106776683609657/2279239023 45 | BANNER_AD_UNIT_ID_RELEASE=ca-app-pub-4106776683609657/9302978224 46 | 47 | ############################################################################### 48 | # App variables. 49 | ############################################################################### 50 | monkeyAppKeystoreDebugLocation = ../config/keystore/debug.keystore 51 | monkeyAppDebugKeyAlias = androiddebugkey 52 | monkeyAppDebugStorePassword = android 53 | monkeyAppDebugKeyPassword = android 54 | 55 | # The location of the keystore, alias and passwords used when creating a release build. The values 56 | # below are just defaults, these will need to be set to your own values. Make sure you generate a 57 | # new keystore and place it under the keystore folder or point this to the location of your release 58 | # keystore. 59 | monkeyAppKeystoreReleaseLocation = ../config/keystore/release.keystore 60 | monkeyAppReleaseKeyAlias = android 61 | monkeyAppReleaseStorePassword = android 62 | monkeyAppReleaseKeyPassword = android -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeungeek/monkey-android/f1bc5132b7571766b26d1b07645ffe9f0de5ab81/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Sep 28 10:17:05 CST 2016 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-2.14.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 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 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 Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /images/fir.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeungeek/monkey-android/f1bc5132b7571766b26d1b07645ffe9f0de5ab81/images/fir.png -------------------------------------------------------------------------------- /images/monkey.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeungeek/monkey-android/f1bc5132b7571766b26d1b07645ffe9f0de5ab81/images/monkey.gif -------------------------------------------------------------------------------- /images/preview1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeungeek/monkey-android/f1bc5132b7571766b26d1b07645ffe9f0de5ab81/images/preview1.png -------------------------------------------------------------------------------- /images/preview2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeungeek/monkey-android/f1bc5132b7571766b26d1b07645ffe9f0de5ab81/images/preview2.png -------------------------------------------------------------------------------- /images/preview3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeungeek/monkey-android/f1bc5132b7571766b26d1b07645ffe9f0de5ab81/images/preview3.png -------------------------------------------------------------------------------- /mvp/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /mvp/README.md: -------------------------------------------------------------------------------- 1 | ## MVP based architecture 2 | github: https://github.com/sockeqwe/mosby -------------------------------------------------------------------------------- /mvp/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion rootProject.ext.compileSdkVersion 5 | buildToolsVersion rootProject.ext.buildToolsVersion 6 | 7 | defaultConfig { 8 | minSdkVersion rootProject.ext.minSdk 9 | targetSdkVersion rootProject.ext.targetSdk 10 | versionCode 1 11 | versionName "1.0" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | compile fileTree(dir: 'libs', include: ['*.jar']) 23 | compile supportDependencies.appcompat 24 | 25 | // Test 26 | testCompile domainTestDependencies.junit 27 | } 28 | -------------------------------------------------------------------------------- /mvp/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in E:\dev\Android\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /mvp/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /mvp/src/main/java/com/yeungeek/mvp/common/Defaults.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.mvp.common; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import static java.util.Collections.unmodifiableMap; 7 | 8 | 9 | final class Defaults { 10 | private static final Map, Object> DEFAULTS = 11 | unmodifiableMap(new HashMap, Object>() { 12 | { 13 | put(Boolean.TYPE, false); 14 | put(Byte.TYPE, (byte) 0); 15 | put(Character.TYPE, '\000'); 16 | put(Double.TYPE, 0.0d); 17 | put(Float.TYPE, 0.0f); 18 | put(Integer.TYPE, 0); 19 | put(Long.TYPE, 0L); 20 | put(Short.TYPE, (short) 0); 21 | } 22 | }); 23 | 24 | private Defaults() { 25 | // no instances 26 | } 27 | 28 | @SuppressWarnings("unchecked") 29 | public static T defaultValue(Class type) { 30 | return (T) DEFAULTS.get(type); 31 | } 32 | } -------------------------------------------------------------------------------- /mvp/src/main/java/com/yeungeek/mvp/common/MvpBasePresenter.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.mvp.common; 2 | 3 | import android.support.annotation.Nullable; 4 | 5 | import java.lang.ref.WeakReference; 6 | 7 | /** 8 | * A base implementation of a {@link MvpPresenter} that uses a WeakReference for referring 9 | * to the attached view. 10 | */ 11 | public class MvpBasePresenter implements MvpPresenter { 12 | 13 | private WeakReference viewRef; 14 | 15 | @Override 16 | public void attachView(V view) { 17 | viewRef = new WeakReference(view); 18 | } 19 | 20 | @Override 21 | public void detachView(boolean retainInstance) { 22 | if (null != viewRef) { 23 | viewRef.clear(); 24 | viewRef = null; 25 | } 26 | } 27 | 28 | @Nullable 29 | public V getView() { 30 | return null == viewRef ? null : viewRef.get(); 31 | } 32 | 33 | public boolean isViewAttached() { 34 | return viewRef != null && viewRef.get() != null; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /mvp/src/main/java/com/yeungeek/mvp/common/MvpNullObjectBasePresenter.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.mvp.common; 2 | 3 | import java.lang.reflect.ParameterizedType; 4 | import java.lang.reflect.Type; 5 | 6 | /** 7 | * A {@link MvpPresenter} implmenetation that implements the null 8 | * object pattern for the attached mvp view. So whenever the view gets detached from this 9 | * presenter by calling{@link #detachView(boolean)}, a new "null object" view gets dynamically 10 | * instantiated by using reflections and set as the current 11 | * view (instead of null). The new "null object" view simply does nothing. This avoids null pointer 12 | * exceptions and checks like {@code if (getView() != null)} 13 | */ 14 | public class MvpNullObjectBasePresenter implements MvpPresenter { 15 | private V view; 16 | 17 | @Override 18 | public void attachView(V view) { 19 | this.view = view; 20 | } 21 | 22 | @Override 23 | public void detachView(boolean retainInstance) { 24 | if (null != view) { 25 | Type[] types = 26 | ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments(); 27 | 28 | final Class viewClass = (Class) types[0]; 29 | view = NoOp.of(viewClass); 30 | } 31 | } 32 | 33 | public V getView() { 34 | if (null == view) { 35 | throw new NullPointerException("MvpView reference is null. Have you called attachView()?"); 36 | } 37 | return view; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /mvp/src/main/java/com/yeungeek/mvp/common/MvpPresenter.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.mvp.common; 2 | 3 | /** 4 | * Created by yeungeek on 2015/12/24. 5 | */ 6 | public interface MvpPresenter { 7 | 8 | void attachView(V view); 9 | 10 | void detachView(boolean retainInstance); 11 | } 12 | -------------------------------------------------------------------------------- /mvp/src/main/java/com/yeungeek/mvp/common/MvpView.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.mvp.common; 2 | 3 | /** 4 | * Created by yeungeek on 2016/1/8. 5 | */ 6 | public interface MvpView { 7 | } 8 | -------------------------------------------------------------------------------- /mvp/src/main/java/com/yeungeek/mvp/common/NoOp.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.mvp.common; 2 | 3 | import java.lang.reflect.InvocationHandler; 4 | import java.lang.reflect.Method; 5 | import java.lang.reflect.Proxy; 6 | 7 | /** 8 | * Created by yeungeek on 2015/12/24. 9 | */ 10 | public final class NoOp { 11 | private NoOp() { 12 | 13 | } 14 | 15 | private static final InvocationHandler DEFAULT_VALUE = new DefaultValueInvocationHandler(); 16 | 17 | public static T of(Class interfaceClass) { 18 | return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[]{interfaceClass}, DEFAULT_VALUE); 19 | } 20 | 21 | private static class DefaultValueInvocationHandler implements InvocationHandler { 22 | @Override 23 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 24 | return Defaults.defaultValue(method.getReturnType()); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /mvp/src/main/java/com/yeungeek/mvp/common/lce/MvpLceView.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.mvp.common.lce; 2 | 3 | 4 | import com.yeungeek.mvp.common.MvpView; 5 | 6 | /** 7 | * A {@link MvpView} that assumes that there are 3 display operation: 8 | *
    9 | *
  • {@link #showLoading(boolean)}: Display a loading animation while loading data in background 10 | * by 11 | * invoking the corresponding presenter method
  • 12 | *

    13 | *

  • {@link #showError(Throwable, boolean)}: Display a error view (a TextView) on the screen if 14 | * an error has occurred while loading data. You can distinguish between a pull-to-refresh error by 15 | * checking the boolean parameter and display the error message in another, more suitable way like 16 | * a 17 | * Toast
  • 18 | *

    19 | *

  • {@link #showContent()}: After the content has been loaded the presenter calls {@link 20 | * #setData(Object)} to fill the view with data. Afterwards, the presenter calls {@link 21 | * #showContent()} to display the data
  • 22 | *
23 | */ 24 | public interface MvpLceView extends MvpView { 25 | 26 | /** 27 | * Display a loading view while loading data in background. 28 | * The loading view must have the id = R.id.loadingView 29 | * 30 | * @param pullToRefresh true, if pull-to-refresh has been invoked loading. 31 | */ 32 | void showLoading(boolean pullToRefresh); 33 | 34 | void showContent(); 35 | 36 | void showError(Throwable e, boolean pullToRefresh); 37 | 38 | void setData(V data); 39 | 40 | /** 41 | * Load the data. Typically invokes the presenter method to load the desired data. 42 | *

43 | * Should not be called from presenter to prevent infinity loops. The method is declared 44 | * in 45 | * the views interface to add support for view state easily. 46 | *

47 | * 48 | * @param pullToRefresh true, if triggered by a pull to refresh. Otherwise false. 49 | */ 50 | void loadData(boolean pullToRefresh); 51 | } 52 | -------------------------------------------------------------------------------- /mvp/src/main/java/com/yeungeek/mvp/core/delegate/ActivityMvpDelegate.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.mvp.core.delegate; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.support.v4.app.FragmentActivity; 6 | 7 | import com.yeungeek.mvp.common.MvpPresenter; 8 | import com.yeungeek.mvp.common.MvpView; 9 | 10 | /** 11 | * A delegate for Activities to attach them to mosbies mvp. 12 | * 13 | *

14 | * The following methods must be invoked from the corresponding Activities lifecycle methods: 15 | *

    16 | *
  • {@link #onCreate(Bundle)}
  • 17 | *
  • {@link #onDestroy()}
  • 18 | *
  • {@link #onPause()}
  • 19 | *
  • {@link #onResume()}
  • 20 | *
  • {@link #onStart()}
  • 21 | *
  • {@link #onStop()}
  • 22 | *
  • {@link #onRestart()}
  • 23 | *
  • {@link #onContentChanged()}
  • 24 | *
  • {@link #onSaveInstanceState(Bundle)}
  • 25 | *
  • {@link #onPostCreate(Bundle)}
  • 26 | *
  • 27 | *
28 | *

29 | * 30 | * @param The type of {@link MvpView} 31 | * @param

The type of {@link MvpPresenter} 32 | * @author Hannes Dorfmann 33 | * @since 1.1.0 34 | */ 35 | public interface ActivityMvpDelegate> { 36 | 37 | /** 38 | * This method must be called from {@link Activity#onCreate(Bundle)}. 39 | * This method internally creates the presenter and attaches the view to it. 40 | */ 41 | void onCreate(Bundle bundle); 42 | 43 | /** 44 | * This method must be called from {@link Activity#onDestroy()}}. 45 | * This method internally detaches the view from presenter 46 | */ 47 | void onDestroy(); 48 | 49 | /** 50 | * This method must be called from {@link Activity#onPause()} 51 | */ 52 | void onPause(); 53 | 54 | /** 55 | * This method must be called from {@link Activity#onResume()} 56 | */ 57 | void onResume(); 58 | 59 | /** 60 | * This method must be called from {@link Activity#onStart()} 61 | */ 62 | void onStart(); 63 | 64 | /** 65 | * This method must be called from {@link Activity#onStop()} 66 | */ 67 | void onStop(); 68 | 69 | /** 70 | * This method must be called from {@link Activity#onRestart()} 71 | */ 72 | void onRestart(); 73 | 74 | /** 75 | * This method must be called from {@link Activity#onContentChanged()} 76 | */ 77 | void onContentChanged(); 78 | 79 | /** 80 | * This method must be called from {@link Activity#onSaveInstanceState(Bundle)} 81 | */ 82 | void onSaveInstanceState(Bundle outState); 83 | 84 | /** 85 | * This method must be called from {@link Activity#onPostCreate(Bundle)} 86 | */ 87 | void onPostCreate(Bundle savedInstanceState); 88 | 89 | /** 90 | * This method must be called from {@link FragmentActivity#onRetainCustomNonConfigurationInstance()} 91 | * 92 | * @return Don't forget to return the value returned by this delegate method 93 | */ 94 | Object onRetainCustomNonConfigurationInstance(); 95 | 96 | /** 97 | * @return the value returned from {@link ActivityMvpDelegateCallback#onRetainNonMosbyCustomNonConfigurationInstance()} 98 | */ 99 | Object getNonMosbyLastCustomNonConfigurationInstance(); 100 | } 101 | -------------------------------------------------------------------------------- /mvp/src/main/java/com/yeungeek/mvp/core/delegate/ActivityMvpDelegateCallback.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.mvp.core.delegate; 2 | 3 | import android.support.v4.app.FragmentActivity; 4 | 5 | import com.yeungeek.mvp.common.MvpPresenter; 6 | import com.yeungeek.mvp.common.MvpView; 7 | 8 | /** 9 | * The MvpDelegate callback that will be called from {@link 10 | * ActivityMvpDelegate}. This interface must be implemented by all 11 | * Activities that you want to support mosby's mvp. 12 | * 13 | * @author Hannes Dorfmann 14 | * @see BaseMvpDelegateCallback 15 | * @since 2.0.0 16 | */ 17 | public interface ActivityMvpDelegateCallback> 18 | extends BaseMvpDelegateCallback { 19 | 20 | /** 21 | * Return any Object holding the desired state to propagate to the next activity instance. Please 22 | * note that mosby internals like the presenter are already saved internally and you don't have 23 | * to take them into account. You can retrieve this value later with {@link 24 | * ActivityMvpDelegate#getNonMosbyLastCustomNonConfigurationInstance()}. 25 | *

26 | *

27 | * This mechanism works pretty the same way as {@link FragmentActivity#onRetainCustomNonConfigurationInstance()} 28 | * and {@link #getNonMosbyLastCustomNonConfigurationInstance()} 29 | *

30 | * 31 | * @return Object holding state. 32 | */ 33 | Object onRetainNonMosbyCustomNonConfigurationInstance(); 34 | 35 | /** 36 | * @return Return the value previously returned from {@link FragmentActivity#onRetainCustomNonConfigurationInstance()}. 37 | */ 38 | Object getLastCustomNonConfigurationInstance(); 39 | 40 | /** 41 | * This method should invoke {@link 42 | * ActivityMvpDelegate#getNonMosbyLastCustomNonConfigurationInstance()}. 43 | *

44 | *

45 | * This method is not really a "callback" method (will not invoked from delegate somehow). 46 | * However, it's part of this interface to ensure that no custom implementation will miss this 47 | * method since this method is the counterpart to {@link #onRetainNonMosbyCustomNonConfigurationInstance()} 48 | *

49 | */ 50 | Object getNonMosbyLastCustomNonConfigurationInstance(); 51 | } 52 | -------------------------------------------------------------------------------- /mvp/src/main/java/com/yeungeek/mvp/core/delegate/ActivityMvpDelegateImpl.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.mvp.core.delegate; 2 | 3 | import android.os.Bundle; 4 | 5 | import com.yeungeek.mvp.common.MvpPresenter; 6 | import com.yeungeek.mvp.common.MvpView; 7 | 8 | /** 9 | * The concrete implementation of {@link} 10 | */ 11 | public class ActivityMvpDelegateImpl> 12 | implements ActivityMvpDelegate { 13 | protected MvpInternalDelegate internalDelegate; 14 | protected ActivityMvpDelegateCallback delegateCallback; 15 | 16 | public ActivityMvpDelegateImpl(ActivityMvpDelegateCallback delegateCallback) { 17 | if (delegateCallback == null) { 18 | throw new NullPointerException("MvpDelegateCallback is null!"); 19 | } 20 | this.delegateCallback = delegateCallback; 21 | } 22 | 23 | /** 24 | * Get the internal delegate. 25 | */ 26 | protected MvpInternalDelegate getInternalDelegate() { 27 | if (internalDelegate == null) { 28 | internalDelegate = new MvpInternalDelegate<>(delegateCallback); 29 | } 30 | 31 | return internalDelegate; 32 | } 33 | 34 | @Override 35 | public void onCreate(Bundle bundle) { 36 | ActivityMvpNonConfigurationInstances nci = 37 | (ActivityMvpNonConfigurationInstances) delegateCallback.getLastCustomNonConfigurationInstance(); 38 | 39 | if (null != nci && null != nci.presenter) { 40 | delegateCallback.setPresenter(nci.presenter); 41 | } else { 42 | getInternalDelegate().createPresenter(); 43 | } 44 | 45 | getInternalDelegate().attachView(); 46 | } 47 | 48 | @Override 49 | public void onDestroy() { 50 | getInternalDelegate().detachView(); 51 | } 52 | 53 | @Override 54 | public void onPause() { 55 | 56 | } 57 | 58 | @Override 59 | public void onResume() { 60 | 61 | } 62 | 63 | @Override 64 | public void onStart() { 65 | 66 | } 67 | 68 | @Override 69 | public void onStop() { 70 | 71 | } 72 | 73 | @Override 74 | public void onRestart() { 75 | 76 | } 77 | 78 | @Override 79 | public void onContentChanged() { 80 | 81 | } 82 | 83 | @Override 84 | public void onSaveInstanceState(Bundle outState) { 85 | 86 | } 87 | 88 | @Override 89 | public void onPostCreate(Bundle savedInstanceState) { 90 | 91 | } 92 | 93 | @Override 94 | public Object onRetainCustomNonConfigurationInstance() { 95 | P presenter = delegateCallback.shouldInstanceBeRetained() ? delegateCallback.getPresenter() : null; 96 | Object nonMosbyConfiguraionInstance = 97 | delegateCallback.onRetainNonMosbyCustomNonConfigurationInstance(); 98 | 99 | if (presenter == null && nonMosbyConfiguraionInstance == null) { 100 | return null; 101 | } 102 | 103 | return new ActivityMvpNonConfigurationInstances<>(presenter, nonMosbyConfiguraionInstance); 104 | } 105 | 106 | @Override 107 | public Object getNonMosbyLastCustomNonConfigurationInstance() { 108 | ActivityMvpNonConfigurationInstances last = 109 | (ActivityMvpNonConfigurationInstances) delegateCallback.getLastCustomNonConfigurationInstance(); 110 | return last == null ? null : last.nonMosbyCustomConfigurationInstance; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /mvp/src/main/java/com/yeungeek/mvp/core/delegate/ActivityMvpNonConfigurationInstances.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.mvp.core.delegate; 2 | 3 | 4 | import com.yeungeek.mvp.common.MvpPresenter; 5 | import com.yeungeek.mvp.common.MvpView; 6 | 7 | /** 8 | * This kind of class is used in Activities to save the presenter in retaining activities. It's a 9 | * mosby internal class. 10 | * 11 | * @author Hannes Dorfmann 12 | * @since 2.0.0 13 | */ 14 | class ActivityMvpNonConfigurationInstances> { 15 | 16 | /** 17 | * The reference to the presenter 18 | */ 19 | P presenter; 20 | 21 | /** 22 | * The reference to the custom non configuration. 23 | */ 24 | Object nonMosbyCustomConfigurationInstance; 25 | 26 | /** 27 | * Constructor 28 | * 29 | * @param presenter The retaining presenter 30 | * @param nonMosbyCustomConfigurationInstance the other custom object 31 | */ 32 | ActivityMvpNonConfigurationInstances(P presenter, Object nonMosbyCustomConfigurationInstance) { 33 | this.presenter = presenter; 34 | this.nonMosbyCustomConfigurationInstance = nonMosbyCustomConfigurationInstance; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /mvp/src/main/java/com/yeungeek/mvp/core/delegate/BaseMvpDelegateCallback.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.mvp.core.delegate; 2 | 3 | import android.app.Activity; 4 | 5 | import com.yeungeek.mvp.common.MvpPresenter; 6 | import com.yeungeek.mvp.common.MvpView; 7 | 8 | /** 9 | * The MvpDelegate callback that will be called from {@link 10 | * FragmentMvpDelegate} or {@link ViewGroupMvpDelegate}. This interface must be implemented by all 11 | * Fragment or android.view.View that you want to support mosbys mvp. Please note that Activties 12 | * need a special callback {@link ActivityMvpDelegateCallback} 13 | * 14 | * @param The type of {@link MvpView} 15 | * @param

The type of {@link MvpPresenter} 16 | */ 17 | public interface BaseMvpDelegateCallback> { 18 | P createPresenter(); 19 | 20 | P getPresenter(); 21 | 22 | void setPresenter(P presenter); 23 | 24 | V getMvpView(); 25 | 26 | /** 27 | * Indicate whether the retain instance feature is enabled by this view or not 28 | */ 29 | boolean isRetainInstance(); 30 | 31 | /** 32 | * Mark this instance as retaining. This means that the feature of a retaining instance is 33 | * enabled. 34 | */ 35 | void setRetainInstance(boolean retainingInstance); 36 | 37 | /** 38 | * Indicates whether or not the the view will be retained during next screen orientation change. 39 | * This boolean flag is used for {@link MvpPresenter#detachView(boolean)} 40 | * as parameter. Usually you should take {@link Activity#isChangingConfigurations()} into 41 | * account. The difference between {@link #shouldInstanceBeRetained()} and {@link 42 | * #isRetainInstance()} is that {@link #isRetainInstance()} indicates that retain instance 43 | * feature is enabled or disabled while {@link #shouldInstanceBeRetained()} indicates if the 44 | * view is going to be destroyed permanently and hence should no more be retained (i.e. Activity 45 | * is finishing and not just screen orientation changing) 46 | * 47 | * @return true if the instance should be retained, otherwise false 48 | */ 49 | boolean shouldInstanceBeRetained(); 50 | } 51 | -------------------------------------------------------------------------------- /mvp/src/main/java/com/yeungeek/mvp/core/delegate/FragmentMvpDelegateImpl.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.mvp.core.delegate; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.support.annotation.Nullable; 6 | import android.view.View; 7 | 8 | import com.yeungeek.mvp.common.MvpPresenter; 9 | import com.yeungeek.mvp.common.MvpView; 10 | 11 | /** 12 | * The default implementation of {@link FragmentMvpDelegate} 13 | */ 14 | public class FragmentMvpDelegateImpl> 15 | implements FragmentMvpDelegate { 16 | protected BaseMvpDelegateCallback delegateCallback; 17 | protected MvpInternalDelegate internalDelegate; 18 | 19 | public FragmentMvpDelegateImpl(final BaseMvpDelegateCallback delegateCallback) { 20 | if (null == delegateCallback) { 21 | throw new NullPointerException("MvpDelegateCallback is null!"); 22 | } 23 | 24 | this.delegateCallback = delegateCallback; 25 | } 26 | 27 | protected MvpInternalDelegate getInternalDelegate() { 28 | if (internalDelegate == null) { 29 | internalDelegate = new MvpInternalDelegate<>(delegateCallback); 30 | } 31 | return internalDelegate; 32 | } 33 | 34 | @Override 35 | public void onCreate(Bundle saved) { 36 | 37 | } 38 | 39 | @Override 40 | public void onDestroy() { 41 | 42 | } 43 | 44 | @Override 45 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { 46 | getInternalDelegate().createPresenter(); 47 | getInternalDelegate().attachView(); 48 | } 49 | 50 | @Override 51 | public void onDestroyView() { 52 | getInternalDelegate().detachView(); 53 | } 54 | 55 | @Override 56 | public void onPause() { 57 | 58 | } 59 | 60 | @Override 61 | public void onResume() { 62 | 63 | } 64 | 65 | @Override 66 | public void onStart() { 67 | 68 | } 69 | 70 | @Override 71 | public void onStop() { 72 | 73 | } 74 | 75 | @Override 76 | public void onActivityCreated(Bundle savedInstanceState) { 77 | 78 | } 79 | 80 | @Override 81 | public void onAttach(Activity activity) { 82 | 83 | } 84 | 85 | @Override 86 | public void onDetach() { 87 | 88 | } 89 | 90 | @Override 91 | public void onSaveInstanceState(Bundle outState) { 92 | 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /mvp/src/main/java/com/yeungeek/mvp/core/delegate/MvpInternalDelegate.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.mvp.core.delegate; 2 | 3 | import com.yeungeek.mvp.common.MvpPresenter; 4 | import com.yeungeek.mvp.common.MvpView; 5 | 6 | /** 7 | * This is just the internal implementation for the delegate. Don't use it by your own. 8 | */ 9 | class MvpInternalDelegate> { 10 | protected BaseMvpDelegateCallback delegateCallback; 11 | 12 | MvpInternalDelegate(BaseMvpDelegateCallback delegateCallback) { 13 | if (delegateCallback == null) { 14 | throw new NullPointerException("MvpDelegateCallback is null!"); 15 | } 16 | this.delegateCallback = delegateCallback; 17 | } 18 | 19 | /** 20 | * Called to create the presenter (if no other one already exisits) 21 | */ 22 | void createPresenter() { 23 | P presenter = delegateCallback.getPresenter(); 24 | if (presenter == null) { 25 | presenter = delegateCallback.createPresenter(); 26 | } 27 | if (presenter == null) { 28 | throw new NullPointerException("Presenter is null! Do you return null in createPresenter()?"); 29 | } 30 | 31 | delegateCallback.setPresenter(presenter); 32 | } 33 | 34 | /** 35 | * Attaches the view to the presenter 36 | */ 37 | void attachView() { 38 | getPresenter().attachView(delegateCallback.getMvpView()); 39 | } 40 | 41 | /** 42 | * Called to detach the view from presenter 43 | */ 44 | void detachView() { 45 | getPresenter().detachView(delegateCallback.shouldInstanceBeRetained()); 46 | } 47 | 48 | private P getPresenter() { 49 | P presenter = delegateCallback.getPresenter(); 50 | if (presenter == null) { 51 | throw new NullPointerException("Presenter returned from getPresenter() is null"); 52 | } 53 | return presenter; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /mvp/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /mvp/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | mvp 3 | 4 | -------------------------------------------------------------------------------- /mvp/src/test/java/com/yeungeek/mvp/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.yeungeek.mvp; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * To work on unit tests, switch the Test Artifact in the Build Variants view. 9 | */ 10 | public class ExampleUnitTest { 11 | @Test 12 | public void addition_isCorrect() throws Exception { 13 | assertEquals(4, 2 + 2); 14 | } 15 | } -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':mvp' 2 | --------------------------------------------------------------------------------