├── settings.gradle ├── app ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_cat.png │ │ │ │ ├── ic_fork.png │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_menu_about.png │ │ │ │ ├── ic_menu_code.png │ │ │ │ ├── ic_menu_theme.png │ │ │ │ └── ic_menu_favorite.png │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_fork.png │ │ │ │ ├── ic_hugh.png │ │ │ │ ├── ic_cancel.png │ │ │ │ ├── ic_github.png │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_menu_about.png │ │ │ │ ├── ic_menu_code.png │ │ │ │ ├── ic_menu_theme.png │ │ │ │ ├── ic_github_circle.png │ │ │ │ └── ic_menu_favorite.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── values │ │ │ │ ├── attrs.xml │ │ │ │ ├── ids.xml │ │ │ │ ├── color_red.xml │ │ │ │ ├── dimens.xml │ │ │ │ ├── color_gray.xml │ │ │ │ ├── color_green.xml │ │ │ │ ├── color_purple.xml │ │ │ │ ├── drawables.xml │ │ │ │ ├── colors.xml │ │ │ │ ├── styles.xml │ │ │ │ └── strings.xml │ │ │ ├── drawable │ │ │ │ ├── bg_rect_pressed.xml │ │ │ │ ├── bg_rect_normal.xml │ │ │ │ ├── shape_def_icon.xml │ │ │ │ ├── side_nav_bar.xml │ │ │ │ ├── layer_app_splash.xml │ │ │ │ ├── side_nav_bar_blue.xml │ │ │ │ ├── side_nav_bar_purple.xml │ │ │ │ ├── side_nav_bar_red.xml │ │ │ │ ├── ic_check_48dp.xml │ │ │ │ ├── side_nav_bar_gray.xml │ │ │ │ ├── bg_common_rect.xml │ │ │ │ ├── ic_star_black_18dp.xml │ │ │ │ ├── ic_visibility_black_20dp.xml │ │ │ │ ├── ic_remove_red_eye_black_24dp.xml │ │ │ │ ├── selectable_item_background.xml │ │ │ │ ├── ic_share_black_20dp.xml │ │ │ │ ├── custom_progress_drawable.xml │ │ │ │ └── vector_logo_text.xml │ │ │ ├── drawable-v21 │ │ │ │ ├── ic_menu_send.xml │ │ │ │ ├── ic_menu_slideshow.xml │ │ │ │ ├── ic_menu_gallery.xml │ │ │ │ ├── ic_menu_manage.xml │ │ │ │ ├── ic_menu_camera.xml │ │ │ │ └── ic_menu_share.xml │ │ │ ├── menu │ │ │ │ ├── activity_theme_menu.xml │ │ │ │ ├── activity_main_menu.xml │ │ │ │ ├── menu_about.xml │ │ │ │ ├── activity_search_menu.xml │ │ │ │ └── activity_main_drawer.xml │ │ │ ├── layout │ │ │ │ ├── search_view.xml │ │ │ │ ├── fragment_tag_cache.xml │ │ │ │ ├── include_toolbar.xml │ │ │ │ ├── activity_search.xml │ │ │ │ ├── activity_theme.xml │ │ │ │ ├── activity_favorites.xml │ │ │ │ ├── item_other.xml │ │ │ │ ├── fragment_repos.xml │ │ │ │ ├── activity_languages.xml │ │ │ │ ├── item_other_langs_header.xml │ │ │ │ ├── content_about.xml │ │ │ │ ├── activity_main.xml │ │ │ │ ├── item_lang.xml │ │ │ │ ├── content_main.xml │ │ │ │ ├── nav_header_main.xml │ │ │ │ ├── item_theme.xml │ │ │ │ ├── item_selected_langs_header.xml │ │ │ │ ├── activity_webview.xml │ │ │ │ ├── app_bar_main.xml │ │ │ │ ├── item_trends.xml │ │ │ │ ├── activity_about.xml │ │ │ │ └── item_repo.xml │ │ │ ├── values-v21 │ │ │ │ └── styles.xml │ │ │ └── values-w820dp │ │ │ │ └── dimens.xml │ │ ├── java │ │ │ └── net │ │ │ │ └── angrycode │ │ │ │ └── wehub │ │ │ │ ├── event │ │ │ │ ├── ThemeChangeEvent.java │ │ │ │ └── LangEditedEvent.java │ │ │ │ ├── bean │ │ │ │ ├── Trends.java │ │ │ │ ├── Langs.java │ │ │ │ ├── Lang.java │ │ │ │ ├── AppUpdate.java │ │ │ │ ├── GsonBuilder.java │ │ │ │ ├── Owner.java │ │ │ │ └── TrendingRepo.java │ │ │ │ ├── ui │ │ │ │ ├── component │ │ │ │ │ ├── drag │ │ │ │ │ │ ├── OnItemMoveListener.java │ │ │ │ │ │ ├── OnDragVHListener.java │ │ │ │ │ │ └── ItemDragHelperCallback.java │ │ │ │ │ ├── LangGridLayoutManager.java │ │ │ │ │ ├── WebViewFallback.java │ │ │ │ │ ├── WebViewActivity.java │ │ │ │ │ └── DividerItemDecoration.java │ │ │ │ ├── adapter │ │ │ │ │ ├── ViewHolder.java │ │ │ │ │ ├── MainPagerAdapter.java │ │ │ │ │ ├── ReposAdapter.java │ │ │ │ │ ├── TrendsAdapter.java │ │ │ │ │ ├── BaseSimpleRecycleAdapter.java │ │ │ │ │ └── ThemeAdapter.java │ │ │ │ ├── fragment │ │ │ │ │ ├── BaseFragment.java │ │ │ │ │ ├── TagCacheFragment.java │ │ │ │ │ └── ReposFragment.java │ │ │ │ └── activity │ │ │ │ │ ├── AboutActivity.java │ │ │ │ │ ├── ThemeActivity.java │ │ │ │ │ ├── LanguagesActivity.java │ │ │ │ │ ├── SearchActivity.java │ │ │ │ │ ├── FavoritesActivity.java │ │ │ │ │ └── BaseActivity.java │ │ │ │ ├── mvp │ │ │ │ ├── view │ │ │ │ │ ├── LangView.java │ │ │ │ │ ├── MainView.java │ │ │ │ │ ├── FavorView.java │ │ │ │ │ ├── RepositoriesView.java │ │ │ │ │ ├── TrendingView.java │ │ │ │ │ └── BaseView.java │ │ │ │ └── presenter │ │ │ │ │ ├── Presenter.java │ │ │ │ │ ├── FavorPresenter.java │ │ │ │ │ ├── MainPresenter.java │ │ │ │ │ ├── ReposPresenter.java │ │ │ │ │ ├── LangPresenter.java │ │ │ │ │ └── TrendsPresenter.java │ │ │ │ ├── utils │ │ │ │ ├── SnackbarUtils.java │ │ │ │ ├── LogUtils.java │ │ │ │ ├── GlideUtils.java │ │ │ │ ├── IntentUtils.java │ │ │ │ ├── ToastUtils.java │ │ │ │ ├── Utils.java │ │ │ │ ├── WebUtils.java │ │ │ │ ├── ThemeUtils.java │ │ │ │ ├── MD5Utils.java │ │ │ │ ├── JsonCacheUtils.java │ │ │ │ ├── TagPrefUtils.java │ │ │ │ ├── AppUtils.java │ │ │ │ ├── LangsUtils.java │ │ │ │ ├── Keyboard.java │ │ │ │ └── FileUtils.java │ │ │ │ ├── api │ │ │ │ ├── ReposApi.java │ │ │ │ ├── CacheInterceptor.java │ │ │ │ ├── LangApi.java │ │ │ │ ├── FavorApi.java │ │ │ │ ├── BaseTrendApi.java │ │ │ │ ├── TrendingApi.java │ │ │ │ └── BaseApi.java │ │ │ │ └── WeCodeApp.java │ │ ├── assets │ │ │ └── langs │ │ └── AndroidManifest.xml │ ├── test │ │ └── java │ │ │ └── net │ │ │ └── angrycode │ │ │ └── githubtrending │ │ │ └── ExampleUnitTest.java │ └── androidTest │ │ └── java │ │ └── net │ │ └── angrycode │ │ └── githubtrending │ │ └── ExampleInstrumentedTest.java ├── build.gradle └── proguard-rules.pro ├── gradle └── wrapper │ └── gradle-wrapper.jar ├── .gitignore ├── customtabs ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── org │ └── chromium │ └── customtabsclient │ └── shared │ ├── KeepAliveService.java │ ├── ServiceConnectionCallback.java │ └── ServiceConnection.java ├── README.md └── gradlew.bat /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':customtabs' 2 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /.gradle 3 | /.idea 4 | 5 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hylinux1024/WeCode/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_cat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hylinux1024/WeCode/HEAD/app/src/main/res/mipmap-hdpi/ic_cat.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_fork.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hylinux1024/WeCode/HEAD/app/src/main/res/mipmap-hdpi/ic_fork.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_fork.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hylinux1024/WeCode/HEAD/app/src/main/res/mipmap-xhdpi/ic_fork.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_hugh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hylinux1024/WeCode/HEAD/app/src/main/res/mipmap-xhdpi/ic_hugh.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hylinux1024/WeCode/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_cancel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hylinux1024/WeCode/HEAD/app/src/main/res/mipmap-xhdpi/ic_cancel.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hylinux1024/WeCode/HEAD/app/src/main/res/mipmap-xhdpi/ic_github.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_menu_about.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hylinux1024/WeCode/HEAD/app/src/main/res/mipmap-hdpi/ic_menu_about.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_menu_code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hylinux1024/WeCode/HEAD/app/src/main/res/mipmap-hdpi/ic_menu_code.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_menu_theme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hylinux1024/WeCode/HEAD/app/src/main/res/mipmap-hdpi/ic_menu_theme.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hylinux1024/WeCode/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_menu_about.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hylinux1024/WeCode/HEAD/app/src/main/res/mipmap-xhdpi/ic_menu_about.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_menu_code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hylinux1024/WeCode/HEAD/app/src/main/res/mipmap-xhdpi/ic_menu_code.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_menu_theme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hylinux1024/WeCode/HEAD/app/src/main/res/mipmap-xhdpi/ic_menu_theme.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hylinux1024/WeCode/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hylinux1024/WeCode/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_menu_favorite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hylinux1024/WeCode/HEAD/app/src/main/res/mipmap-hdpi/ic_menu_favorite.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_github_circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hylinux1024/WeCode/HEAD/app/src/main/res/mipmap-xhdpi/ic_github_circle.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_menu_favorite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hylinux1024/WeCode/HEAD/app/src/main/res/mipmap-xhdpi/ic_menu_favorite.png -------------------------------------------------------------------------------- /app/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | /.idea 7 | .DS_Store 8 | /build 9 | /captures 10 | .externalNativeBuild 11 | *.keystore 12 | *.properties 13 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/event/ThemeChangeEvent.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.event; 2 | 3 | /** 4 | * Created by lancelot on 2016/12/18. 5 | */ 6 | 7 | public class ThemeChangeEvent { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/bean/Trends.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.bean; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Created by lancelot on 2016/12/2. 7 | */ 8 | 9 | public class Trends { 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/ui/component/drag/OnItemMoveListener.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.ui.component.drag; 2 | 3 | public interface OnItemMoveListener { 4 | boolean onItemMove(int fromPosition, int toPosition); 5 | } 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_rect_pressed.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_rect_normal.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_def_icon.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/ic_menu_send.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/side_nav_bar.xml: -------------------------------------------------------------------------------- 1 | 3 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/menu/activity_theme_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 8 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/mvp/view/LangView.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.mvp.view; 2 | 3 | import net.angrycode.wehub.bean.Langs; 4 | 5 | /** 6 | * Created by lancelot on 2016/12/17. 7 | */ 8 | 9 | public interface LangView extends BaseView { 10 | void onGetLangsFinished(Langs langs); 11 | 12 | void onSetLangsFinished(Langs langs); 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/layer_app_splash.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/side_nav_bar_blue.xml: -------------------------------------------------------------------------------- 1 | 3 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/layout/search_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/side_nav_bar_purple.xml: -------------------------------------------------------------------------------- 1 | 3 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/side_nav_bar_red.xml: -------------------------------------------------------------------------------- 1 | 3 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_check_48dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/side_nav_bar_gray.xml: -------------------------------------------------------------------------------- 1 | 3 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_common_rect.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/menu/activity_main_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/mvp/view/MainView.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.mvp.view; 2 | 3 | import net.angrycode.wehub.bean.AppUpdate; 4 | import net.angrycode.wehub.bean.Langs; 5 | 6 | /** 7 | * Created by lancelot on 2016/12/17. 8 | */ 9 | 10 | public interface MainView extends BaseView { 11 | 12 | void onGetLangsFinished(Langs langs); 13 | 14 | void onCheckAppUpdateFinish(AppUpdate appUpdate); 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/mvp/view/FavorView.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.mvp.view; 2 | 3 | import net.angrycode.wehub.bean.TrendingRepo; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Created by lancelot on 2016/12/18. 9 | */ 10 | 11 | public interface FavorView extends BaseView { 12 | 13 | void onGetFavorListFinish(List repoList); 14 | 15 | void onDelFavorFinish(TrendingRepo repo); 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/mvp/view/RepositoriesView.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.mvp.view; 2 | 3 | import net.angrycode.wehub.bean.Repos; 4 | import net.angrycode.wehub.bean.TrendingRepo; 5 | 6 | /** 7 | * Created by lancelot on 2016/11/13. 8 | */ 9 | 10 | public interface RepositoriesView extends BaseView { 11 | 12 | void onGetRepositories(Repos repos); 13 | 14 | void onAddFavorFinish(TrendingRepo repo); 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/mvp/view/TrendingView.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.mvp.view; 2 | 3 | import net.angrycode.wehub.bean.TrendingRepo; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Created by lancelot on 2016/12/3. 9 | */ 10 | 11 | public interface TrendingView extends BaseView { 12 | 13 | void onGetTrendingsFinish(List repos); 14 | 15 | void onAddFavorFinish(TrendingRepo repo); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/ui/component/drag/OnDragVHListener.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.ui.component.drag; 2 | 3 | /** 4 | * ViewHolder 被选中 以及 拖拽释放 触发监听器 5 | * Created by YoKeyword on 15/12/29. 6 | */ 7 | public interface OnDragVHListener { 8 | /** 9 | * Item被选中时触发 10 | */ 11 | void onItemSelected(); 12 | 13 | 14 | /** 15 | * Item在拖拽结束/滑动结束后触发 16 | */ 17 | void onItemFinish(); 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_star_black_18dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/event/LangEditedEvent.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.event; 2 | 3 | import net.angrycode.wehub.bean.Langs; 4 | 5 | /** 6 | * Created by lancelot on 2016/12/17. 7 | */ 8 | 9 | public class LangEditedEvent { 10 | private Langs langs; 11 | 12 | public LangEditedEvent(Langs langs) { 13 | this.langs = langs; 14 | } 15 | 16 | public Langs getLangs() { 17 | return langs; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/ic_menu_slideshow.xml: -------------------------------------------------------------------------------- 1 | 3 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/ic_menu_gallery.xml: -------------------------------------------------------------------------------- 1 | 3 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_about.xml: -------------------------------------------------------------------------------- 1 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/ic_menu_manage.xml: -------------------------------------------------------------------------------- 1 | 3 | 5 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/utils/SnackbarUtils.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.utils; 2 | 3 | import android.support.design.widget.Snackbar; 4 | import android.view.View; 5 | 6 | /** 7 | * Created by lancelot on 2016/11/14. 8 | */ 9 | 10 | public class SnackbarUtils { 11 | private SnackbarUtils() { 12 | 13 | } 14 | 15 | public static final void showSnack(View view, String message) { 16 | Snackbar.make(view, message, Snackbar.LENGTH_SHORT) 17 | .show(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/res/values/color_red.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #E91E63 4 | #C2185B 5 | #F8BBD0 6 | #FF5252 7 | #212121 8 | #757575 9 | #FFFFFF 10 | #BDBDBD 11 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | 10dp 7 | 160dp 8 | 16dp 9 | 200dp 10 | 16dp 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/mvp/view/BaseView.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.mvp.view; 2 | 3 | import android.app.Activity; 4 | 5 | public interface BaseView { 6 | Activity getActivity(); 7 | 8 | /** 9 | * 请求中 10 | */ 11 | void onRequestLoading(); 12 | 13 | /** 14 | * 请求结束 15 | */ 16 | void onRequestFinished(); 17 | 18 | /** 19 | * 请求出错 20 | * @param code 21 | * @param message 22 | */ 23 | void onRequestError(int code, String message); 24 | 25 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_tag_cache.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/values/color_gray.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #607D8B 4 | #455A64 5 | #607D8B 6 | #607D8B 7 | #212121 8 | #757575 9 | #FFFFFF 10 | #BDBDBD 11 | -------------------------------------------------------------------------------- /app/src/main/res/values/color_green.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #4CAF50 4 | #388E3C 5 | #C8E6C9 6 | #8BC34A 7 | #212121 8 | #757575 9 | #FFFFFF 10 | #BDBDBD 11 | -------------------------------------------------------------------------------- /app/src/test/java/net/angrycode/githubtrending/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.githubtrending; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/res/menu/activity_search_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/values/color_purple.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #673AB7 4 | #512DA8 5 | #D1C4E9 6 | #E040FB 7 | #212121 8 | #757575 9 | #FFFFFF 10 | #BDBDBD 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_visibility_black_20dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/utils/LogUtils.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.utils; 2 | 3 | import android.util.Log; 4 | 5 | /** 6 | * Created by lancelot on 2016/11/14. 7 | */ 8 | 9 | public class LogUtils { 10 | private static final String TAG = "wecode"; 11 | 12 | public static void log(String message) { 13 | Log.d(TAG, message); 14 | } 15 | 16 | public static void e(Exception e) { 17 | Log.e(TAG, e.getMessage()); 18 | } 19 | 20 | public static void e(Throwable throwable) { 21 | Log.e(TAG, throwable.getMessage()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_remove_red_eye_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/ic_menu_camera.xml: -------------------------------------------------------------------------------- 1 | 3 | 5 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/utils/GlideUtils.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.utils; 2 | 3 | import android.content.Context; 4 | import android.widget.ImageView; 5 | 6 | import com.bumptech.glide.Glide; 7 | 8 | import net.angrycode.wehub.R; 9 | 10 | /** 11 | * Created by lancelot on 2016/11/14. 12 | */ 13 | 14 | public class GlideUtils { 15 | private GlideUtils() { 16 | 17 | } 18 | 19 | public static void loadImage(Context context, String url, ImageView imageView) { 20 | Glide.with(context).load(url).placeholder(R.mipmap.ic_github).crossFade().into(imageView); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/selectable_item_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/layout/include_toolbar.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/utils/IntentUtils.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.utils; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | 7 | /** 8 | * Created by lancelot on 2016/12/10. 9 | */ 10 | 11 | public class IntentUtils { 12 | public static void launch(Context context, Class clazz) { 13 | Intent intent = new Intent(context, clazz); 14 | if (context instanceof Activity) { 15 | 16 | } else { 17 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 18 | } 19 | context.startActivity(intent); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_search.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_theme.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/values/drawables.xml: -------------------------------------------------------------------------------- 1 | 2 | @android:drawable/ic_menu_camera 3 | @android:drawable/ic_menu_gallery 4 | @android:drawable/ic_menu_slideshow 5 | @android:drawable/ic_menu_manage 6 | @android:drawable/ic_menu_share 7 | @android:drawable/ic_menu_send 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_favorites.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_other.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/utils/ToastUtils.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.utils; 2 | 3 | import android.content.Context; 4 | import android.support.annotation.StringRes; 5 | import android.widget.Toast; 6 | 7 | /** 8 | * Created by lancelot on 16/6/21. 9 | */ 10 | public class ToastUtils { 11 | /** 12 | * @param context 13 | * @param msg 14 | */ 15 | public static void show(Context context, String msg) { 16 | Toast.makeText(context, msg, Toast.LENGTH_SHORT).show(); 17 | } 18 | 19 | public static void show(Context context, @StringRes int msgRes) { 20 | Toast.makeText(context, msgRes, Toast.LENGTH_SHORT).show(); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /customtabs/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion "25.0.0" 6 | 7 | defaultConfig { 8 | minSdkVersion 15 9 | targetSdkVersion 25 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 'com.android.support:appcompat-v7:25.0.1' 24 | provided 'com.android.support:customtabs:25.0.1' 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/ic_menu_share.xml: -------------------------------------------------------------------------------- 1 | 3 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_repos.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/api/ReposApi.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.api; 2 | 3 | import net.angrycode.wehub.bean.Repos; 4 | 5 | import retrofit2.http.GET; 6 | import retrofit2.http.Query; 7 | import rx.Observable; 8 | 9 | /** 10 | * 受欢迎库的趋势列表 11 | * Created by lancelot on 2016/11/13. 12 | */ 13 | 14 | public class ReposApi extends BaseApi { 15 | private interface ReposService { 16 | @GET("/search/repositories") 17 | Observable getRepositories(@Query("q") String query); 18 | } 19 | 20 | protected static final ReposService service = getRetrofit().create(ReposService.class); 21 | 22 | public static Observable getRepositories(String query) { 23 | return service.getRepositories(query); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_share_black_20dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_languages.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_other_langs_header.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 14 | 15 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #2196F3 4 | #1976D2 5 | #BBDEFB 6 | #00BCD4 7 | #212121 8 | #757575 9 | #FFFFFF 10 | #BDBDBD 11 | 12 | #F7F8FB 13 | #CFD0E8 14 | #AFFFFFFF 15 | #72A1D0 16 | #7977B2 17 | #C4B4D6 18 | #C8B8B6 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/api/CacheInterceptor.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.api; 2 | 3 | import java.io.IOException; 4 | 5 | import okhttp3.Interceptor; 6 | import okhttp3.Request; 7 | import okhttp3.Response; 8 | 9 | public class CacheInterceptor implements Interceptor { 10 | public CacheInterceptor() { 11 | } 12 | 13 | 14 | @Override 15 | public Response intercept(Chain chain) throws IOException { 16 | Request request = chain.request(); 17 | Response response = chain.proceed(request); 18 | Response newResponse = response.newBuilder() 19 | .removeHeader("Pragma") 20 | .removeHeader("Cache-Control") 21 | //cache for 2 hours 22 | .header("Cache-Control", "max-age=" + 3600 * 2) 23 | .build(); 24 | return newResponse; 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/utils/Utils.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.utils; 2 | 3 | import android.content.ClipboardManager; 4 | import android.content.Context; 5 | import android.os.Build; 6 | 7 | import net.angrycode.wehub.R; 8 | 9 | /** 10 | * Created by lancelot on 2016/12/20. 11 | */ 12 | 13 | public class Utils { 14 | public static void copyText(Context context,String text) { 15 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { 16 | android.text.ClipboardManager clipboard = (android.text.ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); 17 | clipboard.setText(text); 18 | } else { 19 | ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); 20 | clipboard.setText(text); 21 | } 22 | ToastUtils.show(context, R.string.text_copied); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/androidTest/java/net/angrycode/githubtrending/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.githubtrending; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("net.angrycode.githubtrending", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /customtabs/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/ui/adapter/ViewHolder.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.ui.adapter; 2 | 3 | import android.support.annotation.IdRes; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.util.SparseArray; 6 | import android.view.View; 7 | 8 | /** 9 | * 10 | * Created by lancelot on 2016/12/2. 11 | */ 12 | 13 | public class ViewHolder extends RecyclerView.ViewHolder { 14 | View itemView; 15 | SparseArray views = new SparseArray<>(); 16 | public ViewHolder(View itemView) { 17 | super(itemView); 18 | this.itemView = itemView; 19 | } 20 | 21 | @SuppressWarnings("unchecked") 22 | public T getView(@IdRes int resId) { 23 | 24 | View v = views.get(resId); 25 | if (null == v && itemView!=null) { 26 | v = itemView.findViewById(resId); 27 | views.put(resId, v); 28 | } 29 | return (T) v; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/utils/WebUtils.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.utils; 2 | 3 | import android.app.Activity; 4 | import android.net.Uri; 5 | import android.support.customtabs.CustomTabsIntent; 6 | import android.support.v4.content.ContextCompat; 7 | 8 | import net.angrycode.wehub.R; 9 | import net.angrycode.wehub.ui.component.CustomTabActivityHelper; 10 | import net.angrycode.wehub.ui.component.WebViewFallback; 11 | 12 | /** 13 | * Created by lancelot on 2016/12/4. 14 | */ 15 | 16 | public class WebUtils { 17 | public static void openUrl(Activity context, String url) { 18 | CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder(); 19 | builder.setToolbarColor(ContextCompat.getColor(context, R.color.primary)); 20 | CustomTabsIntent customTabsIntent = builder.build(); 21 | CustomTabActivityHelper.openCustomTab(context, customTabsIntent, Uri.parse(url), new WebViewFallback()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/utils/ThemeUtils.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.utils; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | 6 | import net.angrycode.wehub.R; 7 | 8 | /** 9 | * Created by lancelot on 2016/12/18. 10 | */ 11 | 12 | public class ThemeUtils { 13 | private static final String FILE_NAME = "theme"; 14 | private static final String KEY = "key"; 15 | 16 | private ThemeUtils() { 17 | 18 | } 19 | 20 | public static void putTheme(Context context, int theme) { 21 | SharedPreferences sp = context.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE); 22 | sp.edit().putInt(KEY, theme).apply(); 23 | } 24 | 25 | public static int getTheme(Context context) { 26 | SharedPreferences sp = context.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE); 27 | int theme = sp.getInt(KEY, R.style.AppTheme_Green); 28 | return theme; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/ui/component/LangGridLayoutManager.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.ui.component; 2 | 3 | import android.content.Context; 4 | import android.support.v7.widget.GridLayoutManager; 5 | 6 | import net.angrycode.wehub.ui.adapter.LangAdapter; 7 | 8 | /** 9 | * Created by lancelot on 2016/12/17. 10 | */ 11 | 12 | public class LangGridLayoutManager extends GridLayoutManager { 13 | 14 | public LangGridLayoutManager(Context context, final LangAdapter adapter, int spanCount) { 15 | super(context, spanCount); 16 | setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { 17 | @Override 18 | public int getSpanSize(int position) { 19 | int viewType = adapter.getItemViewType(position); 20 | return viewType == LangAdapter.VIEW_TYPE_LANG_SEL_ITEM || viewType == LangAdapter.VIEW_TYPE_LANG_UNSEL_ITEM ? 1 : 4; 21 | } 22 | }); 23 | } 24 | 25 | 26 | } 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/layout/content_about.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/ui/fragment/BaseFragment.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.ui.fragment; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.v4.app.Fragment; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | 10 | import butterknife.ButterKnife; 11 | 12 | /** 13 | * Created by lancelot on 2016/11/15. 14 | */ 15 | 16 | public abstract class BaseFragment extends Fragment { 17 | @Override 18 | public final View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 19 | int resLayoutId = getLayoutResource(); 20 | if (resLayoutId != 0) { 21 | View view = inflater.inflate(resLayoutId, container, false); 22 | ButterKnife.bind(this, view); 23 | return view; 24 | } 25 | return null; 26 | } 27 | 28 | public abstract int getLayoutResource(); 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/bean/Langs.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.bean; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Created by lancelot on 2016/12/17. 9 | */ 10 | 11 | public class Langs { 12 | @SerializedName("sel") 13 | private List selList; 14 | @SerializedName("unsel") 15 | private List unselList; 16 | 17 | public Langs() { 18 | } 19 | 20 | public Langs(List selList, List unselList) { 21 | this.selList = selList; 22 | this.unselList = unselList; 23 | } 24 | 25 | public List getSelList() { 26 | return selList; 27 | } 28 | 29 | public void setSelList(List selList) { 30 | this.selList = selList; 31 | } 32 | 33 | public List getUnselList() { 34 | return unselList; 35 | } 36 | 37 | public void setUnselList(List unselList) { 38 | this.unselList = unselList; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/WeCodeApp.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub; 2 | 3 | import android.app.Application; 4 | 5 | import net.angrycode.wehub.bean.DaoMaster; 6 | import net.angrycode.wehub.bean.DaoSession; 7 | 8 | import org.greenrobot.greendao.database.Database; 9 | 10 | /** 11 | * Created by lancelot on 2016/11/13. 12 | */ 13 | 14 | public class WeCodeApp extends Application { 15 | 16 | private static WeCodeApp sInstance; 17 | private DaoSession daoSession; 18 | 19 | @Override 20 | public void onCreate() { 21 | super.onCreate(); 22 | sInstance = this; 23 | 24 | initGreenDao(); 25 | } 26 | 27 | private void initGreenDao() { 28 | DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, "repos-db"); 29 | Database db = helper.getWritableDb(); 30 | daoSession = new DaoMaster(db).newSession(); 31 | } 32 | 33 | public static WeCodeApp get() { 34 | return sInstance; 35 | } 36 | 37 | public DaoSession getDaoSession() { 38 | return daoSession; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 15 | 16 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_lang.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 18 | 19 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/utils/MD5Utils.java: -------------------------------------------------------------------------------- 1 | 2 | package net.angrycode.wehub.utils; 3 | 4 | import java.security.MessageDigest; 5 | 6 | public class MD5Utils { 7 | private static final String TAG = "MD5Utils"; 8 | 9 | public final static String MD5(String s) { 10 | if (s == null) 11 | return null; 12 | char hexDigits[] = { 13 | '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' 14 | }; 15 | try { 16 | byte[] strTemp = s.getBytes(); 17 | MessageDigest mdTemp = MessageDigest.getInstance("MD5"); 18 | mdTemp.update(strTemp); 19 | byte[] md = mdTemp.digest(); 20 | int j = md.length; 21 | char str[] = new char[j * 2]; 22 | int k = 0; 23 | for (int i = 0; i < j; i++) { 24 | byte byte0 = md[i]; 25 | str[k++] = hexDigits[byte0 >>> 4 & 0xf]; 26 | str[k++] = hexDigits[byte0 & 0xf]; 27 | } 28 | return new String(str); 29 | } catch (Exception e) { 30 | e.printStackTrace(); 31 | } 32 | return null; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/res/menu/activity_main_drawer.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 12 | 16 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/api/LangApi.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.api; 2 | 3 | import android.content.Context; 4 | 5 | import net.angrycode.wehub.bean.GsonBuilder; 6 | import net.angrycode.wehub.bean.Langs; 7 | import net.angrycode.wehub.utils.LangsUtils; 8 | 9 | import rx.Observable; 10 | 11 | /** 12 | * Created by lancelot on 2016/12/17. 13 | */ 14 | 15 | public class LangApi { 16 | private LangApi() { 17 | } 18 | 19 | public static Observable getLangs(Context context) { 20 | Observable observable = Observable.create(subscriber -> { 21 | String json = LangsUtils.readLangJson(context); 22 | Langs langs = GsonBuilder.parseJson(json, Langs.class); 23 | subscriber.onNext(langs); 24 | subscriber.onCompleted(); 25 | }); 26 | return observable; 27 | } 28 | 29 | public static Observable setLangs(Context context, Langs langs) { 30 | Observable observable = Observable.create(subscriber -> { 31 | LangsUtils.write2sdcard(context, langs); 32 | subscriber.onNext(langs); 33 | subscriber.onCompleted(); 34 | }); 35 | return observable; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /customtabs/src/main/java/org/chromium/customtabsclient/shared/KeepAliveService.java: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package org.chromium.customtabsclient.shared; 16 | 17 | import android.app.Service; 18 | import android.content.Intent; 19 | import android.os.Binder; 20 | import android.os.IBinder; 21 | 22 | /** 23 | * Empty service used by the custom tab to bind to, raising the application's importance. 24 | */ 25 | public class KeepAliveService extends Service { 26 | private static final Binder sBinder = new Binder(); 27 | 28 | @Override 29 | public IBinder onBind(Intent intent) { 30 | return sBinder; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/bean/Lang.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.bean; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.JsonSyntaxException; 5 | 6 | import net.angrycode.wehub.utils.LogUtils; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | /** 12 | * Created by lancelot on 2016/12/17. 13 | */ 14 | 15 | public class Lang { 16 | private String key; 17 | private String name; 18 | 19 | public Lang() { 20 | } 21 | 22 | public Lang(String key, String name) { 23 | this.key = key; 24 | this.name = name; 25 | } 26 | 27 | public String getKey() { 28 | return key; 29 | } 30 | 31 | public void setKey(String key) { 32 | this.key = key; 33 | } 34 | 35 | public String getName() { 36 | return name; 37 | } 38 | 39 | public void setName(String name) { 40 | this.name = name; 41 | } 42 | 43 | public static List buildLangs(String json) { 44 | List list = new ArrayList<>(); 45 | try { 46 | Gson gson = new Gson(); 47 | gson.fromJson(json, Lang.class); 48 | } catch (JsonSyntaxException e) { 49 | LogUtils.log(e.getMessage()); 50 | } 51 | return list; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/api/FavorApi.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.api; 2 | 3 | import android.content.Context; 4 | import android.text.TextUtils; 5 | 6 | import net.angrycode.wehub.bean.GsonBuilder; 7 | import net.angrycode.wehub.bean.Repos; 8 | import net.angrycode.wehub.bean.TrendingRepo; 9 | import net.angrycode.wehub.utils.FileUtils; 10 | 11 | import java.io.File; 12 | import java.util.List; 13 | 14 | import rx.Observable; 15 | 16 | /** 17 | * Created by lancelot on 2016/12/17. 18 | */ 19 | 20 | public class FavorApi { 21 | public static final String FILE_NAME = "favor"; 22 | 23 | private FavorApi() { 24 | } 25 | 26 | public Observable> getFavorList(Context context) { 27 | Observable> observable = Observable.create(subscriber -> { 28 | String json = read(context); 29 | List list = GsonBuilder.buildArray(json, TrendingRepo.class); 30 | subscriber.onNext(list); 31 | subscriber.onCompleted(); 32 | }); 33 | return observable; 34 | } 35 | 36 | 37 | private static String read(Context context) { 38 | File file = new File(context.getExternalCacheDir(), FILE_NAME); 39 | return FileUtils.readFile(file.getAbsolutePath()); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /customtabs/src/main/java/org/chromium/customtabsclient/shared/ServiceConnectionCallback.java: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package org.chromium.customtabsclient.shared; 16 | 17 | import android.support.customtabs.CustomTabsClient; 18 | 19 | /** 20 | * Callback for events when connecting and disconnecting from Custom Tabs Service. 21 | */ 22 | public interface ServiceConnectionCallback { 23 | /** 24 | * Called when the service is connected. 25 | * @param client a CustomTabsClient 26 | */ 27 | void onServiceConnected(CustomTabsClient client); 28 | 29 | /** 30 | * Called when the service is disconnected. 31 | */ 32 | void onServiceDisconnected(); 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/ui/component/WebViewFallback.java: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package net.angrycode.wehub.ui.component; 15 | 16 | import android.app.Activity; 17 | import android.content.Intent; 18 | import android.net.Uri; 19 | 20 | /** 21 | * A Fallback that opens a Webview when Custom Tabs is not available 22 | */ 23 | public class WebViewFallback implements CustomTabActivityHelper.CustomTabFallback { 24 | @Override 25 | public void openUri(Activity activity, Uri uri) { 26 | Intent intent = new Intent(activity, WebViewActivity.class); 27 | intent.putExtra(WebViewActivity.EXTRA_URL, uri.toString()); 28 | activity.startActivity(intent); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/ui/activity/AboutActivity.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.ui.activity; 2 | 3 | import android.os.Bundle; 4 | import android.support.design.widget.FloatingActionButton; 5 | import android.support.design.widget.Snackbar; 6 | import android.view.View; 7 | import android.widget.TextView; 8 | 9 | import net.angrycode.wehub.BuildConfig; 10 | import net.angrycode.wehub.R; 11 | 12 | import butterknife.BindView; 13 | 14 | public class AboutActivity extends BaseActivity { 15 | 16 | @BindView(R.id.tv_version) 17 | TextView tvVersion; 18 | @BindView(R.id.fab) 19 | FloatingActionButton fab; 20 | 21 | @Override 22 | protected int getLayoutResource() { 23 | return R.layout.activity_about; 24 | } 25 | 26 | @Override 27 | protected void onCreate(Bundle savedInstanceState) { 28 | super.onCreate(savedInstanceState); 29 | init(); 30 | fab.setOnClickListener(new View.OnClickListener() { 31 | @Override 32 | public void onClick(View view) { 33 | Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) 34 | .setAction("Action", null).show(); 35 | } 36 | }); 37 | } 38 | 39 | private void init() { 40 | tvVersion.setText("V"+ BuildConfig.VERSION_NAME); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/ui/adapter/MainPagerAdapter.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.ui.adapter; 2 | 3 | import android.support.v4.app.Fragment; 4 | import android.support.v4.app.FragmentManager; 5 | import android.support.v4.app.FragmentStatePagerAdapter; 6 | 7 | import net.angrycode.wehub.bean.Lang; 8 | import net.angrycode.wehub.ui.fragment.TrendsFragment; 9 | 10 | import java.util.List; 11 | 12 | 13 | /** 14 | * Created by lancelot on 2016/11/15. 15 | */ 16 | 17 | public class MainPagerAdapter extends FragmentStatePagerAdapter { 18 | // private String[] mTabTitles = new String[]{"All", "C", "C++", "HTML", "Java", "JavaScript", "Kotlin", "Python", "PHP", "Swift"}; 19 | private List mLangs; 20 | 21 | public MainPagerAdapter(FragmentManager fm, List list) { 22 | super(fm); 23 | mLangs = list; 24 | } 25 | public void setLangs(List langs){ 26 | mLangs = langs; 27 | notifyDataSetChanged(); 28 | } 29 | @Override 30 | public Fragment getItem(int position) { 31 | return TrendsFragment.newInstance(mLangs.get(position).getKey()); 32 | } 33 | 34 | @Override 35 | public int getCount() { 36 | return mLangs.size(); 37 | } 38 | 39 | @Override 40 | public CharSequence getPageTitle(int position) { 41 | return mLangs.get(position).getName(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/utils/JsonCacheUtils.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.utils; 2 | 3 | import android.content.Context; 4 | import android.support.annotation.NonNull; 5 | import android.text.TextUtils; 6 | 7 | import java.io.File; 8 | 9 | /** 10 | * 接口缓存数据 11 | * Created by lancelot on 2016/12/3. 12 | */ 13 | 14 | public class JsonCacheUtils { 15 | 16 | /** 17 | * 读取 18 | * 19 | * @param lang 20 | * @return 21 | */ 22 | public static String readFile(Context context,String lang) { 23 | if (TextUtils.isEmpty(lang)) { 24 | return null; 25 | } 26 | String fileName = getFileName(context, lang); 27 | String txt = FileUtils.readFile(fileName); 28 | return txt; 29 | } 30 | 31 | /** 32 | * @param json 33 | * @param lang 34 | */ 35 | public static void write2File(Context context,String json, String lang) { 36 | String fileName = getFileName(context, lang); 37 | FileUtils.write2File(json, fileName); 38 | 39 | } 40 | 41 | /** 42 | * @param context 43 | * @param lang 44 | * @return 45 | */ 46 | public static String getFileName(@NonNull Context context, @NonNull String lang) { 47 | File cacheDir = context.getCacheDir(); 48 | String fileName = MD5Utils.MD5(lang); 49 | return new File(cacheDir, fileName).getAbsolutePath(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/assets/langs: -------------------------------------------------------------------------------- 1 | { 2 | "sel":[ 3 | { 4 | "name":"All", 5 | "key":"all" 6 | }, 7 | { 8 | "name":"Java", 9 | "key":"java" 10 | }, 11 | { 12 | "name":"C", 13 | "key":"c" 14 | }, 15 | { 16 | "name":"HTML", 17 | "key":"html" 18 | }, 19 | { 20 | "name":"JavaScript", 21 | "key":"javascript" 22 | }, 23 | { 24 | "name":"Python", 25 | "key":"python" 26 | }, 27 | { 28 | "name":"PHP", 29 | "key":"php" 30 | }, 31 | { 32 | "name":"Swift", 33 | "key":"swift" 34 | } 35 | ], 36 | "unsel":[ 37 | { 38 | "name":"CSS", 39 | "key":"css" 40 | }, 41 | { 42 | "name":"Kotlin", 43 | "key":"kotlin" 44 | }, 45 | { 46 | "name":"Go", 47 | "key":"go" 48 | }, 49 | { 50 | "name":"C++", 51 | "key":"cpp" 52 | }, 53 | { 54 | "name":"Object-C", 55 | "key":"objective-c" 56 | } 57 | 58 | ] 59 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/content_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 30 | 31 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/ui/fragment/TagCacheFragment.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.ui.fragment; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | 6 | import net.angrycode.wehub.R; 7 | import net.angrycode.wehub.utils.TagPrefUtils; 8 | 9 | import butterknife.BindView; 10 | import me.gujun.android.taggroup.TagGroup; 11 | 12 | /** 13 | * 搜索页面的历史记录 14 | * Created by lancelot on 2016/12/4. 15 | */ 16 | 17 | public class TagCacheFragment extends BaseFragment { 18 | @BindView(R.id.tag_group) 19 | TagGroup mTagGroup; 20 | OnTagClickListener mOnTagClickListener; 21 | 22 | @Override 23 | public int getLayoutResource() { 24 | return R.layout.fragment_tag_cache; 25 | } 26 | 27 | @Override 28 | public void onActivityCreated(@Nullable Bundle savedInstanceState) { 29 | super.onActivityCreated(savedInstanceState); 30 | String[] tags = TagPrefUtils.getTags(getActivity()); 31 | if (tags.length > 0) { 32 | mTagGroup.setTags(tags); 33 | } 34 | mTagGroup.setOnTagClickListener(tag -> { 35 | if (mOnTagClickListener != null) { 36 | mOnTagClickListener.onTagClick(tag); 37 | } 38 | }); 39 | } 40 | 41 | public void setOnTagClickListener(OnTagClickListener listener) { 42 | mOnTagClickListener = listener; 43 | } 44 | 45 | public interface OnTagClickListener { 46 | void onTagClick(String tag); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/bean/AppUpdate.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.bean; 2 | 3 | /** 4 | * Created by lancelot on 2016/12/21. 5 | */ 6 | 7 | public class AppUpdate { 8 | private int code; 9 | private String msg; 10 | private Data data; 11 | 12 | public AppUpdate() { 13 | } 14 | 15 | public Data getData() { 16 | return data; 17 | } 18 | 19 | public void setData(Data data) { 20 | this.data = data; 21 | } 22 | 23 | public int getCode() { 24 | return code; 25 | } 26 | 27 | public void setCode(int code) { 28 | this.code = code; 29 | } 30 | 31 | public String getMsg() { 32 | return msg; 33 | } 34 | 35 | public void setMsg(String msg) { 36 | this.msg = msg; 37 | } 38 | 39 | public static class Data { 40 | private String url; 41 | private int has_new; 42 | private String version; 43 | 44 | public String getUrl() { 45 | return url; 46 | } 47 | 48 | public void setUrl(String url) { 49 | this.url = url; 50 | } 51 | 52 | public int getHas_new() { 53 | return has_new; 54 | } 55 | 56 | public void setHas_new(int has_new) { 57 | this.has_new = has_new; 58 | } 59 | 60 | public String getVersion() { 61 | return version; 62 | } 63 | 64 | public void setVersion(String version) { 65 | this.version = version; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /app/src/main/res/layout/nav_header_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 23 | 24 | 30 | 31 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/ui/adapter/ReposAdapter.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.ui.adapter; 2 | 3 | import android.widget.ImageView; 4 | import android.widget.TextView; 5 | 6 | import net.angrycode.wehub.R; 7 | import net.angrycode.wehub.bean.Repository; 8 | import net.angrycode.wehub.utils.GlideUtils; 9 | 10 | /** 11 | * Created by lancelot on 2016/11/13. 12 | */ 13 | 14 | public class ReposAdapter extends BaseSimpleRecycleAdapter { 15 | 16 | public ReposAdapter() { 17 | 18 | } 19 | 20 | @Override 21 | public int getItemLayout(int viewType) { 22 | return R.layout.item_repo; 23 | } 24 | 25 | @Override 26 | public void onRender(ViewHolder holder, int position) { 27 | Repository repository = getItem(position); 28 | 29 | ImageView imageView = holder.getView(R.id.iv_user_face); 30 | TextView titleTv = holder.getView(R.id.tv_repo_name); 31 | TextView descTv = holder.getView(R.id.tv_repo_desc); 32 | // TextView watcherCountTv = holder.getView(R.id.tv_watcher_count); 33 | TextView starCountTv = holder.getView(R.id.tv_star_count); 34 | TextView forkCountTv = holder.getView(R.id.tv_fork_count); 35 | 36 | titleTv.setText(repository.getFull_name()); 37 | descTv.setText(repository.getDescription()); 38 | // watcherCountTv.setText(String.valueOf(repository.getWatchers())); 39 | starCountTv.setText(String.valueOf(repository.getWatchers_count())); 40 | forkCountTv.setText(String.valueOf(repository.getForks())); 41 | GlideUtils.loadImage(imageView.getContext(), repository.getOwner().getAvatar_url(), imageView); 42 | } 43 | 44 | 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/utils/TagPrefUtils.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.utils; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | import android.text.TextUtils; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | /** 11 | * Created by lancelot on 2016/12/4. 12 | */ 13 | 14 | public class TagPrefUtils { 15 | private static final String FILE_NAME = "tag_file_name"; 16 | private static final String KEY_TAG = "tag_key"; 17 | 18 | /** 19 | * 获取Tags 20 | * 21 | * @param context 22 | * @return 23 | */ 24 | public static String[] getTags(Context context) { 25 | SharedPreferences sp = context.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE); 26 | String tags = sp.getString(KEY_TAG, ""); 27 | String[] list = new String[]{}; 28 | if (!TextUtils.isEmpty(tags)) { 29 | list = tags.split(","); 30 | } 31 | return list; 32 | } 33 | 34 | public static void appendTags(Context context, String query) { 35 | SharedPreferences sp = context.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE); 36 | String[] tags = sp.getString(KEY_TAG, "").split(","); 37 | List tagList = new ArrayList<>(); 38 | for (int i = 0; i < tags.length; ++i) { 39 | if (!TextUtils.isEmpty(tags[i])) { 40 | tagList.add(tags[i]); 41 | } 42 | } 43 | if (!tagList.contains(query)) { 44 | tagList.add(query); 45 | } 46 | String tagStr = TextUtils.join(",", tagList); 47 | sp.edit().putString(KEY_TAG, tagStr).apply(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/bean/GsonBuilder.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.bean; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.JsonArray; 5 | import com.google.gson.JsonElement; 6 | import com.google.gson.JsonParser; 7 | 8 | import net.angrycode.wehub.utils.LogUtils; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | /** 14 | * Created by lancelot on 2016/12/3. 15 | */ 16 | 17 | public class GsonBuilder { 18 | 19 | /** 20 | * 解析数组[{}] 21 | * 22 | * @param jsonString 23 | * @param cls 24 | * @param 25 | * @return 26 | */ 27 | public static List buildArray(String jsonString, Class cls) { 28 | List list = new ArrayList<>(); 29 | try { 30 | Gson gson = new Gson(); 31 | JsonArray array = new JsonParser().parse(jsonString).getAsJsonArray(); 32 | for (JsonElement jsonElement : array) { 33 | list.add(gson.fromJson(jsonElement, cls)); 34 | } 35 | } catch (Exception e) { 36 | LogUtils.e(e); 37 | } 38 | return list; 39 | } 40 | 41 | /** 42 | * 对象转换成json字符串 43 | * 44 | * @param obj 45 | * @return 46 | */ 47 | public static String toJson(Object obj) { 48 | Gson gson = new Gson(); 49 | return gson.toJson(obj); 50 | } 51 | 52 | /** 53 | * 将Json数据解析成相应的映射对象 54 | * 55 | * @param json 56 | * @param type 57 | * @param 58 | * @return 59 | */ 60 | public static T parseJson(String json, Class type) { 61 | Gson gson = new Gson(); 62 | T result = gson.fromJson(json, type); 63 | return result; 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/mvp/presenter/Presenter.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.mvp.presenter; 2 | 3 | import android.app.Activity; 4 | import android.support.annotation.StringRes; 5 | 6 | import net.angrycode.wehub.R; 7 | import net.angrycode.wehub.mvp.view.BaseView; 8 | 9 | import rx.subscriptions.CompositeSubscription; 10 | 11 | public abstract class Presenter { 12 | T view; 13 | 14 | interface ErrorCode { 15 | int ERROR_REQUEST_DATA = 400; 16 | int ERROR_OBSERVER_DATA = 404; 17 | int ERROR_SAVE_DATA = 405; 18 | int ERROR_GET_DATA = 406; 19 | int ERROR_DEL_DATA = 407; 20 | } 21 | 22 | protected CompositeSubscription compositeSubscription = new CompositeSubscription(); 23 | 24 | public Presenter(T view) { 25 | this.view = view; 26 | } 27 | 28 | public Activity getActivity() { 29 | return view.getActivity(); 30 | } 31 | 32 | public void resume() { 33 | } 34 | 35 | public void pause() { 36 | 37 | } 38 | 39 | public String getString(@StringRes int res) { 40 | return view.getActivity().getString(res); 41 | } 42 | 43 | public String getRequestErrorMsg() { 44 | return view.getActivity().getString(R.string.error_request_data); 45 | } 46 | 47 | public boolean isViewAttached() { 48 | if (compositeSubscription.isUnsubscribed()) { 49 | return false; 50 | } 51 | if (view == null || view.getActivity() == null || view.getActivity().isFinishing()) { 52 | return false; 53 | } 54 | return true; 55 | } 56 | 57 | public void destroy() { 58 | compositeSubscription.unsubscribe(); 59 | view = null; 60 | } 61 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/item_theme.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 17 | 18 | 23 | 24 | 30 | 31 | 32 | 41 | 42 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/ui/adapter/TrendsAdapter.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.ui.adapter; 2 | 3 | import android.widget.ImageView; 4 | import android.widget.TextView; 5 | 6 | import net.angrycode.wehub.R; 7 | import net.angrycode.wehub.bean.TrendingRepo; 8 | import net.angrycode.wehub.utils.GlideUtils; 9 | 10 | /** 11 | * Created by lancelot on 2016/12/2. 12 | */ 13 | 14 | public class TrendsAdapter extends BaseSimpleRecycleAdapter { 15 | public TrendsAdapter() { 16 | } 17 | 18 | @Override 19 | public int getItemLayout(int viewType) { 20 | return R.layout.item_trends; 21 | } 22 | 23 | @Override 24 | public void onRender(ViewHolder holder, int position) { 25 | TrendingRepo repo = getItem(position); 26 | 27 | ImageView imageView = holder.getView(R.id.iv_user_face); 28 | TextView titleTv = holder.getView(R.id.tv_repo_name); 29 | TextView descTv = holder.getView(R.id.tv_repo_desc); 30 | TextView starCountTv = holder.getView(R.id.tv_stars_today); 31 | 32 | titleTv.setText(repo.getOwner() + "/" + repo.getName()); 33 | descTv.setText(repo.getDesc()); 34 | starCountTv.setText(String.valueOf(repo.getStars_today()) + " stars today"); 35 | GlideUtils.loadImage(imageView.getContext(), repo.getAvatar(), imageView); 36 | } 37 | 38 | public void remove(TrendingRepo repo) { 39 | int len = mList.size(); 40 | for (int i = 0; i < len; ++i) { 41 | TrendingRepo trend = mList.get(i); 42 | if (trend.equals(repo)) { 43 | mList.remove(i); 44 | notifyItemRemoved(i); 45 | notifyItemRangeChanged(0,mList.size()); 46 | break; 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_selected_langs_header.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 16 | 17 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 44 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_webview.xml: -------------------------------------------------------------------------------- 1 | 15 | 21 | 22 | 23 | 24 | 27 | 28 | 32 | 33 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/api/BaseTrendApi.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.api; 2 | 3 | import java.io.File; 4 | import java.util.concurrent.TimeUnit; 5 | 6 | import okhttp3.Cache; 7 | import okhttp3.OkHttpClient; 8 | import retrofit2.Retrofit; 9 | import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory; 10 | import retrofit2.converter.gson.GsonConverterFactory; 11 | 12 | /** 13 | * Created by lancelot on 2016/12/2. 14 | */ 15 | 16 | public class BaseTrendApi { 17 | public final static String API_SERVER = "https://angrycode.leanapp.cn/api/github/"; 18 | private final static OkHttpClient mOkHttpClient; 19 | private static Retrofit sRetrofit; 20 | 21 | public BaseTrendApi() { 22 | 23 | 24 | } 25 | 26 | static { 27 | Cache cache = new Cache(new File("/data/data/net.angrycode.wehub/cache"), 1024 * 1024); 28 | mOkHttpClient = new OkHttpClient.Builder() 29 | .cache(cache) 30 | .addNetworkInterceptor(new CacheInterceptor()) 31 | .connectTimeout(30, TimeUnit.SECONDS) 32 | .readTimeout(20, TimeUnit.SECONDS) 33 | .build(); 34 | } 35 | 36 | protected static Retrofit getRetrofit() { 37 | if (sRetrofit == null) { 38 | //构建Retrofit 39 | sRetrofit = new Retrofit.Builder() 40 | //配置服务器路径 41 | .baseUrl(API_SERVER) 42 | //设置日期解析格式,这样可以直接解析Date类型 43 | // .setDateFormat("yyyy-MM-dd HH:mm:ss") 44 | //配置转化库,默认是Gson 45 | .addConverterFactory(GsonConverterFactory.create()) 46 | //配置回调库,采用RxJava 47 | .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) 48 | //设置OKHttpClient为网络客户端 49 | .client(mOkHttpClient) 50 | .build(); 51 | } 52 | return sRetrofit; 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/utils/AppUtils.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.utils; 2 | 3 | import android.app.DownloadManager; 4 | import android.content.Context; 5 | import android.net.Uri; 6 | import android.os.Environment; 7 | import android.util.DisplayMetrics; 8 | import android.webkit.MimeTypeMap; 9 | 10 | import net.angrycode.wehub.R; 11 | 12 | import java.io.File; 13 | 14 | /** 15 | * Created by lancelot on 2016/12/17. 16 | */ 17 | 18 | public class AppUtils { 19 | public static int getScreenWidth(Context context) { 20 | DisplayMetrics dm = context.getResources().getDisplayMetrics(); 21 | return dm.widthPixels; 22 | } 23 | public static void downLoad(Context context,String url) { 24 | DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); 25 | DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url)); 26 | //设置状态栏中显示Notification 27 | request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); 28 | request.setTitle(context.getResources().getString(R.string.app_name)); 29 | 30 | request.setDescription("正在下载中..."); 31 | //设置可用的网络类型 32 | request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_MOBILE | DownloadManager.Request.NETWORK_WIFI); 33 | //不显示下载界面 34 | request.setVisibleInDownloadsUi(true); 35 | 36 | //创建文件的下载路径 37 | File folder = Environment.getDownloadCacheDirectory(); 38 | 39 | //指定下载的路径为和上面创建的路径相同 40 | request.setDestinationInExternalPublicDir(folder.getAbsolutePath(), "WeCode_xyz.apk"); 41 | 42 | //设置文件类型 43 | MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton(); 44 | String mimeString = mimeTypeMap.getMimeTypeFromExtension(MimeTypeMap.getFileExtensionFromUrl(url)); 45 | request.setMimeType(mimeString); 46 | //将请求加入请求队列会 downLoadManager会自动调用对应的服务执行者个请求 47 | downloadManager.enqueue(request); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 这是一款可以查阅Github上的热门趋势的APP 2 | 3 | 随时查阅当前Github上的热门趋势。使用Material Design设计风格,和流行的MVP+Retrofit+RxJava框架。数据抓取自[https://github.com/trending](https://github.com/trending) 4 | 5 | ### Features 6 | 7 | - Material Design设计风格 8 | - MVP结构 9 | - 使用Retrofit网络请求 10 | - 多种内置皮肤可以切换 11 | - 可订阅常见编程语言 12 | - 支持搜索 13 | 14 | ### Demo 15 | 16 | ![demo](http://pp.myapp.com/ma_pic2/0/shot_52402110_1_1487174558/550) 17 | 18 | ![demo2](http://pp.myapp.com/ma_pic2/0/shot_52402110_2_1487174558/550) 19 | 20 | ![demo3](http://pp.myapp.com/ma_pic2/0/shot_52402110_3_1487174558/550) 21 | 22 | ![demo4](http://pp.myapp.com/ma_pic2/0/shot_52402110_4_1487174558/550) 23 | 24 | ### Thanks 25 | 26 | 感谢以下开源协议 27 | 28 | ```groovy 29 | 30 | compile 'com.android.support:appcompat-v7:25.0.1' 31 | compile 'com.android.support:recyclerview-v7:25.0.1' 32 | compile 'com.android.support:cardview-v7:25.0.1' 33 | //design 34 | compile 'com.android.support:design:25.0.1' 35 | //custom tabs 36 | compile 'com.android.support:customtabs:25.0.1' 37 | compile project(':customtabs') 38 | 39 | //编译RxJava 40 | compile 'io.reactivex:rxjava:1.1.6' 41 | //编译RxAndroid 42 | compile 'io.reactivex:rxandroid:1.2.1' 43 | //编译Retrofit及其相关库,包括Gson 44 | compile 'com.squareup.okhttp3:okhttp:3.3.1' 45 | compile 'com.squareup.retrofit2:retrofit:2.1.0' 46 | compile 'com.squareup.retrofit2:converter-gson:2.1.0' 47 | compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0' 48 | compile 'com.squareup.okhttp3:logging-interceptor:3.3.1' 49 | //view injector 50 | compile 'com.jakewharton:butterknife:8.4.0' 51 | annotationProcessor 'com.jakewharton:butterknife-compiler:8.4.0' 52 | //glide 53 | compile 'com.github.bumptech.glide:glide:3.7.0' 54 | //TagGroup 55 | compile 'me.gujun.android.taggroup:library:1.4@aar' 56 | //EventBus 57 | compile 'org.greenrobot:eventbus:3.0.0' 58 | compile 'org.greenrobot:greendao:3.2.0' 59 | ``` 60 | ### App下载 61 | [App](http://sj.qq.com/myapp/detail.htm?apkName=net.angrycode.wehub) 62 | -------------------------------------------------------------------------------- /customtabs/src/main/java/org/chromium/customtabsclient/shared/ServiceConnection.java: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package org.chromium.customtabsclient.shared; 16 | 17 | import android.content.ComponentName; 18 | import android.support.customtabs.CustomTabsClient; 19 | import android.support.customtabs.CustomTabsServiceConnection; 20 | 21 | import java.lang.ref.WeakReference; 22 | 23 | /** 24 | * Implementation for the CustomTabsServiceConnection that avoids leaking the 25 | * ServiceConnectionCallback 26 | */ 27 | public class ServiceConnection extends CustomTabsServiceConnection { 28 | // A weak reference to the ServiceConnectionCallback to avoid leaking it. 29 | private WeakReference mConnectionCallback; 30 | 31 | public ServiceConnection(ServiceConnectionCallback connectionCallback) { 32 | mConnectionCallback = new WeakReference<>(connectionCallback); 33 | } 34 | 35 | @Override 36 | public void onCustomTabsServiceConnected(ComponentName name, CustomTabsClient client) { 37 | ServiceConnectionCallback connectionCallback = mConnectionCallback.get(); 38 | if (connectionCallback != null) connectionCallback.onServiceConnected(client); 39 | } 40 | 41 | @Override 42 | public void onServiceDisconnected(ComponentName name) { 43 | ServiceConnectionCallback connectionCallback = mConnectionCallback.get(); 44 | if (connectionCallback != null) connectionCallback.onServiceDisconnected(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/api/TrendingApi.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.api; 2 | 3 | import android.content.Context; 4 | 5 | import net.angrycode.wehub.bean.AppUpdate; 6 | import net.angrycode.wehub.bean.GsonBuilder; 7 | import net.angrycode.wehub.bean.TrendingRepo; 8 | import net.angrycode.wehub.utils.JsonCacheUtils; 9 | 10 | import java.util.List; 11 | 12 | import retrofit2.http.FormUrlEncoded; 13 | import retrofit2.http.GET; 14 | import retrofit2.http.Path; 15 | import rx.Observable; 16 | 17 | /** 18 | * Created by lancelot on 2016/12/1. 19 | */ 20 | 21 | public class TrendingApi extends BaseTrendApi { 22 | public TrendingApi() { 23 | } 24 | 25 | private interface TrendingService { 26 | @GET("trending/{lang}") 27 | Observable> getTrendingRepos(@Path("lang") String lang); 28 | 29 | @GET("check_update/{version}") 30 | Observable checkAppUpdate(@Path("version") String version); 31 | } 32 | 33 | protected static final TrendingService service = getRetrofit().create(TrendingService.class); 34 | 35 | public static Observable> getRepos(String lang) { 36 | Observable> observable = service.getTrendingRepos(lang); 37 | return observable; 38 | } 39 | 40 | /** 41 | * 获取缓存数据 42 | * 43 | * @param lang 44 | * @returnxxxx 45 | */ 46 | public static Observable> getReposFromeCache(Context context, String lang) { 47 | Observable> observable = Observable.create(subscriber -> { 48 | String json = JsonCacheUtils.readFile(context, lang); 49 | List list = GsonBuilder.buildArray(json, TrendingRepo.class); 50 | subscriber.onNext(list); 51 | subscriber.onCompleted(); 52 | }); 53 | return observable; 54 | } 55 | 56 | /** 57 | * 检查更新 58 | * @param version 59 | * @return 60 | */ 61 | public static Observable checkAppUpdate(String version) { 62 | Observable observable = service.checkAppUpdate(version); 63 | return observable; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/utils/LangsUtils.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.utils; 2 | 3 | import android.content.Context; 4 | import android.text.TextUtils; 5 | 6 | import net.angrycode.wehub.bean.GsonBuilder; 7 | import net.angrycode.wehub.bean.Langs; 8 | 9 | import java.io.File; 10 | import java.io.FileOutputStream; 11 | import java.io.IOException; 12 | import java.io.InputStream; 13 | 14 | /** 15 | * Created by lancelot on 2016/12/17. 16 | */ 17 | 18 | public class LangsUtils { 19 | private static final String FILE_NAME = "langs"; 20 | 21 | public static boolean write2sdcard(Context context, String langsJson) { 22 | File file = new File(context.getExternalCacheDir(), FILE_NAME); 23 | FileOutputStream out = null; 24 | try { 25 | out = new FileOutputStream(file); 26 | out.write(langsJson.getBytes()); 27 | out.flush(); 28 | } catch (IOException e) { 29 | LogUtils.e(e); 30 | return false; 31 | } finally { 32 | try { 33 | if (out != null) { 34 | out.close(); 35 | } 36 | } catch (IOException e) { 37 | LogUtils.e(e); 38 | } 39 | } 40 | return true; 41 | } 42 | 43 | public static boolean write2sdcard(Context context, Langs langs) { 44 | String json = GsonBuilder.toJson(langs); 45 | return write2sdcard(context, json); 46 | } 47 | 48 | public static String readFromAssets(Context context) { 49 | String retVal = null; 50 | try { 51 | InputStream in = context.getAssets().open(FILE_NAME); 52 | retVal = FileUtils.read(in); 53 | } catch (IOException e) { 54 | LogUtils.e(e); 55 | } 56 | return retVal; 57 | } 58 | 59 | public static String readLangJson(Context context) { 60 | File file = new File(context.getExternalCacheDir(), FILE_NAME); 61 | String json = FileUtils.readFile(file.getAbsolutePath()); 62 | if (TextUtils.isEmpty(json)) { 63 | json = readFromAssets(context); 64 | write2sdcard(context, json); 65 | } 66 | return json; 67 | } 68 | 69 | 70 | } 71 | -------------------------------------------------------------------------------- /app/src/main/res/layout/app_bar_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 14 | 15 | 22 | 23 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/custom_progress_drawable.xml: -------------------------------------------------------------------------------- 1 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 17 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 31 | 35 | 39 | 43 | 47 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/utils/Keyboard.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.utils; 2 | 3 | import android.os.Build; 4 | import android.view.View; 5 | import android.view.ViewTreeObserver; 6 | import android.view.inputmethod.InputMethodManager; 7 | 8 | import static android.content.Context.INPUT_METHOD_SERVICE; 9 | 10 | 11 | /** 12 | * Keyboard utilities 13 | */ 14 | public class Keyboard { 15 | 16 | public static void show(final View view, long delay) { 17 | if (view == null) { 18 | return; 19 | } 20 | InputMethodManager manager = (InputMethodManager) view.getContext().getSystemService(INPUT_METHOD_SERVICE); 21 | if (view.getWidth() == 0 && view.getHeight() == 0) { 22 | view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { 23 | @Override 24 | public void onGlobalLayout() { 25 | removeViewTreeObserver(view, this); 26 | if (delay <= 0) { 27 | manager.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT); 28 | } else { 29 | view.postDelayed(() -> manager.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT), delay); 30 | } 31 | } 32 | }); 33 | } else { 34 | if (delay <= 0) { 35 | manager.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT); 36 | } else { 37 | view.postDelayed(() -> manager.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT), delay); 38 | } 39 | } 40 | 41 | } 42 | 43 | public static void hide(final View view) { 44 | if (view == null) { 45 | return; 46 | } 47 | InputMethodManager manager = (InputMethodManager) view.getContext().getSystemService(INPUT_METHOD_SERVICE); 48 | if (manager != null) { 49 | manager.hideSoftInputFromWindow(view.getWindowToken(), 0); 50 | } 51 | } 52 | 53 | /** 54 | * 移除View上的ViewTreeObserver,兼容方法 55 | */ 56 | public static void removeViewTreeObserver(View view, ViewTreeObserver.OnGlobalLayoutListener l) { 57 | if (view != null && view.getViewTreeObserver() != null) { 58 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 59 | view.getViewTreeObserver().removeOnGlobalLayoutListener(l); 60 | } else { 61 | view.getViewTreeObserver().removeGlobalOnLayoutListener(l); 62 | } 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | 19 | 20 | 26 | 27 | 33 | 34 | 40 | 41 | 45 | 46 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/ui/activity/ThemeActivity.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.ui.activity; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.widget.LinearLayoutManager; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.view.Menu; 7 | import android.view.MenuItem; 8 | import android.view.View; 9 | 10 | import net.angrycode.wehub.R; 11 | import net.angrycode.wehub.event.ThemeChangeEvent; 12 | import net.angrycode.wehub.ui.adapter.ThemeAdapter; 13 | import net.angrycode.wehub.ui.adapter.TrendsAdapter; 14 | import net.angrycode.wehub.ui.component.DividerItemDecoration; 15 | import net.angrycode.wehub.utils.ThemeUtils; 16 | 17 | import org.greenrobot.eventbus.EventBus; 18 | 19 | import butterknife.BindView; 20 | 21 | /** 22 | * Created by lancelot on 2016/12/9. 23 | */ 24 | 25 | public class ThemeActivity extends BaseActivity { 26 | @BindView(R.id.recycler_view) 27 | RecyclerView mRecyclerView; 28 | 29 | ThemeAdapter mAdapter; 30 | int mCurrTheme; 31 | 32 | @Override 33 | protected int getLayoutResource() { 34 | return R.layout.activity_theme; 35 | } 36 | 37 | @Override 38 | protected void onCreate(Bundle savedInstanceState) { 39 | super.onCreate(savedInstanceState); 40 | 41 | init(); 42 | 43 | } 44 | 45 | private void init() { 46 | mCurrTheme = getCurrTheme(); 47 | 48 | mAdapter = new ThemeAdapter(); 49 | mAdapter.setCurrTheme(mCurrTheme); 50 | 51 | LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getApplicationContext()); 52 | mRecyclerView.setLayoutManager(linearLayoutManager); 53 | 54 | mRecyclerView.setAdapter(mAdapter); 55 | mAdapter.setOnItemClickListener(this::onItemClick); 56 | } 57 | 58 | private void onItemClick(View itemView, int position) { 59 | ThemeAdapter.Theme theme = mAdapter.getItem(position); 60 | mToolbar.setBackgroundResource(theme.getColor()); 61 | mCurrTheme = theme.getStyle(); 62 | mAdapter.setCurrTheme(mCurrTheme); 63 | } 64 | 65 | @Override 66 | public boolean onCreateOptionsMenu(Menu menu) { 67 | getMenuInflater().inflate(R.menu.activity_theme_menu, menu); 68 | return true; 69 | } 70 | 71 | @Override 72 | public boolean onOptionsItemSelected(MenuItem item) { 73 | switch (item.getItemId()) { 74 | case R.id.action_finish: 75 | if (mCurrTheme != getCurrTheme()) { 76 | ThemeUtils.putTheme(this, mCurrTheme); 77 | EventBus.getDefault().post(new ThemeChangeEvent()); 78 | } 79 | finish(); 80 | return true; 81 | } 82 | return super.onOptionsItemSelected(item); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/ui/activity/LanguagesActivity.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.ui.activity; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.support.v7.widget.helper.ItemTouchHelper; 7 | 8 | import net.angrycode.wehub.R; 9 | import net.angrycode.wehub.bean.Langs; 10 | import net.angrycode.wehub.mvp.presenter.LangPresenter; 11 | import net.angrycode.wehub.mvp.view.LangView; 12 | import net.angrycode.wehub.ui.adapter.LangAdapter; 13 | import net.angrycode.wehub.ui.component.LangGridLayoutManager; 14 | import net.angrycode.wehub.ui.component.drag.ItemDragHelperCallback; 15 | import net.angrycode.wehub.utils.ToastUtils; 16 | 17 | import butterknife.BindView; 18 | 19 | /** 20 | * 设置语言 21 | * Created by lancelot on 2016/12/9. 22 | */ 23 | 24 | public class LanguagesActivity extends BaseActivity implements LangView { 25 | @BindView(R.id.recycle_view) 26 | RecyclerView mRecycleView; 27 | LangAdapter mAdapter; 28 | LangPresenter mPresenter; 29 | 30 | @Override 31 | protected int getLayoutResource() { 32 | return R.layout.activity_languages; 33 | } 34 | 35 | @Override 36 | protected void onCreate(Bundle savedInstanceState) { 37 | super.onCreate(savedInstanceState); 38 | init(); 39 | mPresenter = new LangPresenter(this); 40 | mPresenter.getLangs(); 41 | } 42 | 43 | private void init() { 44 | 45 | ItemDragHelperCallback callback = new ItemDragHelperCallback(); 46 | final ItemTouchHelper helper = new ItemTouchHelper(callback); 47 | helper.attachToRecyclerView(mRecycleView); 48 | 49 | mAdapter = new LangAdapter(helper); 50 | 51 | LangGridLayoutManager manager = new LangGridLayoutManager(this, mAdapter, 4); 52 | 53 | mRecycleView.setLayoutManager(manager); 54 | mRecycleView.setAdapter(mAdapter); 55 | 56 | } 57 | 58 | @Override 59 | public void onGetLangsFinished(Langs langs) { 60 | mAdapter.setLangs(langs); 61 | } 62 | 63 | @Override 64 | public void onSetLangsFinished(Langs langs) { 65 | 66 | } 67 | 68 | @Override 69 | protected void onPause() { 70 | if (mAdapter.isEdited()) { 71 | mPresenter.setLangs(mAdapter.getLangs()); 72 | } 73 | super.onPause(); 74 | } 75 | 76 | @Override 77 | public Activity getActivity() { 78 | return this; 79 | } 80 | 81 | @Override 82 | public void onRequestLoading() { 83 | showLoading(); 84 | } 85 | 86 | @Override 87 | public void onRequestFinished() { 88 | dismissLoading(); 89 | } 90 | 91 | @Override 92 | public void onRequestError(int code, String message) { 93 | ToastUtils.show(getApplicationContext(), message); 94 | dismissLoading(); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_trends.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 18 | 19 | 26 | 27 | 36 | 37 | 48 | 49 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/mvp/presenter/FavorPresenter.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.mvp.presenter; 2 | 3 | import net.angrycode.wehub.R; 4 | import net.angrycode.wehub.WeCodeApp; 5 | import net.angrycode.wehub.bean.DaoSession; 6 | import net.angrycode.wehub.bean.TrendingRepo; 7 | import net.angrycode.wehub.bean.TrendingRepoDao; 8 | import net.angrycode.wehub.mvp.view.FavorView; 9 | import net.angrycode.wehub.utils.LogUtils; 10 | 11 | import org.greenrobot.greendao.rx.RxDao; 12 | import org.greenrobot.greendao.rx.RxQuery; 13 | 14 | import rx.android.schedulers.AndroidSchedulers; 15 | 16 | /** 17 | * Created by lancelot on 2016/12/18. 18 | */ 19 | 20 | public class FavorPresenter extends Presenter { 21 | RxDao mRxDao; 22 | private RxQuery mRxQuery; 23 | 24 | public FavorPresenter(FavorView view) { 25 | super(view); 26 | DaoSession session = WeCodeApp.get().getDaoSession(); 27 | mRxDao = session.getTrendingRepoDao().rx(); 28 | } 29 | 30 | public void deleteFavor(TrendingRepo repo) { 31 | mRxDao.delete(repo) 32 | .observeOn(AndroidSchedulers.mainThread()) 33 | .subscribe(aVoid -> { 34 | if (!isViewAttached()) { 35 | return; 36 | } 37 | view.onDelFavorFinish(repo); 38 | }, 39 | throwable -> { 40 | LogUtils.e(throwable); 41 | view.onRequestError(ErrorCode.ERROR_DEL_DATA, throwable.getMessage()); 42 | }, 43 | () -> view.onRequestFinished()); 44 | } 45 | 46 | public void getFavorList() { 47 | if (mRxQuery == null) { 48 | DaoSession session = WeCodeApp.get().getDaoSession(); 49 | mRxQuery = session.getTrendingRepoDao().queryBuilder().orderAsc(TrendingRepoDao.Properties.Date).rx(); 50 | } 51 | mRxQuery.list() 52 | .observeOn(AndroidSchedulers.mainThread()) 53 | .subscribe(repoList -> { 54 | if (!isViewAttached()) { 55 | return; 56 | } 57 | if (repoList != null) { 58 | view.onGetFavorListFinish(repoList); 59 | } else { 60 | view.onRequestError(ErrorCode.ERROR_GET_DATA, getActivity().getResources().getString(R.string.error_request_data)); 61 | } 62 | }, 63 | throwable -> { 64 | LogUtils.e(throwable); 65 | view.onRequestError(ErrorCode.ERROR_GET_DATA, throwable.getMessage()); 66 | }, 67 | () -> view.onRequestFinished()); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/mvp/presenter/MainPresenter.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.mvp.presenter; 2 | 3 | import net.angrycode.wehub.BuildConfig; 4 | import net.angrycode.wehub.R; 5 | import net.angrycode.wehub.api.LangApi; 6 | import net.angrycode.wehub.api.TrendingApi; 7 | import net.angrycode.wehub.bean.AppUpdate; 8 | import net.angrycode.wehub.bean.Langs; 9 | import net.angrycode.wehub.mvp.view.MainView; 10 | import net.angrycode.wehub.utils.LogUtils; 11 | 12 | import rx.Observable; 13 | import rx.android.schedulers.AndroidSchedulers; 14 | import rx.schedulers.Schedulers; 15 | 16 | /** 17 | * Created by lancelot on 2016/12/17. 18 | */ 19 | 20 | public class MainPresenter extends Presenter { 21 | public MainPresenter(MainView view) { 22 | super(view); 23 | } 24 | 25 | public void getLangs() { 26 | Observable observable = LangApi.getLangs(getActivity()); 27 | compositeSubscription.add( 28 | observable.observeOn(AndroidSchedulers.mainThread()) 29 | .subscribeOn(Schedulers.io()) 30 | .subscribe(langs -> { 31 | if (!isViewAttached()) { 32 | return; 33 | } 34 | if (langs != null) { 35 | view.onGetLangsFinished(langs); 36 | } else { 37 | view.onRequestError(ErrorCode.ERROR_REQUEST_DATA, view.getActivity().getString(R.string.error_request_data)); 38 | 39 | } 40 | }, error -> { 41 | LogUtils.e(error); 42 | view.onRequestError(ErrorCode.ERROR_OBSERVER_DATA, error.getMessage()); 43 | }, () -> view.onRequestFinished()) 44 | ); 45 | 46 | } 47 | 48 | public void checkAppUpdate() { 49 | Observable observable = TrendingApi.checkAppUpdate(BuildConfig.VERSION_NAME); 50 | compositeSubscription.add( 51 | observable.observeOn(AndroidSchedulers.mainThread()) 52 | .subscribeOn(Schedulers.io()) 53 | .subscribe(appUpdate -> { 54 | if (!isViewAttached()) { 55 | return; 56 | } 57 | if (appUpdate.getCode() > 0) { 58 | view.onCheckAppUpdateFinish(appUpdate); 59 | } 60 | }, throwable -> { 61 | LogUtils.e(throwable); 62 | view.onRequestError(ErrorCode.ERROR_SAVE_DATA, view.getActivity().getString(R.string.error_request_data)); 63 | }, 64 | () -> view.onRequestFinished()) 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/bean/Owner.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.bean; 2 | 3 | public class Owner { 4 | /** 5 | * login : parallella 6 | * id : 2919930 7 | * avatar_url : https://avatars.githubusercontent.com/u/2919930?v=3 8 | * gravatar_id : 9 | * url : https://api.github.com/users/parallella 10 | * html_url : https://github.com/parallella 11 | * followers_url : https://api.github.com/users/parallella/followers 12 | * following_url : https://api.github.com/users/parallella/following{/other_user} 13 | * gists_url : https://api.github.com/users/parallella/gists{/gist_id} 14 | * starred_url : https://api.github.com/users/parallella/starred{/owner}{/repo} 15 | * subscriptions_url : https://api.github.com/users/parallella/subscriptions 16 | * organizations_url : https://api.github.com/users/parallella/orgs 17 | * repos_url : https://api.github.com/users/parallella/repos 18 | * events_url : https://api.github.com/users/parallella/events{/privacy} 19 | * received_events_url : https://api.github.com/users/parallella/received_events 20 | * type : Organization 21 | * site_admin : false 22 | */ 23 | 24 | private String login; 25 | private int id; 26 | private String avatar_url; 27 | private String gravatar_id; 28 | private String url; 29 | private String html_url; 30 | private String type; 31 | private boolean site_admin; 32 | 33 | public String getLogin() { 34 | return login; 35 | } 36 | 37 | public void setLogin(String login) { 38 | this.login = login; 39 | } 40 | 41 | public int getId() { 42 | return id; 43 | } 44 | 45 | public void setId(int id) { 46 | this.id = id; 47 | } 48 | 49 | public String getAvatar_url() { 50 | return avatar_url; 51 | } 52 | 53 | public void setAvatar_url(String avatar_url) { 54 | this.avatar_url = avatar_url; 55 | } 56 | 57 | public String getGravatar_id() { 58 | return gravatar_id; 59 | } 60 | 61 | public void setGravatar_id(String gravatar_id) { 62 | this.gravatar_id = gravatar_id; 63 | } 64 | 65 | public String getUrl() { 66 | return url; 67 | } 68 | 69 | public void setUrl(String url) { 70 | this.url = url; 71 | } 72 | 73 | public String getHtml_url() { 74 | return html_url; 75 | } 76 | 77 | public void setHtml_url(String html_url) { 78 | this.html_url = html_url; 79 | } 80 | 81 | public String getType() { 82 | return type; 83 | } 84 | 85 | public void setType(String type) { 86 | this.type = type; 87 | } 88 | 89 | public boolean isSite_admin() { 90 | return site_admin; 91 | } 92 | 93 | public void setSite_admin(boolean site_admin) { 94 | this.site_admin = site_admin; 95 | } 96 | } -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/mvp/presenter/ReposPresenter.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.mvp.presenter; 2 | 3 | import android.util.Log; 4 | 5 | import net.angrycode.wehub.R; 6 | import net.angrycode.wehub.WeCodeApp; 7 | import net.angrycode.wehub.api.ReposApi; 8 | import net.angrycode.wehub.bean.DaoSession; 9 | import net.angrycode.wehub.bean.Repos; 10 | import net.angrycode.wehub.bean.TrendingRepo; 11 | import net.angrycode.wehub.mvp.view.RepositoriesView; 12 | 13 | import org.greenrobot.greendao.rx.RxDao; 14 | 15 | import java.util.Date; 16 | 17 | import rx.Observable; 18 | import rx.android.schedulers.AndroidSchedulers; 19 | import rx.schedulers.Schedulers; 20 | 21 | /** 22 | * Created by lancelot on 2016/11/13. 23 | */ 24 | 25 | public class ReposPresenter extends Presenter { 26 | RxDao mRxDao; 27 | 28 | public ReposPresenter(RepositoriesView view) { 29 | super(view); 30 | } 31 | 32 | public void getRepositories(String query) { 33 | view.onRequestLoading(); 34 | Observable observable = ReposApi.getRepositories(query); 35 | compositeSubscription.add( 36 | observable.observeOn(AndroidSchedulers.mainThread()) 37 | .subscribeOn(Schedulers.io()) 38 | .subscribe(repositories -> { 39 | if (!isViewAttached()) { 40 | return; 41 | } 42 | if (repositories != null && !repositories.isIncomplete_results()) { 43 | view.onGetRepositories(repositories); 44 | } else { 45 | view.onRequestError(ErrorCode.ERROR_REQUEST_DATA, getRequestErrorMsg()); 46 | } 47 | }, error -> { 48 | Log.d("repositories", error.getMessage()); 49 | view.onRequestError(ErrorCode.ERROR_OBSERVER_DATA, getRequestErrorMsg()); 50 | }, () -> view.onRequestFinished()) 51 | ); 52 | } 53 | 54 | public void addFavor(TrendingRepo repo) { 55 | if (repo == null) { 56 | return; 57 | } 58 | if (mRxDao == null) { 59 | DaoSession session = WeCodeApp.get().getDaoSession(); 60 | mRxDao = session.getTrendingRepoDao().rx(); 61 | } 62 | repo.setDate(new Date()); 63 | mRxDao.insertOrReplace(repo) 64 | .observeOn(AndroidSchedulers.mainThread()) 65 | .subscribe(trendingRepo -> { 66 | if (!isViewAttached()) { 67 | return; 68 | } 69 | if (trendingRepo.getId() > 0) { 70 | view.onAddFavorFinish(trendingRepo); 71 | } 72 | }, throwable -> { 73 | view.onRequestError(ErrorCode.ERROR_SAVE_DATA, throwable.getMessage()); 74 | }, 75 | () -> view.onRequestFinished()); 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/mvp/presenter/LangPresenter.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.mvp.presenter; 2 | 3 | import net.angrycode.wehub.R; 4 | import net.angrycode.wehub.api.LangApi; 5 | import net.angrycode.wehub.bean.Langs; 6 | import net.angrycode.wehub.event.LangEditedEvent; 7 | import net.angrycode.wehub.mvp.view.LangView; 8 | import net.angrycode.wehub.utils.LogUtils; 9 | 10 | import org.greenrobot.eventbus.EventBus; 11 | 12 | import rx.Observable; 13 | import rx.android.schedulers.AndroidSchedulers; 14 | import rx.schedulers.Schedulers; 15 | 16 | /** 17 | * Created by lancelot on 2016/12/17. 18 | */ 19 | 20 | public class LangPresenter extends Presenter { 21 | public LangPresenter(LangView view) { 22 | super(view); 23 | } 24 | 25 | public void getLangs() { 26 | view.onRequestLoading(); 27 | Observable observable = LangApi.getLangs(getActivity()); 28 | compositeSubscription.add( 29 | observable.observeOn(AndroidSchedulers.mainThread()) 30 | .subscribeOn(Schedulers.io()) 31 | .subscribe(langs -> { 32 | if (!isViewAttached()) { 33 | return; 34 | } 35 | if (langs != null) { 36 | view.onGetLangsFinished(langs); 37 | } else { 38 | view.onRequestError(ErrorCode.ERROR_REQUEST_DATA, view.getActivity().getString(R.string.error_request_data)); 39 | 40 | } 41 | }, error -> { 42 | LogUtils.e(error); 43 | view.onRequestError(ErrorCode.ERROR_OBSERVER_DATA, error.getMessage()); 44 | }, () -> view.onRequestFinished()) 45 | ); 46 | 47 | } 48 | 49 | public void setLangs(final Langs langs) { 50 | // view.onRequestLoading(); 51 | Observable observable = LangApi.setLangs(getActivity(), langs); 52 | compositeSubscription.add( 53 | observable.observeOn(AndroidSchedulers.mainThread()) 54 | .subscribeOn(Schedulers.io()) 55 | .subscribe(result -> { 56 | EventBus.getDefault().post(new LangEditedEvent(langs)); 57 | if (!isViewAttached()) { 58 | return; 59 | } 60 | if (result != null) { 61 | view.onGetLangsFinished(result); 62 | } else { 63 | // view.onRequestError(ErrorCode.ERROR_REQUEST_DATA, view.getActivity().getString(R.string.error_request_data)); 64 | LogUtils.log("Set langs error"); 65 | } 66 | }, error -> { 67 | LogUtils.e(error); 68 | // view.onRequestError(ErrorCode.ERROR_OBSERVER_DATA, error.getMessage()); 69 | }, () -> view.onRequestFinished()) 70 | ); 71 | 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/mvp/presenter/TrendsPresenter.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.mvp.presenter; 2 | 3 | import android.util.Log; 4 | 5 | import net.angrycode.wehub.R; 6 | import net.angrycode.wehub.WeCodeApp; 7 | import net.angrycode.wehub.api.TrendingApi; 8 | import net.angrycode.wehub.bean.DaoSession; 9 | import net.angrycode.wehub.bean.TrendingRepo; 10 | import net.angrycode.wehub.mvp.view.TrendingView; 11 | import net.angrycode.wehub.utils.LogUtils; 12 | 13 | import org.greenrobot.greendao.rx.RxDao; 14 | 15 | import java.util.Date; 16 | import java.util.List; 17 | 18 | import rx.Observable; 19 | import rx.android.schedulers.AndroidSchedulers; 20 | import rx.schedulers.Schedulers; 21 | 22 | /** 23 | * Created by lancelot on 2016/12/3. 24 | */ 25 | 26 | public class TrendsPresenter extends Presenter { 27 | RxDao mRxDao; 28 | 29 | public TrendsPresenter(TrendingView view) { 30 | super(view); 31 | DaoSession session = WeCodeApp.get().getDaoSession(); 32 | mRxDao = session.getTrendingRepoDao().rx(); 33 | } 34 | 35 | /** 36 | * @param lang 37 | */ 38 | public void getTrending(String lang, boolean cache) { 39 | view.onRequestLoading(); 40 | Observable> observable = TrendingApi.getRepos(lang); 41 | compositeSubscription.add( 42 | observable.observeOn(AndroidSchedulers.mainThread()) 43 | .subscribeOn(Schedulers.io()) 44 | .subscribe(repositories -> { 45 | if (!isViewAttached()) { 46 | return; 47 | } 48 | if (repositories != null && repositories.size() > 0) { 49 | view.onGetTrendingsFinish(repositories); 50 | } else { 51 | view.onRequestError(ErrorCode.ERROR_REQUEST_DATA, view.getActivity().getString(R.string.error_request_data)); 52 | } 53 | }, error -> { 54 | LogUtils.e(error); 55 | view.onRequestError(ErrorCode.ERROR_OBSERVER_DATA, getRequestErrorMsg()); 56 | }, () -> view.onRequestFinished()) 57 | ); 58 | } 59 | 60 | public void addFavor(TrendingRepo repo) { 61 | if (repo == null) { 62 | return; 63 | } 64 | repo.setDate(new Date()); 65 | mRxDao.insertOrReplace(repo) 66 | .observeOn(AndroidSchedulers.mainThread()) 67 | .subscribe(trendingRepo -> { 68 | if (!isViewAttached()) { 69 | return; 70 | } 71 | if (trendingRepo.getId() > 0) { 72 | view.onAddFavorFinish(trendingRepo); 73 | } 74 | }, throwable -> { 75 | view.onRequestError(ErrorCode.ERROR_SAVE_DATA, throwable.getMessage()); 76 | }, 77 | () -> view.onRequestFinished()); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_about.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 16 | 17 | 24 | 25 | 30 | 31 | 38 | 39 | 47 | 48 | 49 | 50 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/utils/FileUtils.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.utils; 2 | 3 | import android.content.Context; 4 | 5 | import java.io.ByteArrayOutputStream; 6 | import java.io.File; 7 | import java.io.FileInputStream; 8 | import java.io.FileOutputStream; 9 | import java.io.IOException; 10 | import java.io.InputStream; 11 | 12 | /** 13 | * Created by lancelot on 2016/12/3. 14 | */ 15 | 16 | public class FileUtils { 17 | 18 | /** 19 | * 读取 20 | * 21 | * @param fileName 22 | * @return 23 | */ 24 | public static String readFile(String fileName) { 25 | String txt = null; 26 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 27 | FileInputStream in = null; 28 | try { 29 | in = new FileInputStream(fileName); 30 | byte[] data = new byte[1024]; 31 | int len = -1; 32 | while ((len = in.read(data)) != -1) { 33 | out.write(data, 0, len); 34 | out.flush(); 35 | } 36 | txt = out.toString(); 37 | } catch (IOException e) { 38 | LogUtils.log(e.getMessage()); 39 | } finally { 40 | try { 41 | if (in != null) { 42 | in.close(); 43 | } 44 | out.close(); 45 | } catch (IOException e) { 46 | LogUtils.log(e.getMessage()); 47 | } 48 | 49 | } 50 | return txt; 51 | } 52 | 53 | public static void write2File(String json, String fileName) { 54 | 55 | FileOutputStream out = null; 56 | try { 57 | out = new FileOutputStream(fileName); 58 | byte[] data = json.getBytes(); 59 | out.write(data, 0, data.length); 60 | out.flush(); 61 | } catch (IOException e) { 62 | LogUtils.log(e.getMessage()); 63 | } finally { 64 | try { 65 | out.close(); 66 | } catch (IOException e) { 67 | LogUtils.log(e.getMessage()); 68 | } 69 | } 70 | } 71 | 72 | public static String getFileName(Context context, String lang) { 73 | File cacheDir = context.getCacheDir(); 74 | String fileName = MD5Utils.MD5(lang); 75 | return new File(cacheDir, fileName).getAbsolutePath(); 76 | } 77 | 78 | /** 79 | * 读取 80 | * @param in 81 | * @return 82 | */ 83 | public static String read(InputStream in) { 84 | String retVal = null; 85 | if (in == null) { 86 | return retVal; 87 | } 88 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 89 | int len = -1; 90 | byte[] data = new byte[1024]; 91 | try { 92 | while ((len = in.read(data)) != -1) { 93 | out.write(data, 0, len); 94 | } 95 | out.flush(); 96 | 97 | } catch (IOException e) { 98 | LogUtils.log(e.getMessage()); 99 | } finally { 100 | try { 101 | out.close(); 102 | } catch (IOException e) { 103 | LogUtils.log(e.getMessage()); 104 | } 105 | } 106 | return out.toString(); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/ui/activity/SearchActivity.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.ui.activity; 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.view.MenuItemCompat; 8 | import android.support.v7.widget.SearchView; 9 | import android.view.Menu; 10 | import android.view.MenuItem; 11 | 12 | import net.angrycode.wehub.R; 13 | import net.angrycode.wehub.ui.fragment.ReposFragment; 14 | import net.angrycode.wehub.ui.fragment.TagCacheFragment; 15 | import net.angrycode.wehub.utils.Keyboard; 16 | import net.angrycode.wehub.utils.TagPrefUtils; 17 | 18 | /** 19 | * Search 20 | * Created by lancelot on 2016/12/3. 21 | */ 22 | 23 | public class SearchActivity extends BaseActivity { 24 | SearchView mSearchView; 25 | ReposFragment mFragment; 26 | 27 | @Override 28 | protected int getLayoutResource() { 29 | return R.layout.activity_search; 30 | } 31 | 32 | @Override 33 | protected void onCreate(@Nullable Bundle savedInstanceState) { 34 | super.onCreate(savedInstanceState); 35 | 36 | initTagFrag(); 37 | } 38 | 39 | void initTagFrag() { 40 | TagCacheFragment fragment = new TagCacheFragment(); 41 | fragment.setOnTagClickListener(tag -> mSearchView.setQuery(tag, true)); 42 | getSupportFragmentManager().beginTransaction() 43 | .replace(R.id.search_container, fragment) 44 | .commitAllowingStateLoss(); 45 | } 46 | 47 | void initSearch() { 48 | mSearchView.setIconified(false); 49 | mSearchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { 50 | @Override 51 | public boolean onQueryTextSubmit(String query) { 52 | query = query.trim(); 53 | if (mFragment == null) { 54 | mFragment = ReposFragment.newInstance(query); 55 | getSupportFragmentManager().beginTransaction() 56 | .replace(R.id.search_container, mFragment) 57 | .commitAllowingStateLoss(); 58 | } else { 59 | mFragment.search(query); 60 | } 61 | 62 | TagPrefUtils.appendTags(getApplicationContext(), query); 63 | return true; 64 | } 65 | 66 | @Override 67 | public boolean onQueryTextChange(String newText) { 68 | return false; 69 | } 70 | }); 71 | } 72 | 73 | public void clearFocus() { 74 | if (mSearchView != null) { 75 | mSearchView.clearFocus(); 76 | } 77 | } 78 | 79 | @Override 80 | public boolean onCreateOptionsMenu(Menu menu) { 81 | getMenuInflater().inflate(R.menu.activity_search_menu, menu); 82 | MenuItem menuItem = menu.findItem(R.id.action_search_view); 83 | mSearchView = (SearchView) MenuItemCompat.getActionView(menuItem); 84 | 85 | initSearch(); 86 | MenuItemCompat.collapseActionView(menuItem); 87 | MenuItemCompat.expandActionView(menuItem); 88 | return true; 89 | } 90 | 91 | public static void launch(Context context) { 92 | Intent intent = new Intent(context, SearchActivity.class); 93 | context.startActivity(intent); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | WeCode 3 | 4 | 加载中... 5 | 6 | 请求数据失败 7 | Observer出错 8 | 9 | WebView 10 | 11 | Open navigation drawer 12 | Close navigation drawer 13 | 14 | 搜索 15 | WeCode %s 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 | "开发者自白\n\n" 46 | 47 | "WeCode的产生源于我的一个小需求:就是查找当前GitHub上最热门的项目。WeCode帮助我随时关注当前的热门趋势。" 48 | "它的数据抓取自https://github.com/trending,就像GitHub上说的一样:See what the GitHub community is most excited about today." 49 | "每天都从这里开始,这已成为我工作的一部分。如果你喜欢,可以请我喝杯咖啡,万分感谢。\n\n" 50 | 51 | "支付宝打赏我:elgoog1900@gmail.com(长按可复制)\n\n" 52 | 53 | "开源协议\n\n" 54 | 55 | "WeCode崇尚开源,我的代码也是建立在前人的成果之上的,WeCode也会完全开源,可以关注我的GitHub:https://github.com/wecodexyz。感谢以下开源库的组织以及作者。\n\n" 56 | 57 | "com.android.support:design:25.0.1\n" 58 | "com.android.support:appcompat-v7:25.0.1\n" 59 | "com.android.support:recyclerview-v7:25.0.1\n" 60 | "com.android.support:cardview-v7:25.0.1\n" 61 | "com.android.support:customtabs:25.0.1\n" 62 | "io.reactivex:rxjava:1.1.6\n" 63 | "io.reactivex:rxandroid:1.2.1\n" 64 | "com.squareup.okhttp3:okhttp:3.3.1\n" 65 | "com.squareup.retrofit2:retrofit:2.1.0\n" 66 | "com.squareup.retrofit2:converter-gson:2.1.0\n" 67 | "com.squareup.retrofit2:adapter-rxjava:2.1.0\n" 68 | "com.squareup.okhttp3:logging-interceptor:3.3.1\n" 69 | "com.jakewharton:butterknife:8.4.0\n" 70 | "com.github.bumptech.glide:glide:3.7.0\n" 71 | "me.gujun.android.taggroup:library:1.4@aar\n" 72 | "org.greenrobot:eventbus:3.0.0\n" 73 | "org.greenrobot:greendao:3.2.0\n\n" 74 | 75 | 76 | Settings 77 | 更新 78 | 已复制到粘贴板 79 | 已有新版了哦 80 | 更新WeCode%s 81 | 马上下载 82 | 83 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/ui/component/drag/ItemDragHelperCallback.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.ui.component.drag; 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.support.v7.widget.helper.ItemTouchHelper; 7 | 8 | import net.angrycode.wehub.ui.adapter.LangAdapter; 9 | 10 | public class ItemDragHelperCallback extends ItemTouchHelper.Callback { 11 | 12 | @Override 13 | public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { 14 | int dragFlags; 15 | RecyclerView.LayoutManager manager = recyclerView.getLayoutManager(); 16 | if (manager instanceof GridLayoutManager || manager instanceof StaggeredGridLayoutManager) { 17 | dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT; 18 | } else { 19 | dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN; 20 | } 21 | // 如果想支持滑动(删除)操作, swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END 22 | int swipeFlags = 0; 23 | return makeMovementFlags(dragFlags, swipeFlags); 24 | } 25 | 26 | @Override 27 | public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { 28 | // 不同Type之间不可移动 29 | if (viewHolder.getItemViewType() != target.getItemViewType()) { 30 | return false; 31 | } 32 | if (viewHolder.getItemViewType() != LangAdapter.VIEW_TYPE_LANG_SEL_ITEM) { 33 | return false; 34 | } 35 | if (target.getAdapterPosition() == 1 || viewHolder.getAdapterPosition() == 1) { 36 | return false; 37 | } 38 | 39 | if (recyclerView.getAdapter() instanceof OnItemMoveListener) { 40 | OnItemMoveListener listener = ((OnItemMoveListener) recyclerView.getAdapter()); 41 | return listener.onItemMove(viewHolder.getAdapterPosition(), target.getAdapterPosition()); 42 | } 43 | return false; 44 | } 45 | 46 | @Override 47 | public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { 48 | 49 | } 50 | 51 | @Override 52 | public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) { 53 | // 不在闲置状态 54 | if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) { 55 | if (viewHolder instanceof OnDragVHListener) { 56 | OnDragVHListener itemViewHolder = (OnDragVHListener) viewHolder; 57 | itemViewHolder.onItemSelected(); 58 | } 59 | } 60 | super.onSelectedChanged(viewHolder, actionState); 61 | } 62 | 63 | @Override 64 | public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { 65 | if (viewHolder instanceof OnDragVHListener) { 66 | OnDragVHListener itemViewHolder = (OnDragVHListener) viewHolder; 67 | itemViewHolder.onItemFinish(); 68 | } 69 | super.clearView(recyclerView, viewHolder); 70 | } 71 | 72 | @Override 73 | public boolean isLongPressDragEnabled() { 74 | return false; 75 | } 76 | 77 | @Override 78 | public boolean isItemViewSwipeEnabled() { 79 | // 不支持滑动功能 80 | return false; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/ui/adapter/BaseSimpleRecycleAdapter.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.ui.adapter; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | /** 12 | * 封装了RecycleAdapter 13 | * Created by lancelot on 2016/12/3. 14 | */ 15 | 16 | public abstract class BaseSimpleRecycleAdapter extends RecyclerView.Adapter { 17 | private OnItemClickListener mOnItemClickListener; 18 | private OnItemLongClickListener mOnItemLongClickListener; 19 | protected List mList = new ArrayList<>(); 20 | // private int mCount; 21 | 22 | public void setData(List list) { 23 | mList = list; 24 | // if (list != null) { 25 | // mCount = list.size(); 26 | // } 27 | notifyDataSetChanged(); 28 | } 29 | 30 | public void addData(T t) { 31 | if (mList != null) { 32 | mList = new ArrayList<>(); 33 | } 34 | mList.add(t); 35 | notifyItemInserted(mList.size() - 1); 36 | } 37 | 38 | public List getData() { 39 | return mList; 40 | } 41 | 42 | @Override 43 | public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 44 | int layoutRes = getItemLayout(viewType); 45 | if (layoutRes > 0) { 46 | View itemView = LayoutInflater.from(parent.getContext()).inflate(getItemLayout(viewType), parent, false); 47 | return new ViewHolder(itemView); 48 | } 49 | return null; 50 | } 51 | 52 | /** 53 | * 获取item layout 54 | * 55 | * @return 56 | */ 57 | public abstract int getItemLayout(int viewType); 58 | 59 | /** 60 | * 渲染item 61 | * 62 | * @param holder 63 | * @param position 64 | */ 65 | public abstract void onRender(ViewHolder holder, int position); 66 | 67 | @Override 68 | public void onBindViewHolder(ViewHolder holder, int position) { 69 | if (mOnItemClickListener != null) { 70 | holder.itemView.setOnClickListener(view -> mOnItemClickListener.onItemClick(holder.itemView, position)); 71 | } 72 | if (mOnItemLongClickListener != null) { 73 | holder.itemView.setOnLongClickListener(view -> { 74 | mOnItemLongClickListener.onItemLongClick(holder.itemView, position); 75 | return true; 76 | }); 77 | } 78 | onRender(holder, holder.getAdapterPosition()); 79 | } 80 | 81 | @Override 82 | public int getItemCount() { 83 | return mList.size(); 84 | } 85 | 86 | public T getItem(int position) { 87 | return mList.get(position); 88 | } 89 | 90 | public void setOnItemClickListener(OnItemClickListener listener) { 91 | mOnItemClickListener = listener; 92 | } 93 | 94 | public void setOnItemLongClickListener(OnItemLongClickListener listener) { 95 | mOnItemLongClickListener = listener; 96 | } 97 | 98 | public interface OnItemClickListener { 99 | void onItemClick(View itemView, int position); 100 | } 101 | 102 | public interface OnItemLongClickListener { 103 | void onItemLongClick(View itemView, int position); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/ui/adapter/ThemeAdapter.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.ui.adapter; 2 | 3 | import android.view.View; 4 | import android.widget.TextView; 5 | 6 | import net.angrycode.wehub.R; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | /** 12 | * Created by lancelot on 2016/12/18. 13 | */ 14 | 15 | public class ThemeAdapter extends BaseSimpleRecycleAdapter { 16 | int mCurrTheme; 17 | 18 | public ThemeAdapter() { 19 | List list = new ArrayList<>(); 20 | list.add(new Theme(R.style.AppTheme)); 21 | list.add(new Theme(R.style.AppTheme_Green)); 22 | list.add(new Theme(R.style.AppTheme_Red)); 23 | list.add(new Theme(R.style.AppTheme_Purple)); 24 | list.add(new Theme(R.style.AppTheme_Gray)); 25 | setData(list); 26 | } 27 | 28 | @Override 29 | public int getItemLayout(int viewType) { 30 | return R.layout.item_theme; 31 | } 32 | 33 | public void setCurrTheme(int currTheme) { 34 | mCurrTheme = currTheme; 35 | notifyDataSetChanged(); 36 | } 37 | 38 | @Override 39 | public void onRender(ViewHolder holder, int position) { 40 | Theme theme = getItem(position); 41 | holder.getView(R.id.iv_theme).setBackgroundResource(theme.color); 42 | holder.getView(R.id.iv_check).setVisibility(mCurrTheme == theme.getStyle() ? View.VISIBLE : View.INVISIBLE); 43 | TextView textView = holder.getView(R.id.tv_theme_name); 44 | textView.setText(theme.name); 45 | } 46 | 47 | 48 | public static class Theme { 49 | private int color; 50 | private int name; 51 | private int style; 52 | 53 | public Theme(int style) { 54 | this.style = style; 55 | switch (style) { 56 | case R.style.AppTheme: 57 | color = R.color.primary; 58 | name = R.string.theme_default; 59 | break; 60 | case R.style.AppTheme_Gray: 61 | color = R.color.primary_gray; 62 | name = R.string.theme_gray; 63 | break; 64 | case R.style.AppTheme_Green: 65 | color = R.color.primary_green; 66 | name = R.string.theme_green; 67 | break; 68 | case R.style.AppTheme_Purple: 69 | color = R.color.primary_purple; 70 | name = R.string.theme_purple; 71 | break; 72 | case R.style.AppTheme_Red: 73 | color = R.color.primary_red; 74 | name = R.string.theme_red; 75 | break; 76 | } 77 | } 78 | 79 | public int getColor() { 80 | return color; 81 | } 82 | 83 | public void setColor(int color) { 84 | this.color = color; 85 | } 86 | 87 | public int getName() { 88 | return name; 89 | } 90 | 91 | public void setName(int name) { 92 | this.name = name; 93 | } 94 | 95 | public int getStyle() { 96 | return style; 97 | } 98 | 99 | public void setStyle(int style) { 100 | this.style = style; 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/api/BaseApi.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.api; 2 | 3 | import android.text.TextUtils; 4 | 5 | import java.io.IOException; 6 | 7 | import okhttp3.Interceptor; 8 | import okhttp3.OkHttpClient; 9 | import okhttp3.Request; 10 | import okhttp3.Response; 11 | import retrofit2.Retrofit; 12 | import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory; 13 | import retrofit2.converter.gson.GsonConverterFactory; 14 | 15 | public abstract class BaseApi { 16 | public static String API_SERVER = "https://api.github.com"; 17 | private static final OkHttpClient sOkHttpClient = new OkHttpClient(); 18 | private static Retrofit sRetrofit; 19 | 20 | static { 21 | sOkHttpClient.newBuilder() 22 | .addInterceptor(new Interceptor() { 23 | @Override 24 | public Response intercept(Chain chain) throws IOException { 25 | Request original = chain.request(); 26 | 27 | String[] headers = { 28 | "application/vnd.github.html+json", 29 | "application/vnd.github.raw+json" 30 | }; 31 | 32 | // String token = TokenStore.getInstance(context).getToken(); 33 | String token = null; 34 | Request.Builder requestBuilder = original.newBuilder() 35 | .header("Authorization", "Token " + token) 36 | .method(original.method(), original.body()); 37 | 38 | if (original.header("Accept") == null) { 39 | requestBuilder.addHeader("Accept", TextUtils.join(",", headers)); 40 | } 41 | 42 | Request request = requestBuilder.build(); 43 | return chain.proceed(request); 44 | } 45 | }).build(); 46 | } 47 | 48 | protected static Retrofit getRetrofit() { 49 | if (sRetrofit == null) { 50 | // Context context = TrendingApplication.get(); 51 | //设定30秒超时 52 | // mOkHttpClient.setConnectTimeout(30, TimeUnit.SECONDS); 53 | //设置拦截器,以用于自定义Cookies的设置 54 | // mOkHttpClient.networkInterceptors() 55 | // .add(new CookiesInterceptor(context)); 56 | //设置缓存目录 57 | // File cacheDirectory = new File(context.getCacheDir() 58 | // .getAbsolutePath(), "HttpCache"); 59 | // Cache cache = new Cache(cacheDirectory, 20 * 1024 * 1024); 60 | // mOkHttpClient.setCache(cache); 61 | //构建Retrofit 62 | sRetrofit = new Retrofit.Builder() 63 | //配置服务器路径 64 | .baseUrl(getBaseUrl() + "/") 65 | //设置日期解析格式,这样可以直接解析Date类型 66 | // .setDateFormat("yyyy-MM-dd HH:mm:ss") 67 | //配置转化库,默认是Gson 68 | .addConverterFactory(GsonConverterFactory.create()) 69 | //配置回调库,采用RxJava 70 | .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) 71 | //设置OKHttpClient为网络客户端 72 | .client(sOkHttpClient) 73 | .build(); 74 | } 75 | return sRetrofit; 76 | } 77 | 78 | protected static String getBaseUrl() { 79 | return API_SERVER; 80 | } 81 | } -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'me.tatarka.retrolambda' 3 | apply plugin: 'com.jakewharton.butterknife' 4 | apply plugin: 'org.greenrobot.greendao' 5 | 6 | android { 7 | compileSdkVersion 25 8 | buildToolsVersion "25.0.0" 9 | signingConfigs { 10 | config { 11 | storeFile file(KEY_STORE) 12 | keyAlias KEY_ALIATS 13 | keyPassword KEY_PASSWORD 14 | storePassword KEY_PASSWORD 15 | } 16 | debug { 17 | storeFile file(KEY_STORE) 18 | keyAlias KEY_ALIATS 19 | keyPassword KEY_PASSWORD 20 | storePassword KEY_PASSWORD 21 | } 22 | } 23 | defaultConfig { 24 | applicationId "net.angrycode.wehub" 25 | minSdkVersion 15 26 | targetSdkVersion 25 27 | versionCode 2 28 | versionName "1.0.2" 29 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 30 | } 31 | buildTypes { 32 | release { 33 | minifyEnabled true 34 | shrinkResources true 35 | zipAlignEnabled true 36 | signingConfig signingConfigs.config 37 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 38 | } 39 | } 40 | compileOptions { 41 | sourceCompatibility JavaVersion.VERSION_1_8 42 | targetCompatibility JavaVersion.VERSION_1_8 43 | } 44 | applicationVariants.all { variant -> 45 | variant.outputs.each { output -> 46 | def outputFile = output.outputFile 47 | if (variant.buildType.name.equals('release')) { 48 | //定义打包名称 49 | def fileName = "app_v${defaultConfig.versionName}_${buildTime()}.apk" 50 | output.outputFile = new File(outputFile.parent, fileName) 51 | } 52 | 53 | 54 | } 55 | } 56 | 57 | } 58 | 59 | def buildTime() { 60 | Date date = new Date(); 61 | return date.format("yyyyMMdd_HHmm", TimeZone.getDefault()) 62 | } 63 | 64 | greendao { 65 | schemaVersion 2 66 | } 67 | 68 | dependencies { 69 | compile fileTree(include: ['*.jar'], dir: 'libs') 70 | compile 'com.android.support:appcompat-v7:25.0.1' 71 | compile 'com.android.support:recyclerview-v7:25.0.1' 72 | compile 'com.android.support:cardview-v7:25.0.1' 73 | //design 74 | compile 'com.android.support:design:25.0.1' 75 | //custom tabs 76 | compile 'com.android.support:customtabs:25.0.1' 77 | compile project(':customtabs') 78 | 79 | //编译RxJava 80 | compile 'io.reactivex:rxjava:1.1.6' 81 | //编译RxAndroid 82 | compile 'io.reactivex:rxandroid:1.2.1' 83 | //编译Retrofit及其相关库,包括Gson 84 | compile 'com.squareup.okhttp3:okhttp:3.3.1' 85 | compile 'com.squareup.retrofit2:retrofit:2.1.0' 86 | compile 'com.squareup.retrofit2:converter-gson:2.1.0' 87 | compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0' 88 | compile 'com.squareup.okhttp3:logging-interceptor:3.3.1' 89 | //view injector 90 | compile 'com.jakewharton:butterknife:8.4.0' 91 | annotationProcessor 'com.jakewharton:butterknife-compiler:8.4.0' 92 | //glide 93 | compile 'com.github.bumptech.glide:glide:3.7.0' 94 | //TagGroup 95 | compile 'me.gujun.android.taggroup:library:1.4@aar' 96 | //EventBus 97 | compile 'org.greenrobot:eventbus:3.0.0' 98 | compile 'org.greenrobot:greendao:3.2.0' 99 | //test stuff 100 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 101 | exclude group: 'com.android.support', module: 'support-annotations' 102 | }) 103 | testCompile 'junit:junit:4.12' 104 | 105 | } 106 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_repo.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 17 | 18 | 25 | 26 | 35 | 36 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 71 | 72 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/ui/component/WebViewActivity.java: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package net.angrycode.wehub.ui.component; 16 | 17 | import android.graphics.Bitmap; 18 | import android.os.Build; 19 | import android.os.Bundle; 20 | import android.view.MenuItem; 21 | import android.view.View; 22 | import android.webkit.WebChromeClient; 23 | import android.webkit.WebSettings; 24 | import android.webkit.WebView; 25 | import android.webkit.WebViewClient; 26 | import android.widget.ProgressBar; 27 | 28 | import net.angrycode.wehub.R; 29 | import net.angrycode.wehub.ui.activity.BaseActivity; 30 | 31 | import butterknife.BindView; 32 | 33 | /** 34 | * This Activity is used as a fallback when there is no browser installed that supports 35 | * Chrome Custom Tabs 36 | */ 37 | public class WebViewActivity extends BaseActivity { 38 | 39 | public static final String EXTRA_URL = "extra.url"; 40 | 41 | @BindView(R.id.progress) 42 | ProgressBar mProgressBar; 43 | @BindView(R.id.webview) 44 | WebView mWebView; 45 | 46 | 47 | @Override 48 | protected int getLayoutResource() { 49 | return R.layout.activity_webview; 50 | } 51 | 52 | @Override 53 | protected void onCreate(Bundle savedInstanceState) { 54 | super.onCreate(savedInstanceState); 55 | String url = getIntent().getStringExtra(EXTRA_URL); 56 | 57 | WebSettings webSettings = mWebView.getSettings(); 58 | webSettings.setJavaScriptEnabled(true); 59 | mWebView.setWebViewClient(new WebViewClient() { 60 | @Override 61 | public void onPageStarted(WebView view, String url, Bitmap favicon) { 62 | super.onPageStarted(view, url, favicon); 63 | if (isFinishing()) { 64 | return; 65 | } 66 | mProgressBar.setVisibility(View.VISIBLE); 67 | } 68 | 69 | @Override 70 | public void onPageFinished(WebView view, String url) { 71 | super.onPageFinished(view, url); 72 | if (isFinishing()) { 73 | return; 74 | } 75 | mProgressBar.setVisibility(View.GONE); 76 | } 77 | }); 78 | mWebView.setWebChromeClient(new WebChromeClient() { 79 | @Override 80 | public void onProgressChanged(WebView view, int newProgress) { 81 | super.onProgressChanged(view, newProgress); 82 | if (isFinishing()) { 83 | return; 84 | } 85 | if (Build.VERSION.SDK_INT >= 24) { 86 | mProgressBar.setProgress(newProgress, true); 87 | } else { 88 | mProgressBar.setProgress(newProgress); 89 | } 90 | } 91 | }); 92 | setTitle(url); 93 | mWebView.loadUrl(url); 94 | } 95 | 96 | @Override 97 | public boolean onOptionsItemSelected(MenuItem item) { 98 | switch (item.getItemId()) { 99 | // Respond to the action bar's Up/Home button 100 | case android.R.id.home: 101 | finish(); 102 | return true; 103 | } 104 | return super.onOptionsItemSelected(item); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/bean/TrendingRepo.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.bean; 2 | 3 | import org.greenrobot.greendao.annotation.Entity; 4 | import org.greenrobot.greendao.annotation.Id; 5 | import org.greenrobot.greendao.annotation.Index; 6 | import org.greenrobot.greendao.annotation.NotNull; 7 | import org.json.JSONObject; 8 | import org.greenrobot.greendao.annotation.Generated; 9 | 10 | import java.util.Date; 11 | 12 | /** 13 | * https://github.com/trending 14 | * Created by lancelot on 2016/12/2. 15 | */ 16 | @Entity(indexes = { 17 | @Index(value = "name, date DESC", unique = true) 18 | }) 19 | public class TrendingRepo { 20 | @Id 21 | private Long id; 22 | 23 | private String avatar; 24 | private String desc; 25 | @NotNull 26 | private String url; 27 | @NotNull 28 | private String name; 29 | private String owner; 30 | private String stars_today; 31 | 32 | private Date date; 33 | 34 | public TrendingRepo(JSONObject jsonObject) { 35 | avatar = jsonObject.optString("avatar"); 36 | desc = jsonObject.optString("desc"); 37 | url = jsonObject.optString("url"); 38 | name = jsonObject.optString("name"); 39 | owner = jsonObject.optString("owner"); 40 | stars_today = jsonObject.optString("stars_today"); 41 | } 42 | 43 | 44 | 45 | @Generated(hash = 34610607) 46 | public TrendingRepo(Long id, String avatar, String desc, @NotNull String url, 47 | @NotNull String name, String owner, String stars_today, Date date) { 48 | this.id = id; 49 | this.avatar = avatar; 50 | this.desc = desc; 51 | this.url = url; 52 | this.name = name; 53 | this.owner = owner; 54 | this.stars_today = stars_today; 55 | this.date = date; 56 | } 57 | 58 | 59 | 60 | @Generated(hash = 843815768) 61 | public TrendingRepo() { 62 | } 63 | 64 | 65 | 66 | public String getAvatar() { 67 | return avatar; 68 | } 69 | 70 | public String getDesc() { 71 | return desc; 72 | } 73 | 74 | 75 | public String getStars_today() { 76 | return stars_today; 77 | } 78 | 79 | public String getOwner() { 80 | return owner; 81 | } 82 | 83 | 84 | public Long getId() { 85 | return id; 86 | } 87 | 88 | public void setId(Long id) { 89 | this.id = id; 90 | } 91 | 92 | public void setAvatar(String avatar) { 93 | this.avatar = avatar; 94 | } 95 | 96 | public void setDesc(String desc) { 97 | this.desc = desc; 98 | } 99 | 100 | public String getUrl() { 101 | return url; 102 | } 103 | 104 | public void setUrl(String url) { 105 | this.url = url; 106 | } 107 | 108 | public String getName() { 109 | return name; 110 | } 111 | 112 | public void setName(String name) { 113 | this.name = name; 114 | } 115 | 116 | public void setOwner(String owner) { 117 | this.owner = owner; 118 | } 119 | 120 | public void setStars_today(String stars_today) { 121 | this.stars_today = stars_today; 122 | } 123 | 124 | public Date getDate() { 125 | return date; 126 | } 127 | 128 | public void setDate(Date date) { 129 | this.date = date; 130 | } 131 | 132 | @Override 133 | public boolean equals(Object o) { 134 | if (this == o) return true; 135 | if (o == null || getClass() != o.getClass()) return false; 136 | 137 | TrendingRepo repo1 = (TrendingRepo) o; 138 | 139 | if (!url.equals(repo1.url)) return false; 140 | if (!name.equals(repo1.name)) return false; 141 | return owner != null ? owner.equals(repo1.owner) : repo1.owner == null; 142 | 143 | } 144 | 145 | @Override 146 | public int hashCode() { 147 | int result = url != null ? url.hashCode() : 0; 148 | result = 31 * result + name.hashCode(); 149 | result = 31 * result + (owner != null ? owner.hashCode() : 0); 150 | return result; 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /app/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 /Users/lancelot/Library/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 | -dontwarn net.angrycode.wehub.** 19 | -keep class net.angrycode.wehub.event.** {*;} 20 | #gson相关的bean类不要混淆 21 | -keep class net.angrycode.wehub.bean.** {*;} 22 | #Glide 23 | -keep public class * implements com.bumptech.glide.module.GlideModule 24 | -keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** { 25 | **[] $VALUES; 26 | public *; 27 | } 28 | # for DexGuard only 29 | #-keepresourcexmlelements manifest/application/meta-data@value=GlideModule 30 | 31 | #okhttp 32 | -dontwarn okhttp3.** 33 | -keep class okhttp3.** { *;} 34 | -dontwarn okio.** 35 | 36 | # Retrofit 37 | -dontwarn retrofit2.** 38 | -dontwarn org.codehaus.mojo.** 39 | -keep class retrofit2.** { *; } 40 | -keepattributes Exceptions 41 | -keepclasseswithmembers class * { 42 | @retrofit.* ; 43 | } 44 | 45 | -keepclasseswithmembers interface * { 46 | @retrofit.* ; 47 | } 48 | #rx 49 | -keepclassmembers class rx.internal.util.unsafe.*ArrayQueue*Field* { 50 | long producerIndex; 51 | long consumerIndex; 52 | } 53 | -keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueProducerNodeRef { 54 | rx.internal.util.atomic.LinkedQueueNode producerNode; 55 | } 56 | -keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueConsumerNodeRef { 57 | rx.internal.util.atomic.LinkedQueueNode consumerNode; 58 | } 59 | -dontwarn sun.misc.Unsafe 60 | -keep class rx.schedulers.Schedulers { 61 | public static ; 62 | } 63 | -keep class rx.schedulers.ImmediateScheduler { 64 | public ; 65 | } 66 | -keep class rx.schedulers.TestScheduler { 67 | public ; 68 | } 69 | -keep class rx.schedulers.Schedulers { 70 | public static ** test(); 71 | } 72 | ##---------------Begin: proguard configuration for Gson ---------- 73 | # Gson uses generic type information stored in a class file when working with fields. Proguard 74 | # removes such information by default, so configure it to keep all of it. 75 | -keepattributes Signature 76 | 77 | # For using GSON @Expose annotation 78 | -keepattributes *Annotation* 79 | 80 | # Gson specific classes 81 | -keep class sun.misc.Unsafe { *; } 82 | #-keep class com.google.gson.stream.** { *; } 83 | 84 | # Application classes that will be serialized/deserialized over Gson 85 | -keep class com.google.gson.examples.android.model.** { *; } 86 | 87 | # Prevent proguard from stripping interface information from TypeAdapterFactory, 88 | # JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter) 89 | -keep class * implements com.google.gson.TypeAdapterFactory 90 | -keep class * implements com.google.gson.JsonSerializer 91 | -keep class * implements com.google.gson.JsonDeserializer 92 | 93 | ##---------------End: proguard configuration for Gson ---------- 94 | #greenDao 95 | -keepclassmembers class * extends org.greenrobot.greendao.AbstractDao { 96 | public static java.lang.String TABLENAME; 97 | } 98 | -keep class **$Properties 99 | 100 | # If you do not use SQLCipher: 101 | -dontwarn org.greenrobot.greendao.database.** 102 | 103 | # eventbus 104 | -keepattributes *Annotation* 105 | -keepclassmembers class ** { 106 | @org.greenrobot.eventbus.Subscribe ; 107 | } 108 | -keep enum org.greenrobot.eventbus.ThreadMode { *; } 109 | # Only required if you use AsyncExecutor 110 | -keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent { 111 | (java.lang.Throwable); 112 | } -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/ui/component/DividerItemDecoration.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.ui.component; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Paint; 6 | import android.graphics.Rect; 7 | import android.support.annotation.ColorRes; 8 | import android.support.v4.content.ContextCompat; 9 | import android.support.v7.widget.LinearLayoutManager; 10 | import android.support.v7.widget.RecyclerView; 11 | import android.util.TypedValue; 12 | import android.view.View; 13 | 14 | 15 | public class DividerItemDecoration extends RecyclerView.ItemDecoration { 16 | 17 | /* 18 | * RecyclerView的布局方向,默认先赋值 19 | * 为纵向布局 20 | * RecyclerView 布局可横向,也可纵向 21 | * 横向和纵向对应的分割想画法不一样 22 | * */ 23 | private int mOrientation = LinearLayoutManager.VERTICAL; 24 | 25 | /** 26 | * 分割线的高度,默认为1 27 | */ 28 | private int mDividerHeight = 1; 29 | 30 | /** 31 | * 分割线的画笔,和设置其属性 32 | * 来绘制个性分割线 33 | */ 34 | private Paint mPaint; 35 | 36 | /** 37 | * 构造方法传入布局方向,不可不传 38 | * 39 | * @param context 40 | * @param orientation 41 | */ 42 | public DividerItemDecoration(Context context, int orientation, @ColorRes int color) { 43 | this.mOrientation = orientation; 44 | if (orientation != LinearLayoutManager.VERTICAL && orientation != LinearLayoutManager.HORIZONTAL) { 45 | throw new IllegalArgumentException("请传入正确的参数"); 46 | } 47 | mDividerHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 0.5f, context.getResources().getDisplayMetrics()); 48 | mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 49 | mPaint.setColor(ContextCompat.getColor(context, color)); 50 | /*设置填充*/ 51 | mPaint.setStyle(Paint.Style.FILL); 52 | } 53 | 54 | @Override 55 | public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { 56 | if (mOrientation == LinearLayoutManager.VERTICAL) { 57 | drawVertical(c, parent); 58 | } else { 59 | drawHorizontal(c, parent); 60 | } 61 | } 62 | 63 | /** 64 | * 绘制纵向 item 分割线 65 | * 66 | * @param canvas 67 | * @param parent 68 | */ 69 | private void drawVertical(Canvas canvas, RecyclerView parent) { 70 | final int left = parent.getPaddingLeft(); 71 | final int right = parent.getMeasuredWidth() - parent.getPaddingRight(); 72 | final int childSize = parent.getChildCount(); 73 | for (int i = 0; i < childSize; i++) { 74 | final View child = parent.getChildAt(i); 75 | RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams(); 76 | final int top = child.getBottom() + layoutParams.bottomMargin; 77 | final int bottom = top + mDividerHeight; 78 | canvas.drawRect(left, top, right, bottom, mPaint); 79 | } 80 | } 81 | 82 | /** 83 | * 绘制横向 item 分割线 84 | * 85 | * @param canvas 86 | * @param parent 87 | */ 88 | private void drawHorizontal(Canvas canvas, RecyclerView parent) { 89 | final int top = parent.getPaddingTop(); 90 | final int bottom = parent.getMeasuredHeight() - parent.getPaddingBottom(); 91 | final int childSize = parent.getChildCount(); 92 | for (int i = 0; i < childSize; i++) { 93 | final View child = parent.getChildAt(i); 94 | RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams(); 95 | final int left = child.getRight() + layoutParams.rightMargin; 96 | final int right = left + mDividerHeight; 97 | canvas.drawRect(left, top, right, bottom, mPaint); 98 | } 99 | } 100 | 101 | /** 102 | * 设置item分割线的size 103 | * 104 | * @param outRect 105 | * @param view 106 | * @param parent 107 | * @param state 108 | */ 109 | @Override 110 | public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { 111 | if (mOrientation == LinearLayoutManager.VERTICAL) { 112 | outRect.set(0, 0, 0, mDividerHeight); 113 | } else { 114 | outRect.set(0, 0, mDividerHeight, 0); 115 | } 116 | } 117 | } -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/ui/activity/FavoritesActivity.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.ui.activity; 2 | 3 | import android.app.Activity; 4 | import android.net.Uri; 5 | import android.os.Bundle; 6 | import android.support.annotation.Nullable; 7 | import android.support.customtabs.CustomTabsIntent; 8 | import android.support.v4.content.ContextCompat; 9 | import android.support.v7.app.AlertDialog; 10 | import android.support.v7.widget.LinearLayoutManager; 11 | import android.support.v7.widget.RecyclerView; 12 | import android.view.View; 13 | 14 | import net.angrycode.wehub.R; 15 | import net.angrycode.wehub.bean.TrendingRepo; 16 | import net.angrycode.wehub.mvp.presenter.FavorPresenter; 17 | import net.angrycode.wehub.mvp.view.FavorView; 18 | import net.angrycode.wehub.ui.adapter.TrendsAdapter; 19 | import net.angrycode.wehub.ui.component.CustomTabActivityHelper; 20 | import net.angrycode.wehub.ui.component.DividerItemDecoration; 21 | import net.angrycode.wehub.ui.component.WebViewFallback; 22 | import net.angrycode.wehub.utils.ToastUtils; 23 | import net.angrycode.wehub.utils.Utils; 24 | 25 | import java.util.List; 26 | 27 | import butterknife.BindView; 28 | 29 | /** 30 | * Created by lancelot on 2016/12/9. 31 | */ 32 | 33 | public class FavoritesActivity extends BaseActivity implements FavorView { 34 | @BindView(R.id.recycle_view) 35 | RecyclerView mRecycleView; 36 | 37 | FavorPresenter mPresenter; 38 | TrendsAdapter mAdapter; 39 | 40 | @Override 41 | protected void onCreate(@Nullable Bundle savedInstanceState) { 42 | super.onCreate(savedInstanceState); 43 | 44 | init(); 45 | 46 | mPresenter = new FavorPresenter(this); 47 | mPresenter.getFavorList(); 48 | } 49 | 50 | private void init() { 51 | LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getActivity().getApplicationContext()); 52 | mRecycleView.setLayoutManager(linearLayoutManager); 53 | mRecycleView.addItemDecoration(new DividerItemDecoration(getActivity().getApplicationContext(), LinearLayoutManager.VERTICAL, R.color.divider)); 54 | mAdapter = new TrendsAdapter(); 55 | mRecycleView.setAdapter(mAdapter); 56 | mAdapter.setOnItemClickListener(this::onItemClick); 57 | mAdapter.setOnItemLongClickListener(this::onItemLongClick); 58 | } 59 | 60 | private void onItemClick(View itemView, int position) { 61 | String url = mAdapter.getItem(position).getUrl(); 62 | CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder(); 63 | builder.setToolbarColor(ContextCompat.getColor(getActivity(), R.color.primary)); 64 | CustomTabsIntent customTabsIntent = builder.build(); 65 | CustomTabActivityHelper.openCustomTab( 66 | getActivity(), customTabsIntent, Uri.parse(url), new WebViewFallback()); 67 | } 68 | 69 | private void onItemLongClick(View itemView, int position) { 70 | AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); 71 | builder.setItems(R.array.more_opt_favor, (dialog, which) -> { 72 | TrendingRepo repo = mAdapter.getItem(position); 73 | switch (which) { 74 | case 0: 75 | mPresenter.deleteFavor(repo); 76 | break; 77 | case 1: 78 | Utils.copyText(getActivity(), repo.getUrl()); 79 | break; 80 | } 81 | }); 82 | AlertDialog dialog = builder.create(); 83 | dialog.show(); 84 | } 85 | 86 | @Override 87 | protected int getLayoutResource() { 88 | return R.layout.activity_favorites; 89 | } 90 | 91 | @Override 92 | public void onGetFavorListFinish(List repoList) { 93 | mAdapter.setData(repoList); 94 | } 95 | 96 | @Override 97 | public void onDelFavorFinish(TrendingRepo repo) { 98 | mAdapter.remove(repo); 99 | } 100 | 101 | @Override 102 | public Activity getActivity() { 103 | return this; 104 | } 105 | 106 | @Override 107 | public void onRequestLoading() { 108 | showLoading(); 109 | } 110 | 111 | @Override 112 | public void onRequestFinished() { 113 | dismissLoading(); 114 | } 115 | 116 | @Override 117 | public void onRequestError(int code, String message) { 118 | ToastUtils.show(this, message); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/ui/activity/BaseActivity.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.ui.activity; 2 | 3 | import android.app.ProgressDialog; 4 | import android.content.res.Resources; 5 | import android.os.Bundle; 6 | import android.support.annotation.Nullable; 7 | import android.support.v7.app.AppCompatActivity; 8 | import android.support.v7.widget.Toolbar; 9 | import android.util.Log; 10 | import android.view.Menu; 11 | import android.view.MenuItem; 12 | import android.view.View; 13 | import android.widget.TextView; 14 | 15 | import net.angrycode.wehub.R; 16 | import net.angrycode.wehub.utils.ThemeUtils; 17 | 18 | import butterknife.ButterKnife; 19 | 20 | /** 21 | * Created by lancelot on 2016/5/30. 22 | */ 23 | public abstract class BaseActivity extends AppCompatActivity { 24 | 25 | protected abstract int getLayoutResource(); 26 | 27 | protected Toolbar mToolbar; 28 | protected TextView mToolbarTitle; 29 | 30 | protected ProgressDialog mLoading; 31 | 32 | @Override 33 | protected void onCreate(@Nullable Bundle savedInstanceState) { 34 | setTheme(getCurrTheme()); 35 | super.onCreate(savedInstanceState); 36 | int layoutResId = getLayoutResource(); 37 | try { 38 | if (layoutResId != 0) { 39 | setContentView(layoutResId); 40 | } 41 | } catch (Exception e) { 42 | Log.d("base activity", e.getMessage()); 43 | } 44 | // AppManager.getAppManager().addActivity(this); 45 | } 46 | 47 | @Override 48 | public void setContentView(int layoutResID) { 49 | super.setContentView(layoutResID); 50 | ButterKnife.bind(this); 51 | View v = findViewById(R.id.toolbar); 52 | if (v != null) { 53 | mToolbar = (Toolbar) v; 54 | setSupportActionBar(mToolbar); 55 | mToolbarTitle = (TextView) v.findViewById(R.id.toolbar_title); 56 | if (mToolbarTitle != null && getSupportActionBar() != null) { 57 | getSupportActionBar().setDisplayShowTitleEnabled(false); 58 | } 59 | if (getSupportActionBar() != null && isShowHomeAsUpIndicator()) { 60 | // getSupportActionBar().setIcon(R.drawable.app_divider_drawable); 61 | // getSupportActionBar().setHomeAsUpIndicator(R.drawable.abc_ic_ab_back_mtrl_am_alpha); 62 | 63 | getSupportActionBar().setHomeButtonEnabled(true); 64 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 65 | 66 | } 67 | 68 | mToolbar.setNavigationOnClickListener(view -> onBackPressed()); 69 | 70 | } 71 | } 72 | 73 | public void showLoading() { 74 | if (isFinishing()) { 75 | return; 76 | } 77 | if (mLoading == null) { 78 | mLoading = new ProgressDialog(this); 79 | } 80 | mLoading.setCancelable(false); 81 | mLoading.setCanceledOnTouchOutside(false); 82 | mLoading.setMessage(getResources().getString(R.string.loading)); 83 | mLoading.show(); 84 | } 85 | 86 | public void showLoading(int msg) { 87 | 88 | if (mLoading == null) { 89 | mLoading = new ProgressDialog(this); 90 | } 91 | mLoading.setMessage(getResources().getString(msg)); 92 | mLoading.show(); 93 | } 94 | 95 | public void dismissLoading() { 96 | if (mLoading != null) { 97 | mLoading.dismiss(); 98 | } 99 | } 100 | 101 | // @Override 102 | // public boolean onOptionsItemSelected(MenuItem item) { 103 | // if (item.getItemId() == android.R.id.home) { 104 | // onBackPressed(); 105 | // return true; 106 | // } 107 | // return super.onOptionsItemSelected(item); 108 | // } 109 | 110 | @Override 111 | public boolean onPrepareOptionsMenu(Menu menu) { 112 | int size = menu.size(); 113 | for (int i = 0; i < size; i++) { 114 | MenuItem item = menu.getItem(i); 115 | } 116 | return super.onPrepareOptionsMenu(menu); 117 | } 118 | 119 | protected boolean isShowHomeAsUpIndicator() { 120 | return true; 121 | } 122 | 123 | @Override 124 | public void onBackPressed() { 125 | super.onBackPressed(); 126 | } 127 | 128 | @Override 129 | protected void onDestroy() { 130 | if (mLoading != null && mLoading.isShowing()) { 131 | mLoading.dismiss(); 132 | } 133 | // AppManager.getAppManager().removeActivity(this); 134 | super.onDestroy(); 135 | } 136 | 137 | protected int getCurrTheme() { 138 | return ThemeUtils.getTheme(this); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/vector_logo_text.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 13 | 16 | 19 | 22 | 25 | 28 | 31 | 34 | 37 | 38 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/wehub/ui/fragment/ReposFragment.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.wehub.ui.fragment; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.v4.widget.SwipeRefreshLayout; 6 | import android.support.v7.app.AlertDialog; 7 | import android.support.v7.widget.LinearLayoutManager; 8 | import android.support.v7.widget.RecyclerView; 9 | import android.util.Log; 10 | import android.view.View; 11 | 12 | import net.angrycode.wehub.R; 13 | import net.angrycode.wehub.bean.Repos; 14 | import net.angrycode.wehub.bean.Repository; 15 | import net.angrycode.wehub.bean.TrendingRepo; 16 | import net.angrycode.wehub.mvp.presenter.ReposPresenter; 17 | import net.angrycode.wehub.mvp.view.RepositoriesView; 18 | import net.angrycode.wehub.ui.activity.SearchActivity; 19 | import net.angrycode.wehub.ui.adapter.ReposAdapter; 20 | import net.angrycode.wehub.ui.component.DividerItemDecoration; 21 | import net.angrycode.wehub.utils.Keyboard; 22 | import net.angrycode.wehub.utils.ToastUtils; 23 | import net.angrycode.wehub.utils.Utils; 24 | import net.angrycode.wehub.utils.WebUtils; 25 | 26 | import butterknife.BindView; 27 | 28 | /** 29 | * Created by lancelot on 2016/11/15. 30 | */ 31 | 32 | public class ReposFragment extends BaseFragment implements RepositoriesView { 33 | ReposPresenter mPresenter; 34 | @BindView(R.id.recycle_view) 35 | RecyclerView mRecycleView; 36 | @BindView(R.id.refresh_layout) 37 | SwipeRefreshLayout mRefreshLayout; 38 | 39 | ReposAdapter mAdapter; 40 | 41 | // String mLanguage; 42 | 43 | String mQuery; 44 | 45 | public static ReposFragment newInstance(String query) { 46 | ReposFragment fragment = new ReposFragment(); 47 | fragment.mQuery = query; 48 | return fragment; 49 | } 50 | 51 | @Override 52 | public int getLayoutResource() { 53 | return R.layout.fragment_repos; 54 | } 55 | 56 | @Override 57 | public void onActivityCreated(@Nullable Bundle savedInstanceState) { 58 | super.onActivityCreated(savedInstanceState); 59 | // mQuery = mLanguage+"&language:" + mLanguage; 60 | mPresenter = new ReposPresenter(this); 61 | search(mQuery); 62 | mRefreshLayout.setOnRefreshListener(() -> { 63 | mPresenter.getRepositories(mQuery); 64 | }); 65 | 66 | LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getActivity().getApplicationContext()); 67 | mRecycleView.setLayoutManager(linearLayoutManager); 68 | mRecycleView.addItemDecoration(new DividerItemDecoration(getActivity().getApplicationContext(), LinearLayoutManager.VERTICAL, R.color.divider)); 69 | mAdapter = new ReposAdapter(); 70 | mRecycleView.setAdapter(mAdapter); 71 | mAdapter.setOnItemClickListener((itemView, position) -> { 72 | String url = mAdapter.getItem(position).getHtml_url(); 73 | WebUtils.openUrl(getActivity(), url); 74 | }); 75 | mAdapter.setOnItemLongClickListener(this::onItemLongClick); 76 | } 77 | 78 | private void onItemLongClick(View itemView, int position) { 79 | AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); 80 | builder.setItems(R.array.more_opt, (dialog, which) -> { 81 | Repository repository = mAdapter.getItem(position); 82 | switch (which) { 83 | case 0: 84 | mPresenter.addFavor(repository.convert2TrendingRepo()); 85 | break; 86 | case 1: 87 | Utils.copyText(getActivity(), repository.getHtml_url()); 88 | break; 89 | } 90 | if (getActivity() instanceof SearchActivity) { 91 | ((SearchActivity) getActivity()).clearFocus(); 92 | } 93 | }); 94 | AlertDialog dialog = builder.create(); 95 | dialog.show(); 96 | } 97 | 98 | public void search(String query) { 99 | mPresenter.getRepositories(query); 100 | mRefreshLayout.setRefreshing(true); 101 | } 102 | 103 | @Override 104 | public void onGetRepositories(Repos repos) { 105 | Log.d("main", repos.toString()); 106 | mAdapter.setData(repos.getItems()); 107 | Keyboard.hide(getActivity().getCurrentFocus()); 108 | } 109 | 110 | @Override 111 | public void onAddFavorFinish(TrendingRepo repo) { 112 | ToastUtils.show(getActivity(), R.string.add_favor_finish); 113 | } 114 | 115 | @Override 116 | public void onRequestLoading() { 117 | mRefreshLayout.setRefreshing(true); 118 | } 119 | 120 | @Override 121 | public void onRequestFinished() { 122 | mRefreshLayout.setRefreshing(false); 123 | } 124 | 125 | @Override 126 | public void onRequestError(int code, String message) { 127 | ToastUtils.show(getContext(), message); 128 | mRefreshLayout.setRefreshing(false); 129 | } 130 | } 131 | --------------------------------------------------------------------------------