├── .gitignore ├── LICENSE ├── README.md ├── README.z.md ├── app ├── .gitignore ├── bbupdate.xml ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── me │ │ └── itangqi │ │ └── buildingblocks │ │ ├── ApplicationTest.java │ │ ├── IntentTest.java │ │ ├── OtherTest.java │ │ └── ParserTest.java │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── me │ │ └── itangqi │ │ └── buildingblocks │ │ ├── domain │ │ ├── api │ │ │ └── ZhihuApi.java │ │ ├── application │ │ │ └── App.java │ │ ├── db │ │ │ └── SQLiteHelper.java │ │ ├── receiver │ │ │ └── UpdaterReceiver.java │ │ ├── service │ │ │ └── Updater.java │ │ └── utils │ │ │ ├── Constants.java │ │ │ ├── CrashCatcher.java │ │ │ ├── DeviceUtils.java │ │ │ ├── IntentKeys.java │ │ │ ├── NetworkUtils.java │ │ │ ├── PrefUtils.java │ │ │ ├── ShareUtils.java │ │ │ ├── ThemeUtils.java │ │ │ ├── ToastUtils.java │ │ │ └── VersionUtils.java │ │ ├── model │ │ ├── DailyModel.java │ │ ├── IDaily.java │ │ ├── IGsonCallBack.java │ │ ├── IHtmlCallBack.java │ │ ├── IHttpCallBack.java │ │ └── entity │ │ │ ├── Daily.java │ │ │ ├── DailyGson.java │ │ │ ├── DailyResult.java │ │ │ └── Theme.java │ │ ├── presenters │ │ ├── GsonNewsPresenter.java │ │ ├── MainActivityPresenter.java │ │ ├── NewsListFragmentPresenter.java │ │ └── WebActivityPresenter.java │ │ └── view │ │ ├── IGsonNews.java │ │ ├── IMainActivity.java │ │ ├── IViewPager.java │ │ ├── IWebView.java │ │ ├── adapter │ │ ├── DailyListAdapter.java │ │ └── GooglePlacesAdapter.java │ │ ├── ui │ │ ├── activity │ │ │ ├── AboutActivity.java │ │ │ ├── GooglePlacesActivity.java │ │ │ ├── GsonViewActivity.java │ │ │ ├── MainActivity.java │ │ │ ├── PickPhotoActivity.java │ │ │ ├── PrefsActivity.java │ │ │ ├── SearchResultActivity.java │ │ │ ├── WebActivity.java │ │ │ └── base │ │ │ │ ├── BaseActivity.java │ │ │ │ └── SwipeBackActivity.java │ │ └── fragment │ │ │ ├── DailyListFragment.java │ │ │ └── PrefsFragment.java │ │ └── widget │ │ ├── CircleImageView.java │ │ ├── FABCircleProgressBehavior.java │ │ ├── GlidePaletteListenerImp.java │ │ ├── RecyclerViewItemDecoration.java │ │ └── ScrollingFABBehavior.java │ └── res │ ├── color │ ├── state_selector_dark.xml │ └── state_selector_light.xml │ ├── drawable-xhdpi │ └── icon.png │ ├── drawable-xxhdpi │ ├── drawer_action_collection.png │ ├── drawer_action_read.png │ ├── drawer_action_share.png │ ├── drawer_header.jpg │ ├── ic_tile_bg.png │ └── icon.png │ ├── drawable-xxxhdpi │ └── icon.png │ ├── drawable │ ├── bg_card_nopic.xml │ ├── bg_pick_photo.xml │ ├── item_divider_black.xml │ └── item_divider_white.xml │ ├── layout-v21 │ └── service_update_notification.xml │ ├── layout │ ├── activity_about.xml │ ├── activity_base.xml │ ├── activity_gson_news.xml │ ├── activity_main.xml │ ├── activity_main_dialog.xml │ ├── activity_pick_crop.xml │ ├── activity_place_auto_complete.xml │ ├── activity_prefs.xml │ ├── activity_webview.xml │ ├── drawer_header.xml │ ├── fragment_daily_list.xml │ ├── item_daily_image_info.xml │ ├── item_daily_text_info.xml │ ├── service_update_notification.xml │ ├── widget_floating_action_button.xml │ └── widget_toolbar.xml │ ├── menu │ ├── menu_about.xml │ ├── menu_drawer.xml │ ├── menu_main.xml │ └── menu_pick_photo.xml │ ├── mipmap-xhdpi │ ├── ic_avatar_tangqi.png │ ├── ic_refresh_white_24dp.png │ └── ic_share_white_24dp.png │ ├── values-v21 │ ├── dimens.xml │ └── styles.xml │ ├── values │ ├── android_material_design_colours.xml │ ├── arrays.xml │ ├── attrs.xml │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml │ └── xml │ ├── prefs.xml │ └── searchable.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── screenshots ├── s1-1.png ├── s1-2.png ├── s1.png ├── s2.png ├── s3.png ├── s4.png ├── s5.png ├── s6.png ├── s7.png ├── s8.png ├── s9-1.png └── s9.png └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # built application files 2 | *.ap_ 3 | 4 | # files for the dex VM 5 | *.dex 6 | 7 | # Java class files 8 | *.class 9 | 10 | # generated files 11 | bin/ 12 | gen/ 13 | 14 | # Local configuration file (sdk path, etc) 15 | local.properties 16 | 17 | # Proguard folder generated by Eclipse 18 | proguard/ 19 | 20 | # Ignore gradle files 21 | .gradle/ 22 | build/ 23 | 24 | # Eclipse project files 25 | .classpath 26 | .project 27 | .settings/ 28 | 29 | # Intellij project files 30 | *.iml 31 | *.ipr 32 | *.iws 33 | .idea/ 34 | 35 | # Mac system files 36 | .DS_Store 37 | 38 | *.keystore 39 | gradle.properties 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # This project is no longer maintained. 2 | 3 | [中文介绍](README.z.md) 4 | 5 | ![logo](http://7xk54v.com1.z0.glb.clouddn.com/images/logo/icon.png) 6 | 7 | > **Building Blocks** with Zhihu Daily API as a source of data; OptionsMenu as extensions of the entry; Design Support Library as UI design leader.I know, it sounds cool! 8 | 9 | ### How to use 10 | 11 | > dev branch is currently maintained by [troyliu0105](https://github.com/troyliu0105), he will continue to reconstruct the building blocks and increase the fun of the function, if you also like, then welcome **Star** and **Fork** this branch! 12 | 13 | Well, actually, I have to help you complete the overall framework set up, you need to do is just based on actual demand, simply replace data at source, and modify UI, you can quickly complete a new application development! 14 | 15 | Think about it, it is not very exciting? So hurry **Star** and **Fork** it! Your support will be my greatest motivation and praise! 16 | 17 | ## Update Log 18 | 19 | ### 0.7.0 20 | 21 | - New - night mode 22 | - New - crash log collect 23 | - New - auto update 24 | - New - `html+`mode(actually we just modified the html) 25 | - FIX - auto clear bug 26 | - FIX - MainActivity flicker bug 27 | - Modify - parts of UI 28 | 29 | ##### Known Bugs 30 | 31 | - **Location** in Android 6.0 will crash 32 | 33 | 34 | - problems with showing some article (`html+` mode isn't influenced) 35 | 36 | ### v0.6.0 37 | 38 | - New - use FAB to refresh 39 | - New - WebView cache at `html` mode 40 | - New - json mode(faster, but has some view bugs) 41 | - New - auto clear out of date cache 42 | - New - use SQLite to store data 43 | - Reconstruction - Refactoring with MVP 44 | - New - Logo Support by [Mao](http://weibo.com/cat93/) 45 | - Modify - parts of UI 46 | 47 | ### v0.5.1 48 | 49 | - Abandon - CardView, return to simple, more in line with the design specifications 50 | - Abandon - third party DrawerLayout, flashy guy 51 | - Abandon - WebView article title bar at the top, instead of sharing buttons 52 | - New - Search function, display the search results page jump 53 | - New - Right slide gestures, return to the previous page function (Bug v0.5.0 appearing been fixed) 54 | - Optimization - Extended capabilities as a drawer sub-menu options 55 | - Reconstruction - As far as possible, the code is written and structured elegance 56 | 57 | ## Demo 58 | 59 | [Download](http://7xk54v.com1.z0.glb.clouddn.com/app/bb/0.7.0.apk) 60 | 61 | ## Screenshots 62 | 63 | screenshot screenshot 64 | 65 | screenshot screenshot 66 | 67 | screenshot screenshot 68 | 69 | screenshot screenshot 70 | 71 | screenshot screenshot 72 | 73 | screenshot screenshot 74 | 75 | ## Dependencies 76 | 77 | - [glide](https://github.com/bumptech/glide) 78 | - [butterknife](https://github.com/JakeWharton/butterknife) 79 | - [android-crop](https://github.com/jdamcd/android-crop) 80 | - [android-async-http](https://github.com/loopj/android-async-http) 81 | - [SwipeBackLayout](https://github.com/ikew0ng/SwipeBackLayout) 82 | - [Jsoup](http://http://jsoup.org/) 83 | 84 | ## Thanks 85 | 86 | - Thank [drakeet](https://github.com/drakeet) and his [妹纸&gank.io](https://github.com/drakeet/Meizhi), the code he wrote is very beautiful:), I learned a lot and applied to the project. 87 | - Thank [Izzy Leung](https://github.com/izzyleung) and his [知乎日报·净化](https://github.com/izzyleung/ZhihuDailyPurify),the initial prototype project will come from this. 88 | 89 | ## Contributors 90 | 91 | - dev version: [troyliu0105](https://github.com/troyliu0105) 92 | - Logo: [Mao](http://weibo.com/cat93/) & [troyliu0105](https://github.com/troyliu0105) 93 | 94 | ## Contact Me 95 | 96 | Born in 1992, now a student of Southeast University, master of software engineerin. Loving technology, programming, reading and sports. 97 | 98 | I will graduate in June 2017, expect the internship or full-time job in Android or iOS. 99 | 100 | If you have any questions or want to make friends with me, please feel free to contact me : [imtangqi#gmail.com](mailto:imtangqi@gmail.com "Welcome to contact me") 101 | 102 | ## Project Home 103 | 104 | [http://imtangqi.com/2015/09/03/building-blocks/](http://imtangqi.com/2015/09/03/building-blocks/) 105 | 106 | ## License 107 | 108 | [GPLv3](/LICENSE) 109 | -------------------------------------------------------------------------------- /README.z.md: -------------------------------------------------------------------------------- 1 | Building Blocks - 积木 2 | ===================== 3 | 4 | ![logo](http://7xk54v.com1.z0.glb.clouddn.com/images/logo/icon.png) 5 | 6 | >**积木** - 一个以知乎日报作为数据展现内容;以抽屉菜单作为功能扩展入口;遵循 Material Design 设计规范的应用;好吧,我知道,这听起来就很酷! 7 | 8 | ### How to use - 如何使用 9 | 10 | >dev 分支由 [troyliu0105](https://github.com/troyliu0105) 同学全力维护,他会不断对**积木**进行重构与增加好玩的功能,这非常酷,欢迎 **Star** 与 **Fork** 此分支! 11 | 12 | 那么,你该如何利用「她」呢? 13 | 14 | 好啦,其实我已经帮你完成了应用整体框架的搭建,你需要做的,仅仅是依据自己的实际需求,简单的替换下数据来源,比如在 [APIStore](http://apistore.baidu.com/) 上就有详细的类别供你选择,然后再改改 UI,一款全新应用就完成啦! 15 | 16 | 想想,是不是还有点小激动?那么赶紧 **Star** 与 **Fork** 吧!你的支持将成为我最大的动力与褒奖! 17 | 18 | ## Update Log - 更新日志 19 | 20 | ### 0.7.0 21 | 22 | - 新增 - 夜间模式 23 | - 新增 - 程序崩溃日志收集 24 | - 新增 - 自动更新 25 | - 新增 - `html+`模式(其实就是修改了html标签==) 26 | - 修复 - 自动清理功能的错误 27 | - 修复 - 主界面刷新闪烁BUG 28 | - 修改 - 部分UI 29 | 30 | ##### 已知BUG 31 | 32 | - **位置获取**功能在 Android 6.0 上导致崩溃(一加1实测) 33 | - 部分文章显示有问题(使用`html+`模式无影响) 34 | 35 | ### v0.6.0 36 | 37 | - 新增 - FAB刷新 38 | - 新增 - html模式下的页面缓存 39 | - 新增 - json模式(速度更快,但部分文章显示有问题) 40 | - 新增 - 自动清理过期缓存 41 | - 新增 - SQLite数据储存 42 | - 重构 - 使用MVP进行重构 43 | - 新增 - 由[Mao](http://weibo.com/cat93/)提供的Logo 44 | - 修改 - 部分的UI 45 | 46 | ### v0.5.1 47 | 48 | - 抛弃 - CardView,回归朴实,更符合设计规范 49 | - 抛弃 - 第三方 DrawerLayout,那家伙华而不实 50 | - 抛弃 - WebView 顶部栏中文章标题,取而代之为分享按钮 51 | - 新增 - 搜索功能,在跳转页面显示搜索结果 52 | - 新增 - 手势右滑,返回上级页面功能(v0.5.0 中出现的 Bug 已经修复) 53 | - 优化 - 将功能扩展作为抽屉菜单的子选项 54 | - 重构 - 尽可能将代码写得优雅和规整 55 | 56 | ## Demo - 示例 57 | 58 | [快速下载](http://7xk54v.com1.z0.glb.clouddn.com/app/bb/0.7.0.apk) 59 | 60 | ## Screenshots - 预览 61 | screenshot screenshot 62 | 63 | screenshot screenshot 64 | 65 | screenshot screenshot 66 | 67 | screenshot screenshot 68 | 69 | screenshot screenshot 70 | 71 | screenshot screenshot 72 | 73 | ## Dependencies - 开源项目 74 | 75 | - [glide](https://github.com/bumptech/glide) 76 | - [butterknife](https://github.com/JakeWharton/butterknife) 77 | - [android-crop](https://github.com/jdamcd/android-crop) 78 | - [android-async-http](https://github.com/loopj/android-async-http) 79 | - [SwipeBackLayout](https://github.com/ikew0ng/SwipeBackLayout) 80 | - [Jsoup](http://http://jsoup.org/) 81 | 82 | ## Thanks - 感谢你们 83 | 84 | - 感谢 [drakeet](https://github.com/drakeet) 及他的 [妹纸&gank.io](https://github.com/drakeet/Meizhi), 其代码写得真的非常漂亮:),从中学到了很多并运用到了项目中(依葫芦画瓢而已啦) 85 | 86 | - 感谢 [Izzy Leung](https://github.com/izzyleung) 及他的 [知乎日报·净化](https://github.com/izzyleung/ZhihuDailyPurify),项目最初的原型就来自于此,感谢其提供了详细的知乎日报 API 说明 87 | 88 | ## Contributors 89 | 90 | - dev version: [troyliu0105](https://github.com/troyliu0105) 91 | - Logo: [Mao](http://weibo.com/cat93/) & [troyliu0105](https://github.com/troyliu0105) 92 | 93 | ## Contact - 联系我 94 | 95 | - Weibo:[汤奇V](http://weibo.com/qiktang) 96 | - Blog: [http://itangqi.me](http://itangqi.me) 97 | - Gmail:[imtangqi#gmail.com](mailto:imtangqi@gmail.com "欢迎与我联系") 98 | 99 | ## Project Home - 项目主页 100 | 101 | [http://itangqi.me/2015/09/03/building-blocks/](http://itangqi.me/2015/09/03/building-blocks/) 102 | 103 | ## License - 许可证 104 | 105 | 源代码在 GPL v3 协议下发布 106 | 107 | [LICENSE](/LICENSE) 108 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | # built application files 2 | *.ap_ 3 | 4 | # files for the dex VM 5 | *.dex 6 | 7 | # Java class files 8 | *.class 9 | 10 | # generated files 11 | bin/ 12 | gen/ 13 | 14 | # Local configuration file (sdk path, etc) 15 | local.properties 16 | 17 | # Proguard folder generated by Eclipse 18 | proguard/ 19 | 20 | # Ignore gradle files 21 | .gradle/ 22 | build/ 23 | 24 | # Eclipse project files 25 | .classpath 26 | .project 27 | .settings/ 28 | 29 | # Intellij project files 30 | *.iml 31 | *.ipr 32 | *.iws 33 | .idea/ 34 | 35 | # Mac system files 36 | .DS_Store 37 | 38 | *.keystore -------------------------------------------------------------------------------- /app/bbupdate.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Building Blocks 4 | 96 5 | 0.7.0 6 | http://7xk54v.com1.z0.glb.clouddn.com/app/bb/0.7.0.apk 7 | 1. 增加了夜间模式 8 | 2. 增加了程序崩溃日志收集 9 | 3. 修复了主界面刷新时闪烁的BUG 10 | 4. html+模式(其实就是修改了html标签==) 11 | 5. 修复了自动清理功能的错误 12 | 13 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion '22.0.1' 6 | 7 | useLibrary 'org.apache.http.legacy' 8 | 9 | defaultConfig { 10 | applicationId "me.itangqi.buildingblocks" 11 | minSdkVersion 16 12 | targetSdkVersion 23 13 | versionCode 100 14 | versionName "0.7.0" 15 | } 16 | buildTypes { 17 | release { 18 | minifyEnabled false 19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 20 | } 21 | } 22 | } 23 | 24 | dependencies { 25 | compile fileTree(include: ['*.jar'], dir: 'libs') 26 | // Google Play Services 27 | compile 'com.google.android.gms:play-services-location:8.1.0' 28 | // Support Libraries 29 | compile 'com.android.support:appcompat-v7:23.0.1' 30 | compile 'com.android.support:cardview-v7:23.0.1' 31 | compile 'com.android.support:recyclerview-v7:23.0.1' 32 | compile 'com.android.support:design:23.0.1' 33 | compile 'com.android.support:palette-v7:23.0.1' 34 | compile 'com.google.code.gson:gson:2.3' 35 | // The third party 36 | compile 'com.jakewharton:butterknife:7.0.1' 37 | compile 'com.soundcloud.android:android-crop:1.0.0@aar' 38 | compile 'com.loopj.android:android-async-http:1.4.8' 39 | compile 'com.github.bumptech.glide:glide:3.6.1' 40 | compile 'me.imid.swipebacklayout.lib:library:1.0.0' 41 | compile 'org.jsoup:jsoup:1.8.3' 42 | compile 'com.github.jorgecastilloprz:fabprogresscircle:1.01@aar' 43 | } 44 | -------------------------------------------------------------------------------- /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/tangqi/android-dev/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 | -------------------------------------------------------------------------------- /app/src/androidTest/java/me/itangqi/buildingblocks/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package me.itangqi.buildingblocks; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/me/itangqi/buildingblocks/IntentTest.java: -------------------------------------------------------------------------------- 1 | package me.itangqi.buildingblocks; 2 | 3 | import android.test.InstrumentationTestCase; 4 | 5 | /** 6 | * Created by Troy on 2015/10/7. 7 | */ 8 | public class IntentTest extends InstrumentationTestCase { 9 | 10 | public void testSendMessage() { 11 | 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /app/src/androidTest/java/me/itangqi/buildingblocks/OtherTest.java: -------------------------------------------------------------------------------- 1 | package me.itangqi.buildingblocks; 2 | 3 | import android.test.InstrumentationTestCase; 4 | 5 | import me.itangqi.buildingblocks.domain.utils.VersionUtils; 6 | 7 | /** 8 | * Created by Troy on 2015/10/3. 9 | */ 10 | public class OtherTest extends InstrumentationTestCase { 11 | 12 | public void testVersionCode() { 13 | int versionCode = VersionUtils.getVerisonCode(); 14 | assertEquals(83, versionCode); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/me/itangqi/buildingblocks/ParserTest.java: -------------------------------------------------------------------------------- 1 | package me.itangqi.buildingblocks; 2 | 3 | import android.test.InstrumentationTestCase; 4 | 5 | import java.util.Map; 6 | 7 | import me.itangqi.buildingblocks.domain.utils.ThemeUtils; 8 | import me.itangqi.buildingblocks.model.DailyModel; 9 | import me.itangqi.buildingblocks.presenters.MainActivityPresenter; 10 | 11 | /** 12 | * Created by Troy on 2015/10/2. 13 | */ 14 | public class ParserTest extends InstrumentationTestCase { 15 | 16 | public void testUpdate() { 17 | MainActivityPresenter presenter = new MainActivityPresenter(null); 18 | } 19 | 20 | public void testDarkHtml() { 21 | String url = "http://daily.zhihu.com/story/3892357"; 22 | DailyModel model = DailyModel.newInstance(); 23 | ThemeUtils.isLight = false; 24 | Map dark = model.parseHtml(url); 25 | ThemeUtils.isLight = true; 26 | Map light = model.parseHtml(url); 27 | assertEquals(dark.get("content").equals(light.get("content")), false); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 19 | 20 | 26 | 27 | 30 | 31 | 32 | 35 | 36 | 40 | 41 | 42 | 43 | 44 | 45 | 48 | 49 | 50 | 52 | 53 | 54 | 55 | 56 | 57 | 60 | 61 | 62 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /app/src/main/java/me/itangqi/buildingblocks/domain/api/ZhihuApi.java: -------------------------------------------------------------------------------- 1 | package me.itangqi.buildingblocks.domain.api; 2 | 3 | /* 4 | * Thanks to 5 | * https://github.com/izzyleung/ZhihuDailyPurify/wiki/知乎日报-API-分析 6 | * Author: izzyleung 7 | */ 8 | public final class ZhihuApi { 9 | 10 | public static final String ZHIHU_DAILY_NEWS = "http://news.at.zhihu.com/api/4/news/before/"; 11 | public static final String ZHIHU_DAILY_NEWS_CONTENT = "http://daily.zhihu.com/story/"; 12 | public static final String ZHIHU_DAILY_NEWS_GSON_CONTENT = "http://news-at.zhihu.com/api/4/news/"; 13 | 14 | // getDailyNews GET 15 | public static String getDailyNews(int date) { 16 | return ZHIHU_DAILY_NEWS + date; 17 | } 18 | 19 | // getDailyNewsContent GET 20 | public static String getNewsContent(int id) { 21 | return ZHIHU_DAILY_NEWS_CONTENT + id; 22 | } 23 | 24 | // getGsonNewsContent GET 25 | public static String getGsonNewsContent(int id) { 26 | return ZHIHU_DAILY_NEWS_GSON_CONTENT + id; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/me/itangqi/buildingblocks/domain/application/App.java: -------------------------------------------------------------------------------- 1 | package me.itangqi.buildingblocks.domain.application; 2 | 3 | import android.app.Activity; 4 | import android.app.Application; 5 | import android.content.Context; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | import me.itangqi.buildingblocks.domain.utils.CrashCatcher; 11 | 12 | /** 13 | * Created by tangqi on 7/20/15. 14 | */ 15 | public class App extends Application { 16 | 17 | private static Context mContext; 18 | private static List mActivities = new ArrayList<>(); 19 | 20 | @Override 21 | public void onCreate() { 22 | super.onCreate(); 23 | mContext = this; 24 | CrashCatcher crashCatcher = CrashCatcher.newInstance(); 25 | crashCatcher.setDefaultCrashCatcher(); 26 | } 27 | 28 | public static Context getContext() { 29 | return mContext; 30 | } 31 | 32 | public static void addActivity(Activity activity) { 33 | mActivities.add(activity); 34 | } 35 | 36 | public static void removeActivity(Activity activity) { 37 | mActivities.remove(activity); 38 | } 39 | 40 | public static void exitAll() { 41 | for (Activity activity : mActivities) { 42 | activity.finish(); 43 | } 44 | System.exit(0); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/me/itangqi/buildingblocks/domain/db/SQLiteHelper.java: -------------------------------------------------------------------------------- 1 | package me.itangqi.buildingblocks.domain.db; 2 | 3 | import android.database.sqlite.SQLiteDatabase; 4 | import android.database.sqlite.SQLiteOpenHelper; 5 | 6 | import me.itangqi.buildingblocks.domain.application.App; 7 | 8 | /** 9 | * Created by Troy on 2015/9/23. 10 | */ 11 | public class SQLiteHelper extends SQLiteOpenHelper { 12 | 13 | public static int VERSION = 1; 14 | 15 | public static String DATABASE_NAME = "bb.db"; 16 | 17 | public static String CREATE_DAILYGSON_TABLE = "CREATE TABLE daily(" 18 | + "id SMALLINT PRIMARY KEY NOT NULL," 19 | + "title TEXT NOT NULL," 20 | + "type SMALLINT NOT NULL," 21 | + "image_source TEXT NULL," 22 | + "image TEXT NULL," 23 | + "share_url TEXT NULL," 24 | + "ga_prefix SMALLINT NULL," 25 | + "body TEXT NULL," 26 | + "UNIQUE (id))"; 27 | 28 | public static String CREATE_DAILYRESULT_TABLE = "CREATE TABLE dailyresult(" 29 | + "date SMALLINT," 30 | + "id SMALLINT PRIMARY KEY," 31 | + "title TEXT," 32 | + "image TEXT NULL," 33 | + "type SMALLINT," 34 | + "ga_prefix SMALLINT," 35 | + "UNIQUE (id))"; 36 | 37 | public SQLiteHelper() { 38 | super(App.getContext(), DATABASE_NAME, null, VERSION); 39 | } 40 | 41 | @Override 42 | public void onCreate(SQLiteDatabase db) { 43 | db.execSQL(CREATE_DAILYGSON_TABLE); 44 | db.execSQL(CREATE_DAILYRESULT_TABLE); 45 | } 46 | 47 | @Override 48 | public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 49 | 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/java/me/itangqi/buildingblocks/domain/receiver/UpdaterReceiver.java: -------------------------------------------------------------------------------- 1 | package me.itangqi.buildingblocks.domain.receiver; 2 | 3 | import android.app.Activity; 4 | import android.app.NotificationManager; 5 | import android.content.BroadcastReceiver; 6 | import android.content.Context; 7 | import android.content.DialogInterface; 8 | import android.content.Intent; 9 | import android.os.Build; 10 | import android.support.v7.app.AlertDialog; 11 | import android.util.Log; 12 | 13 | import me.itangqi.buildingblocks.R; 14 | import me.itangqi.buildingblocks.domain.service.Updater; 15 | import me.itangqi.buildingblocks.domain.utils.IntentKeys; 16 | 17 | /** 18 | * Created by Troy on 2015/10/3. 19 | * 用来实现,在更新下载失败后的提醒 20 | */ 21 | public class UpdaterReceiver extends BroadcastReceiver { 22 | 23 | public static final String TAG = "UpdaterReceiver"; 24 | 25 | private NotificationManager mManager; 26 | // Dialog的生成需要Activity的Context (需要窗体) 27 | private Activity mActivity; 28 | 29 | public UpdaterReceiver(Activity activity) { 30 | this.mActivity = activity; 31 | } 32 | 33 | @Override 34 | public void onReceive(final Context context, final Intent intent) { 35 | final String url = intent.getStringExtra(IntentKeys.URL); 36 | Log.d(TAG, "url--->" + url); 37 | mManager = (NotificationManager) mActivity.getSystemService(Context.NOTIFICATION_SERVICE); 38 | AlertDialog.Builder builder; 39 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 40 | builder = new AlertDialog.Builder(mActivity, R.style.Base_V21_Theme_AppCompat_Light_Dialog); 41 | } else { 42 | builder = new AlertDialog.Builder(mActivity); 43 | } 44 | builder.setTitle("Error"); 45 | builder.setMessage(R.string.service_update_error); 46 | builder.setNegativeButton(R.string.service_update_cancel, new DialogInterface.OnClickListener() { 47 | @Override 48 | public void onClick(DialogInterface dialog, int which) { 49 | mManager.cancel(Updater.ID); 50 | dialog.dismiss(); 51 | } 52 | }); 53 | builder.setPositiveButton(R.string.service_update_restart, new DialogInterface.OnClickListener() { 54 | @Override 55 | public void onClick(DialogInterface dialog, int which) { 56 | Intent service = new Intent(context, Updater.class); 57 | service.putExtra(IntentKeys.URL, url); 58 | context.startService(service); 59 | } 60 | }); 61 | builder.create().show(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /app/src/main/java/me/itangqi/buildingblocks/domain/service/Updater.java: -------------------------------------------------------------------------------- 1 | package me.itangqi.buildingblocks.domain.service; 2 | 3 | import android.app.IntentService; 4 | import android.app.Notification; 5 | import android.app.NotificationManager; 6 | import android.content.Context; 7 | import android.content.Intent; 8 | import android.graphics.Bitmap; 9 | import android.graphics.BitmapFactory; 10 | import android.net.Uri; 11 | import android.support.v4.content.LocalBroadcastManager; 12 | import android.util.Log; 13 | 14 | import java.io.BufferedInputStream; 15 | import java.io.BufferedOutputStream; 16 | import java.io.File; 17 | import java.io.FileOutputStream; 18 | import java.io.IOException; 19 | import java.io.InputStream; 20 | import java.net.HttpURLConnection; 21 | import java.net.URL; 22 | import java.util.Timer; 23 | import java.util.TimerTask; 24 | 25 | import me.itangqi.buildingblocks.R; 26 | import me.itangqi.buildingblocks.domain.utils.Constants; 27 | import me.itangqi.buildingblocks.domain.utils.IntentKeys; 28 | 29 | /** 30 | * Created by Troy on 2015/10/2. 31 | * 用来实现下载功能,根据Pref的设置,是否每次在MainActivity启动时调用 32 | */ 33 | public class Updater extends IntentService { 34 | 35 | public static final String TAG = "Updater"; 36 | 37 | private String urlStr = null; 38 | private int hasDown; 39 | private int size; 40 | private boolean isError = false; 41 | 42 | private NotificationManager mNotificationManager; 43 | private Notification.Builder mNotificationBuilder; 44 | public static final int ID = 0x123; 45 | 46 | public Updater() { 47 | super("Updater"); 48 | } 49 | 50 | @Override 51 | protected void onHandleIntent(Intent intent) { 52 | mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); 53 | Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.icon); 54 | mNotificationBuilder = new Notification.Builder(this) 55 | .setContentTitle(getString(R.string.service_updating)) 56 | .setProgress(100, 0, false) 57 | .setLargeIcon(bitmap) 58 | .setWhen(System.currentTimeMillis()) 59 | .setSmallIcon(R.drawable.icon) 60 | .setVibrate(new long[]{500}) 61 | .setOngoing(true); 62 | urlStr = intent.getStringExtra(IntentKeys.URL); 63 | Log.d(TAG, "url--->" + urlStr); 64 | download(); 65 | } 66 | 67 | private void download() { 68 | String name = urlStr.substring(urlStr.lastIndexOf("/") + 1, urlStr.length()); 69 | Uri uri = null; 70 | File apk = null; 71 | InputStream is = null; 72 | BufferedInputStream bis = null; 73 | FileOutputStream fos = null; 74 | BufferedOutputStream bos = null; 75 | try { 76 | URL url = new URL(urlStr); 77 | HttpURLConnection connection = (HttpURLConnection) url.openConnection(); 78 | connection.setRequestMethod("GET"); 79 | connection.setReadTimeout(3000); 80 | connection.setConnectTimeout(3000); 81 | connection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36"); 82 | int responseCode = connection.getResponseCode(); 83 | if (responseCode == 200) { 84 | size = connection.getContentLength(); 85 | is = connection.getInputStream(); 86 | bis = new BufferedInputStream(is); 87 | File dir = new File(Constants.DOWNLOAD_DIR); 88 | if (!dir.exists()) { 89 | dir.mkdir(); 90 | } 91 | apk = new File(dir, name); 92 | fos = new FileOutputStream(apk); 93 | bos = new BufferedOutputStream(fos); 94 | byte[] buffer = new byte[1024]; 95 | int hasRead; 96 | showStatus(); 97 | while ((hasRead = bis.read(buffer)) != -1) { 98 | bos.write(buffer, 0, hasRead); 99 | hasDown += hasRead; 100 | } 101 | uri = Uri.fromFile(apk); 102 | installApk(uri); 103 | } else { 104 | Log.d(TAG, "responseCode--->" + responseCode); 105 | } 106 | } catch (IOException e) { 107 | isError = true; 108 | showError(); 109 | e.printStackTrace(); 110 | } finally { 111 | try { 112 | if (bos != null) { 113 | bos.close(); 114 | } 115 | if (fos != null) { 116 | fos.close(); 117 | } 118 | if (bis != null) { 119 | bis.close(); 120 | } 121 | if (is != null) { 122 | is.close(); 123 | } 124 | } catch (IOException e) { 125 | e.printStackTrace(); 126 | } 127 | } 128 | } 129 | 130 | private void showStatus() { 131 | TimerTask task = new TimerTask() { 132 | @Override 133 | public void run() { 134 | int status = (int) (((float) hasDown / size) * 100); 135 | Log.d(TAG, "size--->" + size + "; hasDown--->" + hasDown); 136 | Log.d(TAG, "status--->" + status); 137 | if (!isError) { 138 | mNotificationManager.notify(ID, mNotificationBuilder.setProgress(100, status, false).build()); 139 | } else { 140 | mNotificationManager.cancel(ID); 141 | cancel(); 142 | } 143 | if (status >= 100) { 144 | mNotificationManager.cancel(ID); 145 | cancel(); 146 | } 147 | } 148 | }; 149 | Timer timer = new Timer(); 150 | timer.schedule(task, 0, 100); 151 | } 152 | 153 | private void installApk(Uri uri) { 154 | Intent intent = new Intent(Intent.ACTION_VIEW); 155 | intent.setDataAndType(uri, "application/vnd.android.package-archive"); 156 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 157 | startActivity(intent); 158 | this.stopSelf(); 159 | } 160 | 161 | private void showError() { 162 | Intent intent = new Intent(Constants.BROADCAST_UPDATE_ACTION); 163 | intent.addCategory(Constants.BROADCAST_UPDATE_CATEGORY); 164 | intent.putExtra(IntentKeys.URL, urlStr); 165 | LocalBroadcastManager.getInstance(this).sendBroadcast(intent); 166 | this.stopSelf(); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /app/src/main/java/me/itangqi/buildingblocks/domain/utils/Constants.java: -------------------------------------------------------------------------------- 1 | package me.itangqi.buildingblocks.domain.utils; 2 | 3 | import android.os.Environment; 4 | 5 | import java.io.File; 6 | import java.text.SimpleDateFormat; 7 | import java.util.Locale; 8 | 9 | public final class Constants { 10 | 11 | public static final int PAGE_COUNT = 7; 12 | 13 | public static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd", Locale.US); 14 | 15 | public static final String BROADCAST_UPDATE_ACTION = "me.itangqi.buildingblocks.broadcast.update.action"; 16 | 17 | public static final String BROADCAST_UPDATE_CATEGORY = "me.itangqi.buildingblocks.broadcast.update.category"; 18 | 19 | public static final String ROOT_DIR = Environment.getExternalStorageDirectory() + File.separator + "BB"; 20 | public static final String LOG_DIR = ROOT_DIR + File.separator + "log"; 21 | public static final String DOWNLOAD_DIR = ROOT_DIR + File.separator + "download"; 22 | public static final String CACHE_DIR = ROOT_DIR + File.separator + "cache"; 23 | 24 | public static final String LOG_NAME = "crash.log"; 25 | } -------------------------------------------------------------------------------- /app/src/main/java/me/itangqi/buildingblocks/domain/utils/CrashCatcher.java: -------------------------------------------------------------------------------- 1 | package me.itangqi.buildingblocks.domain.utils; 2 | 3 | import android.content.Context; 4 | import android.net.Uri; 5 | import android.os.Looper; 6 | 7 | import java.io.File; 8 | import java.io.FileOutputStream; 9 | import java.io.IOException; 10 | import java.io.OutputStreamWriter; 11 | import java.io.PrintWriter; 12 | import java.io.StringWriter; 13 | import java.util.List; 14 | 15 | import me.itangqi.buildingblocks.R; 16 | import me.itangqi.buildingblocks.domain.application.App; 17 | 18 | /** 19 | * 进行错误收集,并由用户选择是否发送回来 20 | * Created by Troy on 2015/10/7. 21 | */ 22 | public class CrashCatcher implements Thread.UncaughtExceptionHandler { 23 | 24 | private static CrashCatcher crashCatcher; 25 | private Context mContext; 26 | 27 | private CrashCatcher() { 28 | } 29 | 30 | public static CrashCatcher newInstance() { 31 | if (crashCatcher != null) { 32 | return crashCatcher; 33 | } else { 34 | return new CrashCatcher(); 35 | } 36 | } 37 | 38 | public void setDefaultCrashCatcher() { 39 | mContext = App.getContext(); 40 | Thread.setDefaultUncaughtExceptionHandler(this); 41 | } 42 | 43 | @Override 44 | public void uncaughtException(Thread thread, Throwable ex) { 45 | ex.printStackTrace(); 46 | Uri uri = saveToSDCard(ex); 47 | PrefUtils.setCrash(true, uri.toString()); 48 | new Thread(new Runnable() { 49 | @Override 50 | public void run() { 51 | Looper.prepare(); 52 | ToastUtils.showLong(R.string.crash_toast); 53 | Looper.loop(); 54 | } 55 | }).start(); 56 | try { 57 | Thread.sleep(3000); 58 | App.exitAll(); 59 | } catch (InterruptedException e) { 60 | e.printStackTrace(); 61 | } 62 | } 63 | 64 | private String catchErrors(Throwable throwable) { 65 | StringWriter stringWriter = new StringWriter(); 66 | PrintWriter printWriter = new PrintWriter(stringWriter); 67 | throwable.printStackTrace(printWriter); 68 | printWriter.close(); 69 | return stringWriter.toString(); 70 | } 71 | 72 | private Uri saveToSDCard(Throwable throwable) { 73 | StringBuilder buffer = new StringBuilder(); 74 | List info = DeviceUtils.getDeviceMsg(mContext); 75 | for (String s : info) { 76 | buffer.append(s).append("\n"); 77 | } 78 | buffer.append("=====\tError Log\t=====\n"); 79 | String errorMsgs = catchErrors(throwable); 80 | buffer.append(errorMsgs); 81 | File dir = new File(Constants.LOG_DIR); 82 | if (!dir.exists()) { 83 | dir.mkdirs(); 84 | } 85 | File crash = new File(dir, Constants.LOG_NAME); 86 | try { 87 | FileOutputStream fos = new FileOutputStream(crash); 88 | OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF-8"); 89 | osw.write(buffer.toString()); 90 | osw.flush(); 91 | osw.close(); 92 | fos.close(); 93 | } catch (IOException e) { 94 | e.printStackTrace(); 95 | } 96 | return Uri.fromFile(crash); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /app/src/main/java/me/itangqi/buildingblocks/domain/utils/DeviceUtils.java: -------------------------------------------------------------------------------- 1 | package me.itangqi.buildingblocks.domain.utils; 2 | 3 | import android.content.Context; 4 | import android.content.pm.PackageInfo; 5 | import android.content.pm.PackageManager; 6 | import android.os.Build; 7 | 8 | import java.text.SimpleDateFormat; 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | import java.util.Locale; 12 | 13 | /** 14 | * Created by tangqi on 10/7/15. 15 | */ 16 | public class DeviceUtils { 17 | 18 | public static List getDeviceMsg(Context mContext) { 19 | List deviceInfo = new ArrayList<>(); 20 | PackageManager manager = mContext.getPackageManager(); 21 | PackageInfo info = null; 22 | try { 23 | info = manager.getPackageInfo(mContext.getPackageName(), PackageManager.GET_ACTIVITIES); 24 | } catch (PackageManager.NameNotFoundException e) { 25 | e.printStackTrace(); 26 | } 27 | if (info != null) { 28 | deviceInfo.add("=====\tDevice Info\t====="); 29 | deviceInfo.add("manufacture:" + Build.MANUFACTURER); 30 | deviceInfo.add("product:" + Build.PRODUCT); 31 | deviceInfo.add("model:" + Build.MODEL); 32 | deviceInfo.add("version.release:" + Build.VERSION.RELEASE); 33 | deviceInfo.add("version:" + Build.DISPLAY); 34 | deviceInfo.add("=====\tBB Info\t====="); 35 | deviceInfo.add("versionCode:" + info.versionCode + ""); 36 | deviceInfo.add("versionName:" + info.versionName); 37 | deviceInfo.add("lastUpdateTime:" + new SimpleDateFormat("yyyy年MM月dd日HH点mm分ss秒", Locale.CHINA).format(info.lastUpdateTime)); 38 | } 39 | return deviceInfo; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/me/itangqi/buildingblocks/domain/utils/IntentKeys.java: -------------------------------------------------------------------------------- 1 | package me.itangqi.buildingblocks.domain.utils; 2 | 3 | /** 4 | * Created by Twos on 2014/5/28. 5 | */ 6 | public class IntentKeys { 7 | 8 | public final static String DATE = "date"; 9 | public final static String URL = "url"; 10 | public final static String EXTRA_ID = "id"; 11 | public final static String EXTRA_TITLE = "title"; 12 | public static final String EXTRA_URL = "extra_url"; 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/me/itangqi/buildingblocks/domain/utils/NetworkUtils.java: -------------------------------------------------------------------------------- 1 | package me.itangqi.buildingblocks.domain.utils; 2 | 3 | import android.content.Context; 4 | import android.net.ConnectivityManager; 5 | import android.net.NetworkInfo; 6 | 7 | import me.itangqi.buildingblocks.domain.application.App; 8 | 9 | /** 10 | * Created by tangqi on 9/10/15. 11 | */ 12 | public class NetworkUtils { 13 | 14 | public static boolean isNetworkConnected() { 15 | Context context = App.getContext(); 16 | if (context != null) { 17 | ConnectivityManager mConnectivityManager = (ConnectivityManager) context 18 | .getSystemService(Context.CONNECTIVITY_SERVICE); 19 | NetworkInfo mNetworkInfo = mConnectivityManager.getActiveNetworkInfo(); 20 | if (mNetworkInfo != null) { 21 | return mNetworkInfo.isAvailable(); 22 | } 23 | } 24 | return false; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/me/itangqi/buildingblocks/domain/utils/PrefUtils.java: -------------------------------------------------------------------------------- 1 | package me.itangqi.buildingblocks.domain.utils; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | 6 | import me.itangqi.buildingblocks.domain.application.App; 7 | 8 | /** 9 | * 用来快速获取相关的设置 10 | * Created by Troy on 2015/9/20. 11 | */ 12 | public class PrefUtils { 13 | 14 | private static final String PRE_NAME = "me.itangqi.buildingblocks_preferences"; 15 | 16 | public static final String PRE_CRASH_ISLASTTIMECRASHED = "isLastTimeCrashed"; 17 | public static final String PRE_CRASH_URI = "crashUri"; 18 | 19 | public static final String PRE_CACHE_ENABLE = "enable_cache"; 20 | 21 | public static final String PRE_GSON_OR_HTML = "gson_or_html"; 22 | 23 | public static final String PRE_AUTO_UPDATE = "auto_update"; 24 | 25 | 26 | private static SharedPreferences getSharedPreferences() { 27 | return App.getContext() 28 | .getSharedPreferences(PRE_NAME, Context.MODE_PRIVATE); 29 | } 30 | 31 | public static boolean isEnableCache() { 32 | return getSharedPreferences().getBoolean(PRE_CACHE_ENABLE, false); 33 | } 34 | 35 | /** 36 | * 用来检测设置中“获取数据方式”的值 37 | * 38 | * @return 返回"http","http+"或者"gson" 39 | */ 40 | public static String wayToData() { 41 | return getSharedPreferences().getString(PRE_GSON_OR_HTML, "http"); 42 | } 43 | 44 | public static boolean isAutoUpdate() { 45 | return getSharedPreferences().getBoolean(PRE_AUTO_UPDATE, false); 46 | } 47 | 48 | public static boolean isCrashedLastTime() { 49 | return getSharedPreferences().getBoolean(PRE_CRASH_ISLASTTIMECRASHED, false); 50 | } 51 | 52 | public static String getCrashUri() { 53 | return getSharedPreferences().getString(PRE_CRASH_URI, null); 54 | } 55 | 56 | public static void setCrash(boolean isLastTimeCrashed, String crashUri) { 57 | SharedPreferences.Editor editor = getSharedPreferences().edit(); 58 | editor.putBoolean(PRE_CRASH_ISLASTTIMECRASHED, isLastTimeCrashed); 59 | editor.putString(PRE_CRASH_URI, crashUri); 60 | editor.commit(); 61 | } 62 | 63 | public static void setCrash(boolean isLastTimeCrashed) { 64 | SharedPreferences.Editor editor = getSharedPreferences().edit(); 65 | editor.putBoolean(PRE_CRASH_ISLASTTIMECRASHED, isLastTimeCrashed); 66 | editor.apply(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /app/src/main/java/me/itangqi/buildingblocks/domain/utils/ShareUtils.java: -------------------------------------------------------------------------------- 1 | package me.itangqi.buildingblocks.domain.utils; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | 6 | import me.itangqi.buildingblocks.R; 7 | 8 | /** 9 | * Created by tangqi on 9/17/15. 10 | */ 11 | public class ShareUtils { 12 | 13 | public static void share(Context context) { 14 | share(context, context.getString(R.string.about_share_text)); 15 | } 16 | 17 | public static void share(Context context, String extraText) { 18 | Intent intent = new Intent(Intent.ACTION_SEND); 19 | intent.setType("text/plain"); 20 | intent.putExtra(Intent.EXTRA_SUBJECT, context.getString(R.string.about_menu_action_share)); 21 | intent.putExtra(Intent.EXTRA_TEXT, extraText); 22 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 23 | context.startActivity( 24 | Intent.createChooser(intent, context.getString(R.string.about_menu_action_share))); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/me/itangqi/buildingblocks/domain/utils/ThemeUtils.java: -------------------------------------------------------------------------------- 1 | package me.itangqi.buildingblocks.domain.utils; 2 | 3 | import android.app.Activity; 4 | 5 | import me.itangqi.buildingblocks.R; 6 | 7 | /** 8 | * Created by tangqi on 10/5/15. 9 | */ 10 | public class ThemeUtils { 11 | 12 | public static boolean isLight = true; 13 | 14 | public static void changeTheme(Activity activity) { 15 | if (!isLight) { 16 | activity.setTheme(R.style.Base_Theme_AppTheme_Dark); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/me/itangqi/buildingblocks/domain/utils/ToastUtils.java: -------------------------------------------------------------------------------- 1 | package me.itangqi.buildingblocks.domain.utils; 2 | 3 | import android.content.Context; 4 | import android.widget.Toast; 5 | 6 | import me.itangqi.buildingblocks.domain.application.App; 7 | 8 | 9 | public class ToastUtils { 10 | 11 | private ToastUtils() { 12 | } 13 | 14 | private static void show(Context context, int resId, int duration) { 15 | Toast.makeText(context, resId, duration).show(); 16 | } 17 | 18 | private static void show(Context context, String message, int duration) { 19 | Toast.makeText(context, message, duration).show(); 20 | } 21 | 22 | public static void showShort(int resId) { 23 | Toast.makeText(App.getContext(), resId, Toast.LENGTH_SHORT).show(); 24 | } 25 | 26 | public static void showShort(String message) { 27 | Toast.makeText(App.getContext(), message, Toast.LENGTH_SHORT).show(); 28 | } 29 | 30 | public static void showLong(int resId) { 31 | Toast.makeText(App.getContext(), resId, Toast.LENGTH_LONG).show(); 32 | } 33 | 34 | public static void showLong(String message) { 35 | Toast.makeText(App.getContext(), message, Toast.LENGTH_LONG).show(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/me/itangqi/buildingblocks/domain/utils/VersionUtils.java: -------------------------------------------------------------------------------- 1 | package me.itangqi.buildingblocks.domain.utils; 2 | 3 | import android.content.Context; 4 | import android.content.pm.PackageManager; 5 | 6 | import me.itangqi.buildingblocks.domain.application.App; 7 | 8 | /** 9 | * Created by tangqi on 9/25/15. 10 | */ 11 | public class VersionUtils { 12 | 13 | public static String setUpVersionName(Context context) { 14 | String versionName = null; 15 | try { 16 | versionName = context.getApplicationContext().getPackageManager() 17 | .getPackageInfo(context.getApplicationContext().getPackageName(), 0).versionName; 18 | } catch (PackageManager.NameNotFoundException e) { 19 | e.printStackTrace(); 20 | } 21 | return versionName; 22 | } 23 | 24 | public static int getVerisonCode() { 25 | int versionCode = 0; 26 | try { 27 | versionCode = App.getContext().getPackageManager().getPackageInfo(App.getContext().getPackageName(), 0).versionCode; 28 | } catch (PackageManager.NameNotFoundException e) { 29 | e.printStackTrace(); 30 | } 31 | return versionCode; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/me/itangqi/buildingblocks/model/IDaily.java: -------------------------------------------------------------------------------- 1 | package me.itangqi.buildingblocks.model; 2 | 3 | import java.util.List; 4 | 5 | import me.itangqi.buildingblocks.model.entity.Daily; 6 | 7 | /** 8 | * Created by Troy on 2015/9/21. 9 | */ 10 | public interface IDaily { 11 | 12 | void getDailyResult(int date); 13 | 14 | /** 15 | * 保存序列化集合的方法 16 | * @param dailies 17 | * @param date 18 | */ 19 | @Deprecated 20 | void saveDailies(List dailies, int date); 21 | 22 | void getGsonNews(int id); 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/me/itangqi/buildingblocks/model/IGsonCallBack.java: -------------------------------------------------------------------------------- 1 | package me.itangqi.buildingblocks.model; 2 | 3 | import me.itangqi.buildingblocks.model.entity.DailyGson; 4 | 5 | /** 6 | * Created by Troy on 2015/9/23. 7 | */ 8 | public interface IGsonCallBack { 9 | 10 | void onGsonItemFinish(DailyGson dailyGson); 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/me/itangqi/buildingblocks/model/IHtmlCallBack.java: -------------------------------------------------------------------------------- 1 | package me.itangqi.buildingblocks.model; 2 | 3 | import java.util.Map; 4 | 5 | /** 6 | * Created by Troy on 2015/9/27. 7 | */ 8 | public interface IHtmlCallBack { 9 | 10 | void onFinish(Map map); 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/me/itangqi/buildingblocks/model/IHttpCallBack.java: -------------------------------------------------------------------------------- 1 | package me.itangqi.buildingblocks.model; 2 | 3 | import java.util.List; 4 | 5 | import me.itangqi.buildingblocks.model.entity.Daily; 6 | 7 | /** 8 | * Created by Troy on 2015/9/21. 9 | */ 10 | public interface IHttpCallBack { 11 | 12 | void onFinish(List dailyList); 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/me/itangqi/buildingblocks/model/entity/Daily.java: -------------------------------------------------------------------------------- 1 | package me.itangqi.buildingblocks.model.entity; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | import java.io.Serializable; 6 | import java.util.List; 7 | 8 | /** 9 | * Created by tangqi on 8/20/15. 10 | */ 11 | public class Daily implements Serializable { 12 | @SerializedName("images") 13 | public List images; 14 | @SerializedName("image") 15 | public String image; 16 | @SerializedName("type") 17 | public int type; 18 | @SerializedName("id") 19 | public int id; 20 | @SerializedName("ga_prefix") 21 | public int ga_prefix; 22 | @SerializedName("title") 23 | public String title; 24 | @SerializedName("multipic") 25 | public boolean multipic; 26 | @SerializedName("theme") 27 | public Theme theme; 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/me/itangqi/buildingblocks/model/entity/DailyGson.java: -------------------------------------------------------------------------------- 1 | package me.itangqi.buildingblocks.model.entity; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | /** 6 | * Created by Troy on 2015/9/23. 7 | */ 8 | public class DailyGson { 9 | @SerializedName("body") 10 | public String body; 11 | @SerializedName("image_source") 12 | public String image_source; 13 | @SerializedName("title") 14 | public String title; 15 | @SerializedName("image") 16 | public String image; 17 | @SerializedName("share_url") 18 | public String share_url; 19 | @SerializedName("ga_prefix") 20 | public int ga_prefix; 21 | @SerializedName("type") 22 | public int type; 23 | @SerializedName("id") 24 | public int id; 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/me/itangqi/buildingblocks/model/entity/DailyResult.java: -------------------------------------------------------------------------------- 1 | package me.itangqi.buildingblocks.model.entity; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | import java.io.Serializable; 6 | import java.util.List; 7 | 8 | /** 9 | * Created by tangqi on 8/20/15. 10 | */ 11 | public class DailyResult implements Serializable { 12 | @SerializedName("date") 13 | public int date; 14 | @SerializedName("stories") 15 | public List stories; 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/me/itangqi/buildingblocks/model/entity/Theme.java: -------------------------------------------------------------------------------- 1 | package me.itangqi.buildingblocks.model.entity; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | import java.io.Serializable; 6 | 7 | /** 8 | * Created by tangqi on 9/14/15. 9 | */ 10 | public class Theme implements Serializable { 11 | @SerializedName("subscribed") 12 | public boolean subscribed; 13 | @SerializedName("id") 14 | public int id; 15 | @SerializedName("name") 16 | public String name; 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/me/itangqi/buildingblocks/presenters/GsonNewsPresenter.java: -------------------------------------------------------------------------------- 1 | package me.itangqi.buildingblocks.presenters; 2 | 3 | import android.os.Bundle; 4 | import android.os.Handler; 5 | import android.os.Message; 6 | 7 | import java.util.LinkedHashMap; 8 | import java.util.Map; 9 | 10 | import me.itangqi.buildingblocks.model.DailyModel; 11 | import me.itangqi.buildingblocks.model.IGsonCallBack; 12 | import me.itangqi.buildingblocks.model.entity.DailyGson; 13 | import me.itangqi.buildingblocks.view.IGsonNews; 14 | import me.itangqi.buildingblocks.view.ui.activity.GsonViewActivity; 15 | 16 | /** 17 | * Created by Troy on 2015/9/24. 18 | */ 19 | public class GsonNewsPresenter { 20 | 21 | private IGsonNews mIGsonNews; 22 | private DailyModel mDailyModel; 23 | private Handler mHandler; 24 | private IGsonCallBack mCallBack = new IGsonCallBack() { 25 | @Override 26 | public void onGsonItemFinish(DailyGson dailyGson) { 27 | mIGsonNews.loadGson(dailyGson); 28 | } 29 | }; 30 | 31 | public GsonNewsPresenter(IGsonNews IGsonNews) { 32 | mIGsonNews = IGsonNews; 33 | mDailyModel = DailyModel.newInstance(mCallBack); 34 | mHandler = mIGsonNews.getHandler(); 35 | } 36 | 37 | public void getGsonNews(int id) { 38 | mDailyModel.getGsonNews(id); 39 | } 40 | 41 | /** 42 | * 使用AsyncTask来更新UI 43 | * 44 | * @param dailyGson 45 | * @return 46 | */ 47 | public Map> getContentMap(DailyGson dailyGson) { 48 | return mDailyModel.parseBody(dailyGson); 49 | } 50 | 51 | /** 52 | * 使用Handler来更新UI 53 | * 54 | * @param dailyGson 55 | */ 56 | public void startInflater(DailyGson dailyGson) { 57 | Map> soup = mDailyModel.parseBody(dailyGson); 58 | Thread thread = new Thread(new Task(soup)); 59 | thread.start(); 60 | } 61 | 62 | private class Task implements Runnable { 63 | 64 | Map> mSoup; 65 | 66 | public Task(Map> soup) { 67 | mSoup = soup; 68 | } 69 | 70 | @Override 71 | public void run() { 72 | LinkedHashMap extra = mSoup.get("extra"); 73 | Message extraMsg = new Message(); 74 | Bundle extraBundle = new Bundle(); 75 | LinkedHashMap article = mSoup.get("article"); 76 | for (Map.Entry entry : extra.entrySet()) { 77 | if (entry.getKey().equals("avatar")) { 78 | extraBundle.putString("avatar", entry.getValue()); 79 | } else if (entry.getKey().equals("author")) { 80 | extraBundle.putString("author", entry.getValue()); 81 | } else if (entry.getKey().equals("bio")) { 82 | extraBundle.putString("bio", entry.getValue()); 83 | } 84 | } 85 | extraMsg.what = GsonViewActivity.EXTRA; 86 | extraMsg.setData(extraBundle); 87 | mHandler.sendMessage(extraMsg); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /app/src/main/java/me/itangqi/buildingblocks/presenters/MainActivityPresenter.java: -------------------------------------------------------------------------------- 1 | package me.itangqi.buildingblocks.presenters; 2 | 3 | import android.net.Uri; 4 | import android.util.Log; 5 | 6 | import org.w3c.dom.Document; 7 | import org.w3c.dom.Element; 8 | import org.w3c.dom.NodeList; 9 | import org.xml.sax.SAXException; 10 | 11 | import java.io.BufferedReader; 12 | import java.io.BufferedWriter; 13 | import java.io.File; 14 | import java.io.FileWriter; 15 | import java.io.IOException; 16 | import java.io.InputStream; 17 | import java.io.InputStreamReader; 18 | import java.net.HttpURLConnection; 19 | import java.net.URL; 20 | import java.util.ArrayList; 21 | import java.util.Calendar; 22 | import java.util.List; 23 | 24 | import javax.xml.parsers.DocumentBuilder; 25 | import javax.xml.parsers.DocumentBuilderFactory; 26 | import javax.xml.parsers.ParserConfigurationException; 27 | 28 | import me.itangqi.buildingblocks.domain.application.App; 29 | import me.itangqi.buildingblocks.domain.utils.Constants; 30 | import me.itangqi.buildingblocks.domain.utils.PrefUtils; 31 | import me.itangqi.buildingblocks.domain.utils.VersionUtils; 32 | import me.itangqi.buildingblocks.model.DailyModel; 33 | import me.itangqi.buildingblocks.view.IMainActivity; 34 | 35 | /** 36 | * MainActivity的Presenter, 主要完成app全局的事情,清理缓存,或者更新app 37 | * 大部分方法只在app启动的时候调用一次 38 | * Created by Troy on 2015/9/26. 39 | */ 40 | public class MainActivityPresenter { 41 | 42 | public static final String TAG = "MainActivityPresenter"; 43 | 44 | private DailyModel mDailyModel; 45 | private IMainActivity mMainActivity; 46 | 47 | public MainActivityPresenter(IMainActivity iMainActivity) { 48 | this.mMainActivity = iMainActivity; 49 | this.mDailyModel = DailyModel.newInstance(); 50 | } 51 | 52 | public void clearCache() { 53 | Calendar calendar = Calendar.getInstance(); 54 | calendar.add(Calendar.DAY_OF_MONTH, -Constants.PAGE_COUNT); 55 | int before = Integer.parseInt(Constants.simpleDateFormat.format(calendar.getTime())); 56 | int totalDeleted = mDailyModel.clearOutdatedDB(before); 57 | mDailyModel.clearOutdatedPhoto(before); 58 | Log.d(TAG, "totalDeleted--->" + totalDeleted); 59 | if (totalDeleted > 0) { 60 | mMainActivity.showSnackBar("清理了" + totalDeleted + "条过期数据", 1500); 61 | } 62 | } 63 | 64 | /** 65 | * 识别服务器上的xml文件来确认是否有新版 66 | * 相应的新版url写在xml里的标签 67 | * 通过替换自己的xml文件来达到替换相应的更新源 68 | */ 69 | public void checkUpdate() { 70 | if (PrefUtils.isAutoUpdate()) { 71 | new Thread(new Runnable() { 72 | @Override 73 | public void run() { 74 | String urlStr = "https://raw.githubusercontent.com/troyliu0105/BuildingBlocks/dev/app/bbupdate.xml"; 75 | int versionCode = 0; 76 | String versionName = null; 77 | String apkUrl = null; 78 | List desc = new ArrayList<>(); 79 | try { 80 | URL url = new URL(urlStr); 81 | HttpURLConnection connection = (HttpURLConnection) url.openConnection(); 82 | connection.setConnectTimeout(3000); 83 | connection.setReadTimeout(3000); 84 | connection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36"); 85 | connection.setRequestProperty("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"); 86 | connection.setRequestProperty("Upgrade-Insecure-Requests", "1"); 87 | connection.setRequestMethod("GET"); 88 | InputStream inputStream = connection.getInputStream(); 89 | BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); 90 | File tmp = new File(App.getContext().getCacheDir(), "update.xml"); 91 | if (tmp.exists()) { 92 | boolean hasDeleted = tmp.delete(); 93 | Log.d(TAG, "旧的update.xml" + (hasDeleted ? "已被删除" : "删除失败")); 94 | } 95 | FileWriter writer = new FileWriter(tmp); 96 | BufferedWriter bufferedWriter = new BufferedWriter(writer); 97 | char[] buffer = new char[1024]; 98 | int hasRead; 99 | while ((hasRead = reader.read(buffer)) != -1) { 100 | bufferedWriter.write(buffer, 0, hasRead); 101 | bufferedWriter.newLine(); 102 | } 103 | bufferedWriter.close(); 104 | writer.close(); 105 | inputStream.close(); 106 | reader.close(); 107 | DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 108 | DocumentBuilder builder = factory.newDocumentBuilder(); 109 | Document document = builder.parse(tmp); 110 | Element bb = (Element) document.getElementsByTagName("update").item(0); 111 | versionCode = Integer.parseInt(bb.getElementsByTagName("versionCode").item(0).getFirstChild().getNodeValue()); 112 | versionName = bb.getElementsByTagName("versionName").item(0).getFirstChild().getNodeValue(); 113 | apkUrl = bb.getElementsByTagName("url").item(0).getFirstChild().getNodeValue(); 114 | NodeList descNodes = bb.getElementsByTagName("description"); 115 | for (int i = 0; i < descNodes.getLength(); i++) { 116 | desc.add(descNodes.item(i).getFirstChild().getNodeValue()); 117 | } 118 | } catch (IOException | ParserConfigurationException | SAXException e) { 119 | e.printStackTrace(); 120 | } 121 | if (versionCode > VersionUtils.getVerisonCode()) { 122 | mMainActivity.showUpdate(versionCode, versionName, apkUrl, desc); 123 | } 124 | } 125 | }).start(); 126 | } 127 | } 128 | 129 | public void handleCrashLog() { 130 | if (PrefUtils.isCrashedLastTime()) { 131 | Uri uri = Uri.parse(PrefUtils.getCrashUri()); 132 | Log.d(TAG, "crash uri--->" + uri); 133 | mMainActivity.showSnackBarWithAction("上次我好像坏掉了ಥ_ಥ", 3000, uri); 134 | PrefUtils.setCrash(false); 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /app/src/main/java/me/itangqi/buildingblocks/presenters/NewsListFragmentPresenter.java: -------------------------------------------------------------------------------- 1 | package me.itangqi.buildingblocks.presenters; 2 | 3 | import java.util.List; 4 | 5 | import me.itangqi.buildingblocks.model.DailyModel; 6 | import me.itangqi.buildingblocks.model.IHttpCallBack; 7 | import me.itangqi.buildingblocks.model.entity.Daily; 8 | import me.itangqi.buildingblocks.view.IViewPager; 9 | 10 | /** 11 | * Created by Troy on 2015/9/21. 12 | */ 13 | public class NewsListFragmentPresenter { 14 | private DailyModel mDailyModel; 15 | private IViewPager mIViewPager; 16 | private int date; 17 | 18 | public NewsListFragmentPresenter(IViewPager IViewPager, final int date) { 19 | this.mIViewPager = IViewPager; 20 | this.date = date; 21 | mDailyModel = DailyModel.newInstance(new IHttpCallBack() { 22 | @Override 23 | public void onFinish(List dailyList) { 24 | // mDailyModel.saveDailies(dailyList, date); //数据储存在Volly获取数据后立即进行,不单独调用saveDailies() 25 | loadData(dailyList); 26 | } 27 | }); 28 | } 29 | 30 | public void getNews(int date) { 31 | mDailyModel.getDailyResult(date); 32 | } 33 | 34 | public void loadData(List dailies) { 35 | mIViewPager.loadData(dailies); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/me/itangqi/buildingblocks/presenters/WebActivityPresenter.java: -------------------------------------------------------------------------------- 1 | package me.itangqi.buildingblocks.presenters; 2 | 3 | import android.util.Log; 4 | 5 | import java.io.File; 6 | import java.util.Map; 7 | 8 | import me.itangqi.buildingblocks.domain.application.App; 9 | import me.itangqi.buildingblocks.domain.utils.Constants; 10 | import me.itangqi.buildingblocks.model.DailyModel; 11 | import me.itangqi.buildingblocks.model.IHtmlCallBack; 12 | import me.itangqi.buildingblocks.view.IWebView; 13 | 14 | /** 15 | * Created by Troy on 2015/9/21. 16 | */ 17 | public class WebActivityPresenter { 18 | 19 | private IWebView mWebView; 20 | private File cacheDir; 21 | private DailyModel mDailyModel; 22 | private long mDeletedSize; 23 | private static WebActivityPresenter mPresenter; 24 | private IHtmlCallBack mIHtmlCallBack; 25 | 26 | public WebActivityPresenter(IWebView webView) { 27 | this.mWebView = webView; 28 | this.cacheDir = mWebView.getWebViewCacheDir(); 29 | mDailyModel = DailyModel.newInstance(); 30 | this.mIHtmlCallBack = new IHtmlCallBack() { 31 | @Override 32 | public void onFinish(Map map) { 33 | mWebView.loadBetterHtml(map); 34 | } 35 | }; 36 | } 37 | 38 | public WebActivityPresenter() { 39 | this.cacheDir = App.getContext().getCacheDir(); 40 | } 41 | 42 | public long clearCacheFolder() { 43 | Log.d("CachePath", "WebViewCachePath--->" + cacheDir.getAbsolutePath()); 44 | Log.d("canWrite?", "目录是否可写?--->" + (cacheDir.canWrite() ? "是" : "否")); 45 | deleteFiles(cacheDir); 46 | return mDeletedSize; 47 | } 48 | 49 | public long clearCacheFolder(int before) { 50 | File webCache = new File(cacheDir, "org.chromium.android_webview"); 51 | File[] files = webCache.listFiles(); 52 | long clearedSize = 0; 53 | if (files != null && files.length != 0) { 54 | for (File child : files) { 55 | if (Integer.parseInt(Constants.simpleDateFormat.format(child.lastModified())) <= before) { 56 | clearedSize += child.length(); 57 | //noinspection ResultOfMethodCallIgnored 58 | child.delete(); 59 | } 60 | } 61 | } 62 | return clearedSize; 63 | } 64 | 65 | private void deleteFiles(File file) { 66 | for (File child : file.listFiles()) { 67 | if (child.isDirectory()) { 68 | deleteFiles(child); 69 | } else if (child.isFile()) { 70 | Log.d("fileName", child.getName()); 71 | mDeletedSize += child.length(); 72 | boolean isDeleted = child.delete(); 73 | Log.d("isDeleted", isDeleted ? "删除成功" : "删除失败"); 74 | } 75 | } 76 | } 77 | 78 | public void getDailyGson(int id) { 79 | mDailyModel.getGsonNews(id); 80 | } 81 | 82 | public void getBetterHtml(final String htmlUrl) { 83 | new Thread() { 84 | @Override 85 | public void run() { 86 | Map map = mDailyModel.parseHtml(htmlUrl); 87 | mIHtmlCallBack.onFinish(map); 88 | } 89 | }.start(); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /app/src/main/java/me/itangqi/buildingblocks/view/IGsonNews.java: -------------------------------------------------------------------------------- 1 | package me.itangqi.buildingblocks.view; 2 | 3 | import android.os.Handler; 4 | 5 | import me.itangqi.buildingblocks.model.entity.DailyGson; 6 | 7 | /** 8 | * Created by Troy on 2015/9/24. 9 | */ 10 | public interface IGsonNews { 11 | 12 | void loadGson(DailyGson dailyGson); 13 | 14 | Handler getHandler(); 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/me/itangqi/buildingblocks/view/IMainActivity.java: -------------------------------------------------------------------------------- 1 | package me.itangqi.buildingblocks.view; 2 | 3 | import android.net.Uri; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Created by Troy on 2015/9/26. 9 | */ 10 | public interface IMainActivity { 11 | 12 | void showSnackBar(String data, int time); 13 | 14 | void showSnackBarWithAction(String data, int time, Uri uri); 15 | 16 | void showUpdate(int versionCode, String versionName, String apkUrl, List disc); 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/me/itangqi/buildingblocks/view/IViewPager.java: -------------------------------------------------------------------------------- 1 | package me.itangqi.buildingblocks.view; 2 | 3 | import java.util.List; 4 | 5 | import me.itangqi.buildingblocks.model.entity.Daily; 6 | 7 | /** 8 | * Created by Troy on 2015/9/21. 9 | */ 10 | public interface IViewPager { 11 | 12 | void loadData(List dailies); 13 | 14 | void showProgress(); 15 | 16 | void hideProgress(); 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/me/itangqi/buildingblocks/view/IWebView.java: -------------------------------------------------------------------------------- 1 | package me.itangqi.buildingblocks.view; 2 | 3 | import java.io.File; 4 | import java.util.Map; 5 | 6 | import me.itangqi.buildingblocks.model.entity.DailyGson; 7 | 8 | /** 9 | * Created by Troy on 2015/9/21. 10 | */ 11 | public interface IWebView { 12 | 13 | File getWebViewCacheDir(); 14 | 15 | void loadBetterHtml(Map htmlMap); 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/me/itangqi/buildingblocks/view/adapter/DailyListAdapter.java: -------------------------------------------------------------------------------- 1 | package me.itangqi.buildingblocks.view.adapter; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.util.Log; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.ImageView; 11 | import android.widget.TextView; 12 | 13 | import com.bumptech.glide.Glide; 14 | 15 | import java.util.List; 16 | 17 | import butterknife.Bind; 18 | import butterknife.ButterKnife; 19 | import butterknife.OnClick; 20 | import me.itangqi.buildingblocks.R; 21 | import me.itangqi.buildingblocks.domain.api.ZhihuApi; 22 | import me.itangqi.buildingblocks.domain.utils.IntentKeys; 23 | import me.itangqi.buildingblocks.domain.utils.PrefUtils; 24 | import me.itangqi.buildingblocks.model.entity.Daily; 25 | import me.itangqi.buildingblocks.view.ui.activity.GsonViewActivity; 26 | import me.itangqi.buildingblocks.view.ui.activity.WebActivity; 27 | 28 | /** 29 | * Created by tangqi on 8/20/15. 30 | */ 31 | public class DailyListAdapter extends RecyclerView.Adapter { 32 | 33 | public static final String TAG = "DailyListAdapter"; 34 | 35 | private List mNewsList; 36 | private Context mContext; 37 | private LayoutInflater mLayoutInflater; 38 | private static final int ITEM_TYPE_IMAGE = 1; 39 | private static final int ITEM_TYPE_TEXT = 2; 40 | 41 | public DailyListAdapter(Context mContext, List mNewsList) { 42 | this.mContext = mContext; 43 | this.mNewsList = mNewsList; 44 | mLayoutInflater = LayoutInflater.from(mContext); 45 | // I hate it !!! 46 | // http://stackoverflow.com/questions/28787008/onbindviewholder-position-is-starting-again-at-0 47 | // setHasStableIds(true); 48 | } 49 | 50 | @Override 51 | public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 52 | if (viewType == ITEM_TYPE_IMAGE) { 53 | return new ImageViewHolder(mLayoutInflater.inflate(R.layout.item_daily_image_info, parent, false)); 54 | } else { 55 | return new ThemeViewHolder(mLayoutInflater.inflate(R.layout.item_daily_text_info, parent, false)); 56 | } 57 | } 58 | 59 | @Override 60 | public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { 61 | Daily news = mNewsList.get(position); 62 | switch (holder.getItemViewType()) { 63 | case ITEM_TYPE_TEXT: 64 | ((ThemeViewHolder) holder).mTitle.setText(news.title); 65 | break; 66 | case ITEM_TYPE_IMAGE: 67 | ((ImageViewHolder) holder).mTitle.setText(news.title); 68 | Glide.with(mContext).load(news.image).into(((ImageViewHolder) holder).mCover); 69 | break; 70 | } 71 | } 72 | 73 | @Override 74 | public int getItemViewType(int position) { 75 | // add here your booleans or switch() to set viewType at your needed 76 | Daily news = mNewsList.get(position); 77 | // Depending on whether the presence of images to determine the type of load View 78 | return (news.image == null || (news.images != null && news.images.size() == 0)) ? ITEM_TYPE_TEXT : ITEM_TYPE_IMAGE; 79 | } 80 | 81 | @Override 82 | public int getItemCount() { 83 | return mNewsList.size(); 84 | } 85 | 86 | private void gotoWebView(Daily news, View v) { 87 | Log.d("Daily", "id--->" + news.id); 88 | String news_url = ZhihuApi.getNewsContent(news.id); 89 | Intent intent = new Intent(v.getContext(), WebActivity.class); 90 | intent.putExtra(IntentKeys.EXTRA_URL, news_url); 91 | v.getContext().startActivity(intent); 92 | } 93 | 94 | private void gotoGsonView(Daily news, View v) { 95 | Log.d("Daily", "id--->" + news.id); 96 | Intent intent = new Intent(v.getContext(), GsonViewActivity.class); 97 | intent.putExtra(IntentKeys.EXTRA_ID, news.id); 98 | intent.putExtra(IntentKeys.EXTRA_TITLE, news.title); 99 | v.getContext().startActivity(intent); 100 | } 101 | 102 | private void startView(Daily news, View v) { 103 | if (PrefUtils.wayToData().equals("gson")) { 104 | gotoGsonView(news, v); 105 | } else { 106 | gotoWebView(news, v); 107 | } 108 | } 109 | 110 | public class ImageViewHolder extends RecyclerView.ViewHolder { 111 | @Bind(R.id.iv_cover) ImageView mCover; 112 | @Bind(R.id.tv_title) TextView mTitle; 113 | 114 | public ImageViewHolder(View v) { 115 | super(v); 116 | ButterKnife.bind(this, v); 117 | } 118 | 119 | @OnClick(R.id.ll_card_parent) 120 | void onClick(View v) { 121 | // TODO do what you want :) you can use WebActivity to load 122 | Daily news = mNewsList.get(getLayoutPosition()); 123 | startView(news, v); 124 | } 125 | } 126 | 127 | public class ThemeViewHolder extends RecyclerView.ViewHolder { 128 | @Bind(R.id.tv_title) TextView mTitle; 129 | @Bind(R.id.tv_from) TextView mFrom; 130 | 131 | public ThemeViewHolder(View v) { 132 | super(v); 133 | ButterKnife.bind(this, v); 134 | } 135 | 136 | @OnClick(R.id.ll_theme_parent) 137 | void onClick(View v) { 138 | // TODO do what you want :) you can use WebActivity to load 139 | Daily news = mNewsList.get(getLayoutPosition()); 140 | startView(news, v); 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /app/src/main/java/me/itangqi/buildingblocks/view/adapter/GooglePlacesAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.itangqi.buildingblocks.view.adapter; 18 | 19 | import android.content.Context; 20 | import android.widget.ArrayAdapter; 21 | import android.widget.Filter; 22 | import android.widget.Filterable; 23 | 24 | import com.google.android.gms.common.api.GoogleApiClient; 25 | import com.google.android.gms.common.api.PendingResult; 26 | import com.google.android.gms.common.api.Status; 27 | import com.google.android.gms.location.places.AutocompleteFilter; 28 | import com.google.android.gms.location.places.AutocompletePrediction; 29 | import com.google.android.gms.location.places.AutocompletePredictionBuffer; 30 | import com.google.android.gms.location.places.Places; 31 | import com.google.android.gms.maps.model.LatLngBounds; 32 | 33 | import java.util.ArrayList; 34 | import java.util.Iterator; 35 | import java.util.concurrent.TimeUnit; 36 | 37 | import me.itangqi.buildingblocks.R; 38 | import me.itangqi.buildingblocks.domain.utils.ToastUtils; 39 | 40 | public class GooglePlacesAdapter 41 | extends ArrayAdapter implements Filterable { 42 | 43 | /** 44 | * Current results returned by this adapter. 45 | */ 46 | private ArrayList mResultList; 47 | 48 | /** 49 | * Handles autocomplete requests. 50 | */ 51 | private GoogleApiClient mGoogleApiClient; 52 | 53 | /** 54 | * The bounds used for Places Geo Data autocomplete API requests. 55 | */ 56 | private LatLngBounds mBounds; 57 | 58 | /** 59 | * The autocomplete filter used to restrict queries to a specific set of place types. 60 | */ 61 | private AutocompleteFilter mPlaceFilter; 62 | 63 | /** 64 | * Initializes with a resource for text rows and autocomplete query bounds. 65 | * 66 | * @see ArrayAdapter#ArrayAdapter(Context, int) 67 | */ 68 | public GooglePlacesAdapter(Context context, int resource, GoogleApiClient googleApiClient, 69 | LatLngBounds bounds, AutocompleteFilter filter) { 70 | super(context, resource); 71 | mGoogleApiClient = googleApiClient; 72 | mBounds = bounds; 73 | mPlaceFilter = filter; 74 | } 75 | 76 | /** 77 | * Sets the bounds for all subsequent queries. 78 | */ 79 | public void setBounds(LatLngBounds bounds) { 80 | mBounds = bounds; 81 | } 82 | 83 | /** 84 | * Returns the number of results received in the last autocomplete query. 85 | */ 86 | @Override 87 | public int getCount() { 88 | return mResultList.size(); 89 | } 90 | 91 | /** 92 | * Returns an item from the last autocomplete query. 93 | */ 94 | @Override 95 | public PlaceAutocomplete getItem(int position) { 96 | return mResultList.get(position); 97 | } 98 | 99 | /** 100 | * Returns the filter for the current set of autocomplete results. 101 | */ 102 | @Override 103 | public Filter getFilter() { 104 | Filter filter = new Filter() { 105 | @Override 106 | protected FilterResults performFiltering(CharSequence constraint) { 107 | FilterResults results = new FilterResults(); 108 | // Skip the autocomplete query if no constraints are given. 109 | if (constraint != null) { 110 | // Query the autocomplete API for the (constraint) search string. 111 | mResultList = getAutocomplete(constraint); 112 | if (mResultList != null) { 113 | // The API successfully returned results. 114 | results.values = mResultList; 115 | results.count = mResultList.size(); 116 | } 117 | } 118 | return results; 119 | } 120 | 121 | @Override 122 | protected void publishResults(CharSequence constraint, FilterResults results) { 123 | if (results != null && results.count > 0) { 124 | // The API returned at least one result, update the data. 125 | notifyDataSetChanged(); 126 | } else { 127 | // The API did not return any results, invalidate the data set. 128 | notifyDataSetInvalidated(); 129 | } 130 | } 131 | }; 132 | return filter; 133 | } 134 | 135 | private ArrayList getAutocomplete(CharSequence constraint) { 136 | if (mGoogleApiClient.isConnected()) { 137 | 138 | // Submit the query to the autocomplete API and retrieve a PendingResult that will 139 | // contain the results when the query completes. 140 | PendingResult results = 141 | Places.GeoDataApi 142 | .getAutocompletePredictions(mGoogleApiClient, constraint.toString(), 143 | mBounds, mPlaceFilter); 144 | 145 | // This method should have been called off the main UI thread. Block and wait for at most 60s 146 | // for a result from the API. 147 | AutocompletePredictionBuffer autocompletePredictions = results 148 | .await(60, TimeUnit.SECONDS); 149 | 150 | // Confirm that the query completed successfully, otherwise return null 151 | final Status status = autocompletePredictions.getStatus(); 152 | if (!status.isSuccess()) { 153 | ToastUtils.showShort(R.string.place_fuck_gfw); 154 | autocompletePredictions.release(); 155 | return null; 156 | } 157 | 158 | // Copy the results into our own data structure, because we can't hold onto the buffer. 159 | // AutocompletePrediction objects encapsulate the API response (place ID and description). 160 | Iterator iterator = autocompletePredictions.iterator(); 161 | ArrayList resultList = new ArrayList<>(autocompletePredictions.getCount()); 162 | while (iterator.hasNext()) { 163 | AutocompletePrediction prediction = iterator.next(); 164 | // Get the details of this prediction and copy it into a new PlaceAutocomplete object. 165 | resultList.add(new PlaceAutocomplete(prediction.getPlaceId(), 166 | prediction.getDescription())); 167 | } 168 | 169 | // Release the buffer now that all data has been copied. 170 | autocompletePredictions.release(); 171 | 172 | return resultList; 173 | } 174 | return null; 175 | } 176 | 177 | /** 178 | * Holder for Places Geo Data Autocomplete API results. 179 | */ 180 | public class PlaceAutocomplete { 181 | 182 | public CharSequence placeId; 183 | public CharSequence description; 184 | 185 | PlaceAutocomplete(CharSequence placeId, CharSequence description) { 186 | this.placeId = placeId; 187 | this.description = description; 188 | } 189 | 190 | @Override 191 | public String toString() { 192 | return description.toString(); 193 | } 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /app/src/main/java/me/itangqi/buildingblocks/view/ui/activity/AboutActivity.java: -------------------------------------------------------------------------------- 1 | package me.itangqi.buildingblocks.view.ui.activity; 2 | 3 | import android.os.Bundle; 4 | import android.support.design.widget.CollapsingToolbarLayout; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.support.v7.widget.Toolbar; 7 | import android.text.method.LinkMovementMethod; 8 | import android.view.Menu; 9 | import android.view.MenuItem; 10 | import android.view.View; 11 | import android.widget.TextView; 12 | 13 | import butterknife.Bind; 14 | import butterknife.ButterKnife; 15 | import me.imid.swipebacklayout.lib.SwipeBackLayout; 16 | import me.imid.swipebacklayout.lib.Utils; 17 | import me.imid.swipebacklayout.lib.app.SwipeBackActivityBase; 18 | import me.imid.swipebacklayout.lib.app.SwipeBackActivityHelper; 19 | import me.itangqi.buildingblocks.R; 20 | import me.itangqi.buildingblocks.domain.application.App; 21 | import me.itangqi.buildingblocks.domain.utils.ShareUtils; 22 | import me.itangqi.buildingblocks.domain.utils.ThemeUtils; 23 | import me.itangqi.buildingblocks.domain.utils.VersionUtils; 24 | 25 | /* 26 | * Thanks 27 | * Author: drakeet 28 | */ 29 | 30 | public class AboutActivity extends AppCompatActivity implements SwipeBackActivityBase { 31 | 32 | @Bind(R.id.toolbar) Toolbar mToolbar; 33 | @Bind(R.id.tv_version) TextView mVersionTextView; 34 | @Bind(R.id.collapsing_toolbar) CollapsingToolbarLayout mCollapsingToolbarLayout; 35 | @Bind(R.id.about_thanks_to) TextView mThanksTo; 36 | 37 | private SwipeBackActivityHelper mHelper; 38 | 39 | @Override 40 | protected void onPostCreate(Bundle savedInstanceState) { 41 | super.onPostCreate(savedInstanceState); 42 | mHelper.onPostCreate(); 43 | } 44 | 45 | @Override 46 | protected void onCreate(Bundle savedInstanceState) { 47 | ThemeUtils.changeTheme(this); 48 | super.onCreate(savedInstanceState); 49 | App.addActivity(this); 50 | setContentView(R.layout.activity_about); 51 | ButterKnife.bind(this); 52 | 53 | mVersionTextView.setText(VersionUtils.setUpVersionName(this)); 54 | 55 | mCollapsingToolbarLayout.setTitle(getString(R.string.title_about)); 56 | 57 | setSupportActionBar(mToolbar); 58 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 59 | mThanksTo.setMovementMethod(LinkMovementMethod.getInstance()); 60 | mHelper = new SwipeBackActivityHelper(this); 61 | mHelper.onActivityCreate(); 62 | } 63 | 64 | @Override 65 | public View findViewById(int id) { 66 | View v = super.findViewById(id); 67 | if (v == null && mHelper != null) 68 | return mHelper.findViewById(id); 69 | return v; 70 | } 71 | 72 | @Override 73 | public boolean onCreateOptionsMenu(Menu menu) { 74 | getMenuInflater().inflate(R.menu.menu_about, menu); 75 | return true; 76 | } 77 | 78 | @Override 79 | public boolean onOptionsItemSelected(MenuItem item) { 80 | switch (item.getItemId()) { 81 | case android.R.id.home: 82 | this.finish(); 83 | return true; 84 | case R.id.menu_share: 85 | ShareUtils.share(this); 86 | return true; 87 | } 88 | return super.onOptionsItemSelected(item); 89 | } 90 | 91 | @Override 92 | public SwipeBackLayout getSwipeBackLayout() { 93 | return mHelper.getSwipeBackLayout(); 94 | } 95 | 96 | @Override 97 | public void setSwipeBackEnable(boolean enable) { 98 | getSwipeBackLayout().setEnableGesture(enable); 99 | } 100 | 101 | @Override 102 | public void scrollToFinishActivity() { 103 | Utils.convertActivityToTranslucent(this); 104 | getSwipeBackLayout().scrollToFinishActivity(); 105 | } 106 | 107 | public void onResume() { 108 | super.onResume(); 109 | } 110 | 111 | public void onPause() { 112 | super.onPause(); 113 | } 114 | 115 | @Override 116 | protected void onDestroy() { 117 | super.onDestroy(); 118 | App.removeActivity(this); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /app/src/main/java/me/itangqi/buildingblocks/view/ui/activity/GooglePlacesActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.itangqi.buildingblocks.view.ui.activity; 18 | 19 | import android.content.res.Resources; 20 | import android.net.Uri; 21 | import android.os.Bundle; 22 | import android.text.Html; 23 | import android.text.Spanned; 24 | import android.view.View; 25 | import android.widget.AdapterView; 26 | import android.widget.AutoCompleteTextView; 27 | import android.widget.Button; 28 | import android.widget.TextView; 29 | 30 | import com.google.android.gms.common.ConnectionResult; 31 | import com.google.android.gms.common.api.GoogleApiClient; 32 | import com.google.android.gms.common.api.PendingResult; 33 | import com.google.android.gms.common.api.ResultCallback; 34 | import com.google.android.gms.location.places.Place; 35 | import com.google.android.gms.location.places.PlaceBuffer; 36 | import com.google.android.gms.location.places.PlaceLikelihoodBuffer; 37 | import com.google.android.gms.location.places.Places; 38 | import com.google.android.gms.maps.model.LatLng; 39 | import com.google.android.gms.maps.model.LatLngBounds; 40 | 41 | import butterknife.Bind; 42 | import butterknife.ButterKnife; 43 | import me.imid.swipebacklayout.lib.SwipeBackLayout; 44 | import me.itangqi.buildingblocks.R; 45 | import me.itangqi.buildingblocks.domain.application.App; 46 | import me.itangqi.buildingblocks.domain.utils.ToastUtils; 47 | import me.itangqi.buildingblocks.view.adapter.GooglePlacesAdapter; 48 | import me.itangqi.buildingblocks.view.ui.activity.base.SwipeBackActivity; 49 | 50 | public class GooglePlacesActivity extends SwipeBackActivity 51 | implements GoogleApiClient.OnConnectionFailedListener { 52 | /** 53 | * GoogleApiClient wraps our service connection to Google Play Services and provides access 54 | * to the user's sign in state as well as the Google's APIs. 55 | */ 56 | private static final int GOOGLE_API_CLIENT_ID = 0; 57 | 58 | protected GoogleApiClient mGoogleApiClient; 59 | 60 | private GooglePlacesAdapter mAdapter; 61 | 62 | private SwipeBackLayout mSwipeBackLayout; 63 | 64 | // Retrieve the AutoCompleteTextView that will display Place suggestions. 65 | @Bind(R.id.autocomplete_places) AutoCompleteTextView mAutocompleteView; 66 | // Retrieve the TextViews that will display details and attributions of the selected place. 67 | @Bind(R.id.place_details) TextView mPlaceDetailsText; 68 | @Bind(R.id.place_attribution) TextView mPlaceDetailsAttribution; 69 | // CurrentLocation 70 | @Bind(R.id.btn_current_location) Button mCurrentLocation; 71 | 72 | private static final LatLngBounds BOUNDS_GREATER_SYDNEY = new LatLngBounds( 73 | new LatLng(-34.041458, 150.790100), new LatLng(-33.682247, 151.383362)); 74 | 75 | @Override 76 | protected int getLayoutResource() { 77 | return R.layout.activity_place_auto_complete; 78 | } 79 | 80 | @Override 81 | public boolean canBack() { 82 | return true; 83 | } 84 | 85 | @Override 86 | protected void onCreate(Bundle savedInstanceState) { 87 | super.onCreate(savedInstanceState); 88 | ButterKnife.bind(this); 89 | App.addActivity(this); 90 | // Construct a GoogleApiClient for the {@link Places#GEO_DATA_API} using AutoManage 91 | // functionality, which automatically sets up the API client to handle Activity lifecycle 92 | // events. If your activity does not extend FragmentActivity, make sure to call connect() 93 | // and disconnect() explicitly. 94 | mGoogleApiClient = new GoogleApiClient.Builder(this) 95 | .enableAutoManage(this, GOOGLE_API_CLIENT_ID /* clientId */, this) 96 | .addApi(Places.GEO_DATA_API) 97 | .addApi(Places.PLACE_DETECTION_API) 98 | .build(); 99 | 100 | setTitle(getString(R.string.title_pick_place)); 101 | 102 | // Register a listener that receives callbacks when a suggestion has been selected 103 | mAutocompleteView.setOnItemClickListener(mAutocompleteClickListener); 104 | 105 | mCurrentLocation.setOnClickListener(mOnClickListener); 106 | 107 | // Set up the adapter that will retrieve suggestions from the Places Geo Data API that cover 108 | // the entire world. 109 | mAdapter = new GooglePlacesAdapter(this, android.R.layout.simple_list_item_1, 110 | mGoogleApiClient, BOUNDS_GREATER_SYDNEY, null); 111 | mAutocompleteView.setAdapter(mAdapter); 112 | 113 | mSwipeBackLayout = getSwipeBackLayout(); 114 | mSwipeBackLayout.setEdgeTrackingEnabled(SwipeBackLayout.EDGE_LEFT); 115 | } 116 | 117 | private View.OnClickListener mOnClickListener = new View.OnClickListener() { 118 | @Override 119 | public void onClick(View view) { 120 | PendingResult result = Places.PlaceDetectionApi 121 | .getCurrentPlace(mGoogleApiClient, null); 122 | result.setResultCallback(new ResultCallback() { 123 | @Override 124 | public void onResult(PlaceLikelihoodBuffer likelyPlaces) { 125 | if (!likelyPlaces.getStatus().isSuccess()) { 126 | // Request did not complete successfully 127 | ToastUtils.showShort(R.string.place_fuck_gfw); 128 | likelyPlaces.release(); 129 | return; 130 | } 131 | String placeName = String.format("%s", likelyPlaces.get(0).getPlace().getName()); 132 | String placeAttributuion = String.format("%s", likelyPlaces.get(0).getPlace().getAddress()); 133 | mPlaceDetailsText.setText(placeName); 134 | mPlaceDetailsAttribution.setText(placeAttributuion); 135 | likelyPlaces.release(); 136 | } 137 | }); 138 | } 139 | }; 140 | 141 | /** 142 | * Listener that handles selections from suggestions from the AutoCompleteTextView that 143 | * displays Place suggestions. 144 | * Gets the place id of the selected item and issues a request to the Places Geo Data API 145 | * to retrieve more details about the place. 146 | * 147 | * @see com.google.android.gms.location.places.GeoDataApi#getPlaceById(GoogleApiClient, 148 | * String...) 149 | */ 150 | private AdapterView.OnItemClickListener mAutocompleteClickListener 151 | = new AdapterView.OnItemClickListener() { 152 | @Override 153 | public void onItemClick(AdapterView parent, View view, int position, long id) { 154 | /* 155 | Retrieve the place ID of the selected item from the Adapter. 156 | The adapter stores each Place suggestion in a PlaceAutocomplete object from which we 157 | read the place ID. 158 | */ 159 | final GooglePlacesAdapter.PlaceAutocomplete item = mAdapter.getItem(position); 160 | final String placeId = String.valueOf(item.placeId); 161 | 162 | /* 163 | Issue a request to the Places Geo Data API to retrieve a Place object with additional 164 | details about the place. 165 | */ 166 | PendingResult placeResult = Places.GeoDataApi 167 | .getPlaceById(mGoogleApiClient, placeId); 168 | placeResult.setResultCallback(mUpdatePlaceDetailsCallback); 169 | } 170 | }; 171 | 172 | /** 173 | * Callback for results from a Places Geo Data API query that shows the first place result in 174 | * the details view on screen. 175 | */ 176 | private ResultCallback mUpdatePlaceDetailsCallback 177 | = new ResultCallback() { 178 | @Override 179 | public void onResult(PlaceBuffer places) { 180 | if (!places.getStatus().isSuccess()) { 181 | // Request did not complete successfully 182 | ToastUtils.showShort(R.string.place_fuck_gfw); 183 | places.release(); 184 | return; 185 | } 186 | // Get the Place object from the buffer. 187 | final Place place = places.get(0); 188 | 189 | // Format details of the place for display and show it in a TextView. 190 | mPlaceDetailsText.setText(formatPlaceDetails(getResources(), place.getName(), 191 | place.getId(), place.getAddress(), place.getPhoneNumber(), 192 | place.getWebsiteUri())); 193 | 194 | // Display the third party attributions if set. 195 | final CharSequence thirdPartyAttribution = places.getAttributions(); 196 | if (thirdPartyAttribution == null) { 197 | mPlaceDetailsAttribution.setVisibility(View.GONE); 198 | } else { 199 | mPlaceDetailsAttribution.setVisibility(View.VISIBLE); 200 | mPlaceDetailsAttribution.setText(Html.fromHtml(thirdPartyAttribution.toString())); 201 | } 202 | 203 | places.release(); 204 | } 205 | }; 206 | 207 | private static Spanned formatPlaceDetails(Resources res, CharSequence name, String id, 208 | CharSequence address, CharSequence phoneNumber, Uri websiteUri) { 209 | return Html.fromHtml(res.getString(R.string.place_details, name, id, address, phoneNumber, 210 | websiteUri)); 211 | } 212 | 213 | /** 214 | * Called when the Activity could not connect to Google Play services and the auto manager 215 | * could resolve the error automatically. 216 | * In this case the API is not available and notify the user. 217 | * 218 | * @param connectionResult can be inspected to determine the cause of the failure 219 | */ 220 | @Override 221 | public void onConnectionFailed(ConnectionResult connectionResult) { 222 | 223 | // TODO(Developer): Check error code and notify the user of error state and resolution. 224 | ToastUtils.showShort("Could not connect to Google API Client: Error " + connectionResult.getErrorCode()); 225 | GooglePlacesActivity.this.finish(); 226 | } 227 | 228 | @Override 229 | protected void onDestroy() { 230 | super.onDestroy(); 231 | App.removeActivity(this); 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /app/src/main/java/me/itangqi/buildingblocks/view/ui/activity/GsonViewActivity.java: -------------------------------------------------------------------------------- 1 | package me.itangqi.buildingblocks.view.ui.activity; 2 | 3 | import android.graphics.Color; 4 | import android.os.AsyncTask; 5 | import android.os.Bundle; 6 | import android.os.Handler; 7 | import android.support.design.widget.CollapsingToolbarLayout; 8 | import android.text.Html; 9 | import android.text.method.ScrollingMovementMethod; 10 | import android.view.Gravity; 11 | import android.view.ViewGroup; 12 | import android.webkit.WebSettings; 13 | import android.webkit.WebView; 14 | import android.widget.ImageView; 15 | import android.widget.LinearLayout; 16 | import android.widget.TextView; 17 | 18 | import com.bumptech.glide.Glide; 19 | 20 | import java.util.LinkedHashMap; 21 | import java.util.Map; 22 | 23 | import me.itangqi.buildingblocks.R; 24 | import me.itangqi.buildingblocks.domain.application.App; 25 | import me.itangqi.buildingblocks.domain.utils.IntentKeys; 26 | import me.itangqi.buildingblocks.domain.utils.PrefUtils; 27 | import me.itangqi.buildingblocks.model.entity.DailyGson; 28 | import me.itangqi.buildingblocks.presenters.GsonNewsPresenter; 29 | import me.itangqi.buildingblocks.view.IGsonNews; 30 | import me.itangqi.buildingblocks.view.ui.activity.base.SwipeBackActivity; 31 | import me.itangqi.buildingblocks.view.widget.GlidePaletteListenerImp; 32 | 33 | /** 34 | * Created by Troy on 2015/9/24. 35 | */ 36 | public class GsonViewActivity extends SwipeBackActivity implements IGsonNews { 37 | 38 | public static final int EXTRA = 0x123; 39 | 40 | private CollapsingToolbarLayout mCollapsingToolbarLayout; 41 | private LinearLayout mLinearLayout; 42 | private GsonNewsPresenter mPresenter; 43 | private ImageView mHeader; 44 | private WebView mWebView; 45 | 46 | private GlidePaletteListenerImp mPaletteListenerImp; 47 | 48 | private DailyGson mDailyGson; 49 | 50 | @Override 51 | protected int getLayoutResource() { 52 | return R.layout.activity_gson_news; 53 | } 54 | 55 | @Override 56 | protected void onCreate(Bundle savedInstanceState) { 57 | super.onCreate(savedInstanceState); 58 | App.addActivity(this); 59 | int id = getIntent().getIntExtra(IntentKeys.EXTRA_ID, 0); 60 | String title = getIntent().getStringExtra(IntentKeys.EXTRA_TITLE); 61 | mPresenter = new GsonNewsPresenter(this); 62 | initView(title); 63 | mPaletteListenerImp = new GlidePaletteListenerImp(mHeader, this, mCollapsingToolbarLayout); 64 | mPresenter.getGsonNews(id); 65 | } 66 | 67 | @Override 68 | public void loadGson(DailyGson dailyGson) { 69 | mDailyGson = dailyGson; 70 | Glide.with(App.getContext()).load(dailyGson.image).asBitmap().fitCenter().listener(mPaletteListenerImp).into(mHeader); 71 | String head = "\n" + 72 | "\n" + 73 | "\n" + 74 | "" + dailyGson.title + "\n" + 75 | "\n" + 76 | "\n" + 77 | "\n" + 78 | "\n" + 79 | ""; 80 | String bodyStart = ""; 81 | String bodyEnd = ""; 82 | mWebView.loadData(head + bodyStart + dailyGson.body.replaceAll("
", "") + bodyEnd, "text/html; charset=uft-8", "utf-8"); 83 | } 84 | 85 | @Override 86 | public Handler getHandler() { 87 | return null; 88 | } 89 | 90 | private void initView(String title) { 91 | mCollapsingToolbarLayout = (CollapsingToolbarLayout) findViewById(R.id.collapsing_toolbar_layout); 92 | mLinearLayout = (LinearLayout) findViewById(R.id.ll_content); 93 | mHeader = (ImageView) findViewById(R.id.news_header); 94 | mWebView = (WebView) findViewById(R.id.webView); 95 | mCollapsingToolbarLayout.setTitle(title); 96 | WebSettings webSettings = mWebView.getSettings(); 97 | webSettings.setJavaScriptEnabled(true); 98 | if (PrefUtils.isEnableCache()) { 99 | webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK); 100 | webSettings.setAppCacheEnabled(true); 101 | webSettings.setDatabaseEnabled(true); 102 | } 103 | webSettings.setLoadWithOverviewMode(true); 104 | webSettings.setDefaultTextEncodingName("utf-8"); 105 | } 106 | 107 | @Override 108 | protected void onDestroy() { 109 | super.onDestroy(); 110 | App.removeActivity(this); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /app/src/main/java/me/itangqi/buildingblocks/view/ui/activity/PickPhotoActivity.java: -------------------------------------------------------------------------------- 1 | package me.itangqi.buildingblocks.view.ui.activity; 2 | 3 | import android.content.Intent; 4 | import android.net.Uri; 5 | import android.os.Bundle; 6 | import android.view.Menu; 7 | import android.view.MenuItem; 8 | import android.widget.ImageView; 9 | import android.widget.Toast; 10 | 11 | import com.soundcloud.android.crop.Crop; 12 | 13 | import java.io.File; 14 | 15 | import butterknife.Bind; 16 | import butterknife.ButterKnife; 17 | import me.imid.swipebacklayout.lib.SwipeBackLayout; 18 | import me.itangqi.buildingblocks.R; 19 | import me.itangqi.buildingblocks.domain.application.App; 20 | import me.itangqi.buildingblocks.view.ui.activity.base.SwipeBackActivity; 21 | 22 | public class PickPhotoActivity extends SwipeBackActivity { 23 | 24 | @Bind(R.id.result_image) ImageView resultView; 25 | 26 | private SwipeBackLayout mSwipeBackLayout; 27 | 28 | @Override 29 | protected int getLayoutResource() { 30 | return R.layout.activity_pick_crop; 31 | } 32 | 33 | @Override 34 | public boolean canBack() { 35 | return true; 36 | } 37 | 38 | @Override 39 | protected void onCreate(Bundle savedInstanceState) { 40 | super.onCreate(savedInstanceState); 41 | App.addActivity(this); 42 | ButterKnife.bind(this); 43 | setTitle(getString(R.string.title_pick_photo)); 44 | mSwipeBackLayout = getSwipeBackLayout(); 45 | mSwipeBackLayout.setEdgeTrackingEnabled(SwipeBackLayout.EDGE_LEFT); 46 | } 47 | 48 | @Override 49 | public boolean onCreateOptionsMenu(Menu menu) { 50 | getMenuInflater().inflate(R.menu.menu_pick_photo, menu); 51 | return super.onCreateOptionsMenu(menu); 52 | } 53 | 54 | @Override 55 | public boolean onOptionsItemSelected(MenuItem item) { 56 | if (item.getItemId() == R.id.menu_action_pick_photo) { 57 | resultView.setImageDrawable(null); 58 | Crop.pickImage(this); 59 | return true; 60 | } 61 | return super.onOptionsItemSelected(item); 62 | } 63 | 64 | @Override 65 | protected void onActivityResult(int requestCode, int resultCode, Intent result) { 66 | if (requestCode == Crop.REQUEST_PICK && resultCode == RESULT_OK) { 67 | beginCrop(result.getData()); 68 | } else if (requestCode == Crop.REQUEST_CROP) { 69 | handleCrop(resultCode, result); 70 | } 71 | } 72 | 73 | private void beginCrop(Uri source) { 74 | Uri destination = Uri.fromFile(new File(getCacheDir(), "cropped")); 75 | Crop.of(source, destination).asSquare().start(this); 76 | } 77 | 78 | private void handleCrop(int resultCode, Intent result) { 79 | if (resultCode == RESULT_OK) { 80 | resultView.setImageURI(Crop.getOutput(result)); 81 | } else if (resultCode == Crop.RESULT_ERROR) { 82 | Toast.makeText(this, Crop.getError(result).getMessage(), Toast.LENGTH_SHORT).show(); 83 | } 84 | } 85 | 86 | @Override 87 | protected void onDestroy() { 88 | super.onDestroy(); 89 | App.removeActivity(this); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /app/src/main/java/me/itangqi/buildingblocks/view/ui/activity/PrefsActivity.java: -------------------------------------------------------------------------------- 1 | package me.itangqi.buildingblocks.view.ui.activity; 2 | 3 | import android.os.Bundle; 4 | 5 | import butterknife.ButterKnife; 6 | import me.imid.swipebacklayout.lib.SwipeBackLayout; 7 | import me.itangqi.buildingblocks.R; 8 | import me.itangqi.buildingblocks.domain.application.App; 9 | import me.itangqi.buildingblocks.view.ui.activity.base.SwipeBackActivity; 10 | import me.itangqi.buildingblocks.view.ui.fragment.PrefsFragment; 11 | 12 | public class PrefsActivity extends SwipeBackActivity { 13 | private SwipeBackLayout mSwipeBackLayout; 14 | 15 | @Override 16 | protected int getLayoutResource() { 17 | return R.layout.activity_prefs; 18 | } 19 | 20 | @Override 21 | public boolean canBack() { 22 | return true; 23 | } 24 | 25 | @Override 26 | protected void onCreate(Bundle savedInstanceState) { 27 | super.onCreate(savedInstanceState); 28 | App.addActivity(this); 29 | ButterKnife.bind(this); 30 | setTitle(getString(R.string.title_settings)); 31 | getFragmentManager() 32 | .beginTransaction() 33 | .replace(R.id.fragment_frame, new PrefsFragment()) 34 | .commit(); 35 | mSwipeBackLayout = getSwipeBackLayout(); 36 | mSwipeBackLayout.setEdgeTrackingEnabled(SwipeBackLayout.EDGE_LEFT); 37 | } 38 | 39 | @Override 40 | protected void onDestroy() { 41 | super.onDestroy(); 42 | App.removeActivity(this); 43 | } 44 | } -------------------------------------------------------------------------------- /app/src/main/java/me/itangqi/buildingblocks/view/ui/activity/SearchResultActivity.java: -------------------------------------------------------------------------------- 1 | package me.itangqi.buildingblocks.view.ui.activity; 2 | 3 | import android.app.SearchManager; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.view.Menu; 7 | 8 | import butterknife.ButterKnife; 9 | import me.imid.swipebacklayout.lib.SwipeBackLayout; 10 | import me.itangqi.buildingblocks.R; 11 | import me.itangqi.buildingblocks.domain.application.App; 12 | import me.itangqi.buildingblocks.view.ui.activity.base.SwipeBackActivity; 13 | 14 | public class SearchResultActivity extends SwipeBackActivity { 15 | private SwipeBackLayout mSwipeBackLayout; 16 | 17 | @Override 18 | protected int getLayoutResource() { 19 | return R.layout.activity_base; 20 | } 21 | 22 | @Override 23 | public boolean canBack() { 24 | return true; 25 | } 26 | 27 | @Override 28 | protected void onCreate(Bundle savedInstanceState) { 29 | super.onCreate(savedInstanceState); 30 | App.addActivity(this); 31 | ButterKnife.bind(this); 32 | mSwipeBackLayout = getSwipeBackLayout(); 33 | mSwipeBackLayout.setEdgeTrackingEnabled(SwipeBackLayout.EDGE_LEFT); 34 | handleIntent(getIntent()); 35 | } 36 | 37 | @Override 38 | public boolean onCreateOptionsMenu(Menu menu) { 39 | return super.onCreateOptionsMenu(menu); 40 | } 41 | 42 | @Override 43 | protected void onNewIntent(Intent intent) { 44 | handleIntent(intent); 45 | } 46 | 47 | private void handleIntent(Intent intent) { 48 | if (Intent.ACTION_SEARCH.equals(intent.getAction())) { 49 | String query = intent.getStringExtra(SearchManager.QUERY); 50 | setTitle(getString(R.string.search_result_title) + " " +query); 51 | //use the query to search 52 | } 53 | } 54 | 55 | @Override 56 | protected void onDestroy() { 57 | super.onDestroy(); 58 | App.removeActivity(this); 59 | } 60 | } -------------------------------------------------------------------------------- /app/src/main/java/me/itangqi/buildingblocks/view/ui/activity/WebActivity.java: -------------------------------------------------------------------------------- 1 | package me.itangqi.buildingblocks.view.ui.activity; 2 | 3 | import android.graphics.Bitmap; 4 | import android.os.AsyncTask; 5 | import android.os.Bundle; 6 | import android.support.design.widget.CollapsingToolbarLayout; 7 | import android.support.design.widget.FloatingActionButton; 8 | import android.support.v4.widget.NestedScrollView; 9 | import android.util.Log; 10 | import android.view.KeyEvent; 11 | import android.view.Menu; 12 | import android.view.MenuItem; 13 | import android.view.View; 14 | import android.webkit.WebChromeClient; 15 | import android.webkit.WebSettings; 16 | import android.webkit.WebView; 17 | import android.webkit.WebViewClient; 18 | import android.widget.ImageView; 19 | import android.widget.TextView; 20 | 21 | import com.bumptech.glide.Glide; 22 | import com.github.jorgecastilloprz.FABProgressCircle; 23 | import com.github.jorgecastilloprz.listeners.FABProgressListener; 24 | 25 | import java.io.File; 26 | import java.util.Map; 27 | 28 | import butterknife.Bind; 29 | import butterknife.ButterKnife; 30 | import me.imid.swipebacklayout.lib.SwipeBackLayout; 31 | import me.itangqi.buildingblocks.R; 32 | import me.itangqi.buildingblocks.domain.application.App; 33 | import me.itangqi.buildingblocks.domain.utils.IntentKeys; 34 | import me.itangqi.buildingblocks.domain.utils.PrefUtils; 35 | import me.itangqi.buildingblocks.domain.utils.ShareUtils; 36 | import me.itangqi.buildingblocks.domain.utils.ThemeUtils; 37 | import me.itangqi.buildingblocks.presenters.WebActivityPresenter; 38 | import me.itangqi.buildingblocks.view.IWebView; 39 | import me.itangqi.buildingblocks.view.ui.activity.base.SwipeBackActivity; 40 | import me.itangqi.buildingblocks.view.widget.GlidePaletteListenerImp; 41 | 42 | /* 43 | * Thanks to 44 | * Author: drakeet 45 | */ 46 | 47 | public class WebActivity extends SwipeBackActivity implements IWebView, FABProgressListener { 48 | 49 | public static final String TAG = "WebActivity"; 50 | 51 | private SwipeBackLayout mSwipeBackLayout; 52 | private WebActivityPresenter mPresenter; 53 | private GlidePaletteListenerImp mPaletteListenerImp; 54 | 55 | private String mUrl; 56 | 57 | @Bind(R.id.webView) WebView mWebView; 58 | @Bind(R.id.collapsing_toolbar_layout) CollapsingToolbarLayout mToolbarLayout; 59 | @Bind(R.id.news_header) ImageView mHeaderImg; 60 | @Bind(R.id.img_source) TextView mHeaderSource; 61 | @Bind(R.id.fabProgressCircle) FABProgressCircle fabProgressCircle; 62 | @Bind(R.id.fab) FloatingActionButton fab; 63 | @Bind(R.id.nsv_content) NestedScrollView mNestedScrollView; 64 | 65 | @Override 66 | protected int getLayoutResource() { 67 | return R.layout.activity_webview; 68 | } 69 | 70 | @Override 71 | protected void onCreate(Bundle savedInstanceState) { 72 | super.onCreate(savedInstanceState); 73 | mPresenter = new WebActivityPresenter(this); 74 | ButterKnife.bind(this); 75 | App.addActivity(this); 76 | fabProgressCircle.setVisibility(View.INVISIBLE); 77 | ThemeUtils.changeTheme(this); 78 | if (!ThemeUtils.isLight) { 79 | mNestedScrollView.setBackgroundColor(getResources().getColor(R.color.window_background_dark)); 80 | } 81 | mPaletteListenerImp = new GlidePaletteListenerImp(mHeaderImg, this, mToolbarLayout); 82 | mUrl = getIntent().getStringExtra(IntentKeys.EXTRA_URL); 83 | WebSettings webSettings = mWebView.getSettings(); 84 | webSettings.setJavaScriptEnabled(true); 85 | if (PrefUtils.isEnableCache()) { 86 | webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK); 87 | webSettings.setAppCacheEnabled(true); 88 | webSettings.setDatabaseEnabled(true); 89 | } 90 | webSettings.setLoadWithOverviewMode(true); 91 | webSettings.setDefaultTextEncodingName("utf-8"); 92 | webSettings.setRenderPriority(WebSettings.RenderPriority.HIGH); 93 | mWebView.setWebChromeClient(new ChromeClient()); 94 | mWebView.setWebViewClient(new ViewClient()); 95 | mPresenter.getBetterHtml(mUrl); 96 | mSwipeBackLayout = getSwipeBackLayout(); 97 | mSwipeBackLayout.setEdgeTrackingEnabled(SwipeBackLayout.EDGE_LEFT); 98 | fabProgressCircle.attachListener(this); 99 | } 100 | 101 | @Override 102 | public boolean onCreateOptionsMenu(Menu menu) { 103 | getMenuInflater().inflate(R.menu.menu_about, menu); 104 | return true; 105 | } 106 | 107 | @Override 108 | public boolean onOptionsItemSelected(MenuItem item) { 109 | switch (item.getItemId()) { 110 | case android.R.id.home: 111 | this.finish(); 112 | return true; 113 | case R.id.menu_share: 114 | ShareUtils.share(this); 115 | return true; 116 | } 117 | return super.onOptionsItemSelected(item); 118 | } 119 | 120 | @Override 121 | public boolean onKeyDown(int keyCode, KeyEvent event) { 122 | if (event.getAction() == KeyEvent.ACTION_DOWN) { 123 | switch (keyCode) { 124 | case KeyEvent.KEYCODE_BACK: 125 | if (mWebView.canGoBack()) { 126 | mWebView.goBack(); 127 | } else { 128 | finish(); 129 | } 130 | return true; 131 | } 132 | } 133 | return super.onKeyDown(keyCode, event); 134 | } 135 | 136 | @Override 137 | public boolean canBack() { 138 | return true; 139 | } 140 | 141 | @Override 142 | protected void onDestroy() { 143 | super.onDestroy(); 144 | if (mWebView != null) mWebView.destroy(); 145 | App.removeActivity(this); 146 | ButterKnife.unbind(this); 147 | } 148 | 149 | @Override 150 | protected void onPause() { 151 | if (mWebView != null) mWebView.onPause(); 152 | super.onPause(); 153 | } 154 | 155 | @Override 156 | protected void onResume() { 157 | super.onResume(); 158 | if (mWebView != null) mWebView.onResume(); 159 | } 160 | 161 | @Override 162 | public File getWebViewCacheDir() { 163 | return this.getCacheDir(); 164 | } 165 | 166 | @SuppressWarnings("unchecked") 167 | @Override 168 | public void loadBetterHtml(Map htmlMap) { 169 | UIAsyncTask uiAsyncTask = new UIAsyncTask(); 170 | uiAsyncTask.execute(htmlMap); 171 | } 172 | 173 | @Override 174 | public void onFABProgressAnimationEnd() { 175 | if (fabProgressCircle != null) { 176 | fabProgressCircle.setVisibility(View.GONE); 177 | } 178 | } 179 | 180 | private class UIAsyncTask extends AsyncTask, Map.Entry, Void> { 181 | 182 | @SuppressWarnings("unchecked") 183 | @Override 184 | protected Void doInBackground(Map... params) { 185 | Map map = params[0]; 186 | for (Map.Entry entry : map.entrySet()) { 187 | publishProgress(entry); 188 | } 189 | return null; 190 | } 191 | 192 | @SuppressWarnings("unchecked") 193 | @Override 194 | protected void onProgressUpdate(Map.Entry... values) { 195 | Map.Entry entry = values[0]; 196 | if (entry.getKey().equals("headline_title")) { 197 | mToolbarLayout.setTitle(entry.getValue()); 198 | } else if (entry.getKey().equals("content")) { 199 | // Log.d(TAG, entry.getValue()); 200 | mWebView.loadDataWithBaseURL(mUrl, entry.getValue(), "text/html", "uft-8", null); 201 | } else if (entry.getKey().equals("img")) { 202 | if (ThemeUtils.isLight) { 203 | Glide.with(App.getContext()).load(entry.getValue()).asBitmap().centerCrop().listener(mPaletteListenerImp).into(mHeaderImg); 204 | }else { 205 | Glide.with(App.getContext()).load(entry.getValue()).asBitmap().centerCrop().into(mHeaderImg); 206 | } 207 | } else if (entry.getKey().equals("img_source")) { 208 | mHeaderSource.setText(entry.getValue()); 209 | mHeaderSource.setVisibility(View.VISIBLE); 210 | } 211 | } 212 | } 213 | 214 | private class ChromeClient extends WebChromeClient { 215 | @Override 216 | public void onProgressChanged(WebView view, int newProgress) { 217 | super.onProgressChanged(view, newProgress); 218 | Log.d("newProgress--->", newProgress + ""); 219 | // TODO load 220 | if (newProgress == 100) { 221 | fabProgressCircle.beginFinalAnimation(); 222 | } else if (newProgress != 100) { 223 | fabProgressCircle.show(); 224 | } 225 | } 226 | 227 | @Override 228 | public void onShowCustomView(View view, CustomViewCallback callback) { 229 | super.onShowCustomView(view, callback); 230 | } 231 | } 232 | 233 | private class ViewClient extends WebViewClient { 234 | public boolean shouldOverrideUrlLoading(WebView view, String url) { 235 | if (url != null) view.loadUrl(url); 236 | return true; 237 | } 238 | 239 | @Override 240 | public void onPageStarted(WebView view, String url, Bitmap favicon) { 241 | fabProgressCircle.setVisibility(View.VISIBLE); 242 | super.onPageStarted(view, url, favicon); 243 | } 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /app/src/main/java/me/itangqi/buildingblocks/view/ui/activity/base/BaseActivity.java: -------------------------------------------------------------------------------- 1 | package me.itangqi.buildingblocks.view.ui.activity.base; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | import android.support.v7.widget.Toolbar; 6 | 7 | import butterknife.Bind; 8 | import butterknife.ButterKnife; 9 | import me.itangqi.buildingblocks.R; 10 | import me.itangqi.buildingblocks.domain.utils.ThemeUtils; 11 | 12 | public class BaseActivity extends AppCompatActivity { 13 | 14 | @Bind(R.id.toolbar) Toolbar mToolbar; 15 | protected int layoutResID = R.layout.activity_base; 16 | 17 | @Override 18 | protected void onPostCreate(Bundle savedInstanceState) { 19 | super.onPostCreate(savedInstanceState); 20 | } 21 | 22 | @Override 23 | protected void onCreate(Bundle savedInstanceState) { 24 | ThemeUtils.changeTheme(this); 25 | super.onCreate(savedInstanceState); 26 | setContentView(layoutResID); 27 | ButterKnife.bind(this); 28 | setSupportActionBar(mToolbar); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/me/itangqi/buildingblocks/view/ui/activity/base/SwipeBackActivity.java: -------------------------------------------------------------------------------- 1 | package me.itangqi.buildingblocks.view.ui.activity.base; 2 | 3 | import android.os.Build; 4 | import android.os.Bundle; 5 | import android.support.design.widget.AppBarLayout; 6 | import android.support.v7.app.ActionBar; 7 | import android.support.v7.app.AppCompatActivity; 8 | import android.support.v7.widget.Toolbar; 9 | import android.view.MenuItem; 10 | import android.view.View; 11 | 12 | import butterknife.Bind; 13 | import butterknife.ButterKnife; 14 | import me.imid.swipebacklayout.lib.SwipeBackLayout; 15 | import me.imid.swipebacklayout.lib.Utils; 16 | import me.imid.swipebacklayout.lib.app.SwipeBackActivityBase; 17 | import me.imid.swipebacklayout.lib.app.SwipeBackActivityHelper; 18 | import me.itangqi.buildingblocks.R; 19 | import me.itangqi.buildingblocks.domain.utils.ThemeUtils; 20 | 21 | /** 22 | * 手势右划返回 23 | * Created by tangqi on 9/13/15. 24 | */ 25 | public abstract class SwipeBackActivity extends AppCompatActivity implements SwipeBackActivityBase { 26 | 27 | private SwipeBackActivityHelper mHelper; 28 | 29 | abstract protected int getLayoutResource(); 30 | 31 | @Bind(R.id.app_bar_layout) AppBarLayout mAppBar; 32 | @Bind(R.id.toolbar) Toolbar mToolbar; 33 | 34 | @Override 35 | protected void onPostCreate(Bundle savedInstanceState) { 36 | super.onPostCreate(savedInstanceState); 37 | mHelper.onPostCreate(); 38 | } 39 | 40 | @Override 41 | protected void onCreate(Bundle savedInstanceState) { 42 | ThemeUtils.changeTheme(this); 43 | super.onCreate(savedInstanceState); 44 | setContentView(getLayoutResource()); 45 | ButterKnife.bind(this); 46 | 47 | initBar(); 48 | 49 | mHelper = new SwipeBackActivityHelper(this); 50 | mHelper.onActivityCreate(); 51 | } 52 | 53 | public void initBar() { 54 | if (mToolbar == null || mAppBar == null) { 55 | throw new IllegalStateException("no toolbar"); 56 | } 57 | 58 | setSupportActionBar(mToolbar); 59 | 60 | if (canBack()) { 61 | ActionBar actionBar = getSupportActionBar(); 62 | if (actionBar != null) actionBar.setDisplayHomeAsUpEnabled(true); 63 | } 64 | 65 | if (Build.VERSION.SDK_INT >= 21) { 66 | mAppBar.setElevation(10.6f); 67 | } 68 | } 69 | 70 | @Override 71 | public View findViewById(int id) { 72 | View v = super.findViewById(id); 73 | if (v == null && mHelper != null) 74 | return mHelper.findViewById(id); 75 | return v; 76 | } 77 | 78 | @Override 79 | public SwipeBackLayout getSwipeBackLayout() { 80 | return mHelper.getSwipeBackLayout(); 81 | } 82 | 83 | @Override 84 | public void setSwipeBackEnable(boolean enable) { 85 | getSwipeBackLayout().setEnableGesture(enable); 86 | } 87 | 88 | @Override 89 | public void scrollToFinishActivity() { 90 | Utils.convertActivityToTranslucent(this); 91 | getSwipeBackLayout().scrollToFinishActivity(); 92 | } 93 | 94 | @Override 95 | public boolean onOptionsItemSelected(MenuItem item) { 96 | if (item.getItemId() == android.R.id.home) { 97 | onBackPressed(); 98 | return true; 99 | } else { 100 | return super.onOptionsItemSelected(item); 101 | } 102 | } 103 | 104 | public boolean canBack() { 105 | return false; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /app/src/main/java/me/itangqi/buildingblocks/view/ui/fragment/DailyListFragment.java: -------------------------------------------------------------------------------- 1 | package me.itangqi.buildingblocks.view.ui.fragment; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.support.design.widget.Snackbar; 6 | import android.support.v4.app.Fragment; 7 | import android.support.v4.widget.SwipeRefreshLayout; 8 | import android.support.v7.widget.LinearLayoutManager; 9 | import android.support.v7.widget.RecyclerView; 10 | import android.view.LayoutInflater; 11 | import android.view.View; 12 | import android.view.ViewGroup; 13 | 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | 17 | import butterknife.Bind; 18 | import butterknife.ButterKnife; 19 | import me.itangqi.buildingblocks.R; 20 | import me.itangqi.buildingblocks.domain.utils.IntentKeys; 21 | import me.itangqi.buildingblocks.domain.utils.NetworkUtils; 22 | import me.itangqi.buildingblocks.domain.utils.PrefUtils; 23 | import me.itangqi.buildingblocks.model.entity.Daily; 24 | import me.itangqi.buildingblocks.presenters.NewsListFragmentPresenter; 25 | import me.itangqi.buildingblocks.view.IViewPager; 26 | import me.itangqi.buildingblocks.view.adapter.DailyListAdapter; 27 | import me.itangqi.buildingblocks.view.widget.RecyclerViewItemDecoration; 28 | 29 | public class DailyListFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener, IViewPager { 30 | private int date; 31 | private List mNewsList = new ArrayList<>(); 32 | private DailyListAdapter mAdapter; 33 | private RecyclerView.LayoutManager mLayoutManager; 34 | private NewsListFragmentPresenter mPresenter; 35 | 36 | @Bind(R.id.cardList) RecyclerView mRecyclerView; 37 | @Bind(R.id.swipe_refresh_layout) SwipeRefreshLayout mSwipeRefreshLayout; 38 | 39 | public static DailyListFragment newInstance() { 40 | DailyListFragment fragment = new DailyListFragment(); 41 | // TODO you can use bundle to transfer data 42 | return fragment; 43 | } 44 | 45 | @Override 46 | public void onAttach(Activity activity) { 47 | super.onAttach(activity); 48 | } 49 | 50 | @Override 51 | public void onCreate(Bundle savedInstanceState) { 52 | super.onCreate(savedInstanceState); 53 | if (savedInstanceState == null) { 54 | Bundle bundle = getArguments(); 55 | date = bundle.getInt(IntentKeys.DATE); 56 | setRetainInstance(true); 57 | } 58 | mPresenter = new NewsListFragmentPresenter(this, date); 59 | } 60 | 61 | @Override 62 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 63 | Bundle savedInstanceState) { 64 | View view = inflater.inflate(R.layout.fragment_daily_list, container, false); 65 | ButterKnife.bind(this, view); 66 | 67 | // use this setting to improve performance if you know that changes 68 | // in content do not change the layout size of the RecyclerView 69 | mRecyclerView.setHasFixedSize(true); 70 | 71 | // use a linear layout manager 72 | mLayoutManager = new LinearLayoutManager(getActivity()); 73 | mRecyclerView.setLayoutManager(mLayoutManager); 74 | mRecyclerView.addItemDecoration(new RecyclerViewItemDecoration( 75 | getActivity() 76 | )); 77 | 78 | mSwipeRefreshLayout.setOnRefreshListener(this); 79 | mSwipeRefreshLayout.setColorSchemeResources(R.color.primary); 80 | mAdapter = new DailyListAdapter(getActivity(), mNewsList); 81 | mRecyclerView.setAdapter(mAdapter); 82 | return view; 83 | } 84 | 85 | @Override 86 | public void onViewCreated(View view, Bundle savedInstanceState) { 87 | super.onViewCreated(view, savedInstanceState); 88 | onRefresh(); //Snackbar的显示依赖于当前View,所以在View创建之后刷新 89 | } 90 | 91 | @Override 92 | public void onStart() { 93 | super.onStart(); 94 | } 95 | 96 | @Override 97 | public void onResume() { 98 | super.onResume(); 99 | } 100 | 101 | @Override 102 | public void onStop() { 103 | super.onStop(); 104 | } 105 | 106 | @Override 107 | public void onPause() { 108 | super.onPause(); 109 | } 110 | 111 | @Override 112 | public void onSaveInstanceState(Bundle outState) { 113 | super.onSaveInstanceState(outState); 114 | } 115 | 116 | @Override 117 | public void onDestroyView() { 118 | super.onDestroyView(); 119 | } 120 | 121 | @Override 122 | public void setUserVisibleHint(boolean isVisibleToUser) { 123 | super.setUserVisibleHint(isVisibleToUser); 124 | } 125 | 126 | @Override 127 | public void onHiddenChanged(boolean hidden) { 128 | super.onHiddenChanged(hidden); 129 | } 130 | 131 | @Override 132 | public void onRefresh() { 133 | mPresenter.getNews(date); 134 | if (NetworkUtils.isNetworkConnected()) { 135 | showProgress(); 136 | } else if (!NetworkUtils.isNetworkConnected() && PrefUtils.isEnableCache()) { 137 | Snackbar.make(getView(), R.string.snack_network_error_load_cache, Snackbar.LENGTH_SHORT).show(); 138 | } else if (!NetworkUtils.isNetworkConnected() && !PrefUtils.isEnableCache()) { 139 | Snackbar.make(getView(), R.string.snack_network_error, Snackbar.LENGTH_SHORT).show(); 140 | } 141 | } 142 | 143 | @Override 144 | public void loadData(List dailies) { 145 | mNewsList.clear(); 146 | mNewsList.addAll(dailies); 147 | hideProgress(); 148 | mAdapter.notifyDataSetChanged(); 149 | } 150 | 151 | @Override 152 | public void showProgress() { 153 | mSwipeRefreshLayout.setRefreshing(true); 154 | } 155 | 156 | @Override 157 | public void hideProgress() { 158 | mSwipeRefreshLayout.setRefreshing(false); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /app/src/main/java/me/itangqi/buildingblocks/view/ui/fragment/PrefsFragment.java: -------------------------------------------------------------------------------- 1 | package me.itangqi.buildingblocks.view.ui.fragment; 2 | 3 | import android.content.Intent; 4 | import android.content.SharedPreferences; 5 | import android.net.Uri; 6 | import android.os.Bundle; 7 | import android.preference.CheckBoxPreference; 8 | import android.preference.Preference; 9 | import android.preference.PreferenceFragment; 10 | import android.support.design.widget.Snackbar; 11 | 12 | import java.io.File; 13 | 14 | import me.itangqi.buildingblocks.R; 15 | import me.itangqi.buildingblocks.domain.utils.Constants; 16 | import me.itangqi.buildingblocks.domain.utils.PrefUtils; 17 | import me.itangqi.buildingblocks.domain.utils.ToastUtils; 18 | import me.itangqi.buildingblocks.domain.utils.VersionUtils; 19 | import me.itangqi.buildingblocks.presenters.WebActivityPresenter; 20 | 21 | public class PrefsFragment extends PreferenceFragment implements Preference.OnPreferenceClickListener { 22 | 23 | private CheckBoxPreference mIsEnableCache; 24 | private CheckBoxPreference mIsAutoUpdate; 25 | private Preference mCachePre, mVersionPre, mLogPref; 26 | 27 | @Override 28 | public void onCreate(Bundle savedInstanceState) { 29 | super.onCreate(savedInstanceState); 30 | 31 | addPreferencesFromResource(R.xml.prefs); 32 | 33 | initPrefs(); 34 | } 35 | 36 | @Override 37 | public boolean onPreferenceClick(Preference preference) { 38 | // TODO the rest over to you :) 39 | SharedPreferences.Editor editor = preference.getEditor(); 40 | if (mIsEnableCache == preference) { 41 | editor.putBoolean(PrefUtils.PRE_CACHE_ENABLE, mIsEnableCache.isChecked()); 42 | } else if (mCachePre == preference) { 43 | WebActivityPresenter presenter = new WebActivityPresenter(); 44 | long deletedSize = presenter.clearCacheFolder(); 45 | Snackbar.make(getView(), "释放了 " + (deletedSize / 1024L / 1024L) + " MB", Snackbar.LENGTH_LONG).show(); 46 | } else if (mIsAutoUpdate == preference) { 47 | editor.putBoolean(PrefUtils.PRE_AUTO_UPDATE, mIsAutoUpdate.isChecked()); 48 | } else if (mLogPref == preference) { 49 | String logUri = PrefUtils.getCrashUri(); 50 | if (logUri != null && isCrashLogExit()) { 51 | Uri uri = Uri.parse(logUri); 52 | Intent sendTo = new Intent(Intent.ACTION_SEND); 53 | String[] developers = new String[]{"imtangqi@gmail.com", "troyliu0105@outlook.com"}; 54 | sendTo.putExtra(Intent.EXTRA_EMAIL, developers); 55 | sendTo.putExtra(Intent.EXTRA_SUBJECT, "BuildingBlocks崩溃日志"); 56 | sendTo.putExtra(Intent.EXTRA_TEXT, "欢迎吐槽:\n"); 57 | sendTo.putExtra(Intent.EXTRA_STREAM, uri); 58 | sendTo.setType("text/plain"); 59 | startActivity(Intent.createChooser(sendTo, "请选择邮件客户端")); 60 | } else { 61 | ToastUtils.showShort("我现在好着呢~"); 62 | } 63 | } 64 | return true; 65 | } 66 | 67 | private void initPrefs() { 68 | mIsEnableCache = (CheckBoxPreference) findPreference("enable_cache"); 69 | mIsAutoUpdate = (CheckBoxPreference) findPreference("auto_update"); 70 | mCachePre = findPreference("delete_cache"); 71 | mCachePre.setOnPreferenceClickListener(this); 72 | mLogPref = findPreference("send_log"); 73 | mLogPref.setOnPreferenceClickListener(this); 74 | mVersionPre = findPreference("version"); 75 | mVersionPre.setTitle("版本:" + VersionUtils.setUpVersionName(getActivity())); 76 | } 77 | 78 | public static boolean isCrashLogExit() { 79 | try { 80 | File crash = new File(Constants.LOG_DIR + File.separator + Constants.LOG_NAME); 81 | if (!crash.exists()) { 82 | return false; 83 | } 84 | } catch (Exception e) { 85 | // TODO: handle exception 86 | } 87 | return true; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /app/src/main/java/me/itangqi/buildingblocks/view/widget/CircleImageView.java: -------------------------------------------------------------------------------- 1 | package me.itangqi.buildingblocks.view.widget; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Bitmap; 6 | import android.graphics.BitmapShader; 7 | import android.graphics.Canvas; 8 | import android.graphics.Color; 9 | import android.graphics.Matrix; 10 | import android.graphics.Paint; 11 | import android.graphics.RectF; 12 | import android.graphics.Shader; 13 | import android.graphics.drawable.BitmapDrawable; 14 | import android.graphics.drawable.ColorDrawable; 15 | import android.graphics.drawable.Drawable; 16 | import android.util.AttributeSet; 17 | import android.widget.ImageView; 18 | 19 | import me.itangqi.buildingblocks.R; 20 | 21 | public class CircleImageView extends ImageView { 22 | 23 | private static final ScaleType SCALE_TYPE = ScaleType.CENTER_CROP; 24 | 25 | private static final Bitmap.Config BITMAP_CONFIG = Bitmap.Config.ARGB_8888; 26 | private static final int COLORDRAWABLE_DIMENSION = 1; 27 | 28 | private static final int DEFAULT_BORDER_WIDTH = 0; 29 | private static final int DEFAULT_BORDER_COLOR = Color.BLACK; 30 | 31 | private final RectF mDrawableRect = new RectF(); 32 | private final RectF mBorderRect = new RectF(); 33 | 34 | private final Matrix mShaderMatrix = new Matrix(); 35 | private final Paint mBitmapPaint = new Paint(); 36 | private final Paint mBorderPaint = new Paint(); 37 | 38 | private int mBorderColor = DEFAULT_BORDER_COLOR; 39 | private int mBorderWidth = DEFAULT_BORDER_WIDTH; 40 | 41 | private Bitmap mBitmap; 42 | private BitmapShader mBitmapShader; 43 | private int mBitmapWidth; 44 | private int mBitmapHeight; 45 | 46 | private float mDrawableRadius; 47 | private float mBorderRadius; 48 | 49 | private boolean mReady; 50 | private boolean mSetupPending; 51 | 52 | public CircleImageView(Context context) { 53 | super(context); 54 | } 55 | 56 | public CircleImageView(Context context, AttributeSet attrs) { 57 | this(context, attrs, 0); 58 | } 59 | 60 | public CircleImageView(Context context, AttributeSet attrs, int defStyle) { 61 | super(context, attrs, defStyle); 62 | super.setScaleType(SCALE_TYPE); 63 | 64 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView, defStyle, 0); 65 | 66 | mBorderWidth = a.getDimensionPixelSize(R.styleable.CircleImageView_border_width, DEFAULT_BORDER_WIDTH); 67 | mBorderColor = a.getColor(R.styleable.CircleImageView_border_color, DEFAULT_BORDER_COLOR); 68 | 69 | a.recycle(); 70 | 71 | mReady = true; 72 | 73 | if (mSetupPending) { 74 | setup(); 75 | mSetupPending = false; 76 | } 77 | } 78 | 79 | @Override 80 | public ScaleType getScaleType() { 81 | return SCALE_TYPE; 82 | } 83 | 84 | @Override 85 | public void setScaleType(ScaleType scaleType) { 86 | if (scaleType != SCALE_TYPE) { 87 | throw new IllegalArgumentException(String.format("ScaleType %s not supported.", scaleType)); 88 | } 89 | } 90 | 91 | @Override 92 | protected void onDraw(Canvas canvas) { 93 | if (getDrawable() == null) { 94 | return; 95 | } 96 | 97 | canvas.drawCircle(getWidth() / 2, getHeight() / 2, mDrawableRadius, mBitmapPaint); 98 | if (mBorderWidth != 0) { 99 | canvas.drawCircle(getWidth() / 2, getHeight() / 2, mBorderRadius, mBorderPaint); 100 | } 101 | } 102 | 103 | @Override 104 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 105 | super.onSizeChanged(w, h, oldw, oldh); 106 | setup(); 107 | } 108 | 109 | public int getBorderColor() { 110 | return mBorderColor; 111 | } 112 | 113 | public void setBorderColor(int borderColor) { 114 | if (borderColor == mBorderColor) { 115 | return; 116 | } 117 | 118 | mBorderColor = borderColor; 119 | mBorderPaint.setColor(mBorderColor); 120 | invalidate(); 121 | } 122 | 123 | public int getBorderWidth() { 124 | return mBorderWidth; 125 | } 126 | 127 | public void setBorderWidth(int borderWidth) { 128 | if (borderWidth == mBorderWidth) { 129 | return; 130 | } 131 | 132 | mBorderWidth = borderWidth; 133 | setup(); 134 | } 135 | 136 | @Override 137 | public void setImageBitmap(Bitmap bm) { 138 | super.setImageBitmap(bm); 139 | mBitmap = bm; 140 | setup(); 141 | } 142 | 143 | @Override 144 | public void setImageDrawable(Drawable drawable) { 145 | super.setImageDrawable(drawable); 146 | mBitmap = getBitmapFromDrawable(drawable); 147 | setup(); 148 | } 149 | 150 | @Override 151 | public void setImageResource(int resId) { 152 | super.setImageResource(resId); 153 | mBitmap = getBitmapFromDrawable(getDrawable()); 154 | setup(); 155 | } 156 | 157 | private Bitmap getBitmapFromDrawable(Drawable drawable) { 158 | if (drawable == null) { 159 | return null; 160 | } 161 | 162 | if (drawable instanceof BitmapDrawable) { 163 | return ((BitmapDrawable) drawable).getBitmap(); 164 | } 165 | 166 | try { 167 | Bitmap bitmap; 168 | 169 | if (drawable instanceof ColorDrawable) { 170 | bitmap = Bitmap.createBitmap(COLORDRAWABLE_DIMENSION, COLORDRAWABLE_DIMENSION, BITMAP_CONFIG); 171 | } else { 172 | bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), BITMAP_CONFIG); 173 | } 174 | 175 | Canvas canvas = new Canvas(bitmap); 176 | drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); 177 | drawable.draw(canvas); 178 | return bitmap; 179 | } catch (OutOfMemoryError e) { 180 | return null; 181 | } 182 | } 183 | 184 | private void setup() { 185 | if (!mReady) { 186 | mSetupPending = true; 187 | return; 188 | } 189 | 190 | if (mBitmap == null) { 191 | return; 192 | } 193 | 194 | mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); 195 | 196 | mBitmapPaint.setAntiAlias(true); 197 | mBitmapPaint.setShader(mBitmapShader); 198 | 199 | mBorderPaint.setStyle(Paint.Style.STROKE); 200 | mBorderPaint.setAntiAlias(true); 201 | mBorderPaint.setColor(mBorderColor); 202 | mBorderPaint.setStrokeWidth(mBorderWidth); 203 | 204 | mBitmapHeight = mBitmap.getHeight(); 205 | mBitmapWidth = mBitmap.getWidth(); 206 | 207 | mBorderRect.set(0, 0, getWidth(), getHeight()); 208 | mBorderRadius = Math.min((mBorderRect.height() - mBorderWidth) / 2, (mBorderRect.width() - mBorderWidth) / 2); 209 | 210 | mDrawableRect.set(mBorderWidth, mBorderWidth, mBorderRect.width() - mBorderWidth, mBorderRect.height() - mBorderWidth); 211 | mDrawableRadius = Math.min(mDrawableRect.height() / 2, mDrawableRect.width() / 2); 212 | 213 | updateShaderMatrix(); 214 | invalidate(); 215 | } 216 | 217 | private void updateShaderMatrix() { 218 | float scale; 219 | float dx = 0; 220 | float dy = 0; 221 | 222 | mShaderMatrix.set(null); 223 | 224 | if (mBitmapWidth * mDrawableRect.height() > mDrawableRect.width() * mBitmapHeight) { 225 | scale = mDrawableRect.height() / (float) mBitmapHeight; 226 | dx = (mDrawableRect.width() - mBitmapWidth * scale) * 0.5f; 227 | } else { 228 | scale = mDrawableRect.width() / (float) mBitmapWidth; 229 | dy = (mDrawableRect.height() - mBitmapHeight * scale) * 0.5f; 230 | } 231 | 232 | mShaderMatrix.setScale(scale, scale); 233 | mShaderMatrix.postTranslate((int) (dx + 0.5f) + mBorderWidth, (int) (dy + 0.5f) + mBorderWidth); 234 | 235 | mBitmapShader.setLocalMatrix(mShaderMatrix); 236 | } 237 | } -------------------------------------------------------------------------------- /app/src/main/java/me/itangqi/buildingblocks/view/widget/FABCircleProgressBehavior.java: -------------------------------------------------------------------------------- 1 | package me.itangqi.buildingblocks.view.widget; 2 | 3 | import android.annotation.TargetApi; 4 | import android.content.Context; 5 | import android.os.Build; 6 | import android.support.design.widget.CoordinatorLayout; 7 | import android.support.v4.view.ViewCompat; 8 | import android.support.v4.view.ViewPropertyAnimatorListener; 9 | import android.support.v4.view.animation.FastOutSlowInInterpolator; 10 | import android.util.AttributeSet; 11 | import android.view.View; 12 | import android.view.animation.Animation; 13 | import android.view.animation.AnimationUtils; 14 | import android.view.animation.Interpolator; 15 | 16 | import com.github.jorgecastilloprz.FABProgressCircle; 17 | 18 | /** 19 | * 方法来自 [issue10](https://github.com/JorgeCastilloPrz/FABProgressCircle/issues/10) 20 | * Created by Troy on 2015/10/4. 21 | */ 22 | public class FABCircleProgressBehavior extends CoordinatorLayout.Behavior { 23 | 24 | private static final Interpolator INTERPOLATOR = new FastOutSlowInInterpolator(); 25 | private boolean mIsAnimatingOut = false; 26 | 27 | public FABCircleProgressBehavior(Context context, AttributeSet attrs) { 28 | super(context, attrs); 29 | } 30 | 31 | @Override 32 | public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) { 33 | return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL 34 | || super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes); 35 | } 36 | 37 | @Override 38 | public void onNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) { 39 | super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed); 40 | if (dyConsumed > 0 && !this.mIsAnimatingOut && child.getVisibility() == View.VISIBLE) { 41 | animateOut(child); 42 | } 43 | // else if (dyConsumed < CommonUtils.getToolbarHeight(App.getContext()) && child.getVisibility() != View.VISIBLE) { 44 | // animateIn(child); 45 | // } 46 | } 47 | 48 | // Same animation that FloatingActionButton.Behavior uses to hide the FAB when the AppBarLayout exits 49 | private void animateOut(final View button) { 50 | if (((FABProgressCircle) button).getVisibility() == View.VISIBLE) { 51 | if (Build.VERSION.SDK_INT >= 14) { 52 | ViewCompat.animate(button).scaleX(0.0F).scaleY(0.0F).alpha(0.0F).setInterpolator(INTERPOLATOR).withLayer() 53 | .setListener(new ViewPropertyAnimatorListener() { 54 | public void onAnimationStart(View view) { 55 | FABCircleProgressBehavior.this.mIsAnimatingOut = true; 56 | } 57 | 58 | public void onAnimationCancel(View view) { 59 | FABCircleProgressBehavior.this.mIsAnimatingOut = false; 60 | } 61 | 62 | public void onAnimationEnd(View view) { 63 | FABCircleProgressBehavior.this.mIsAnimatingOut = false; 64 | view.setVisibility(View.GONE); 65 | } 66 | }).start(); 67 | } else { 68 | Animation anim = AnimationUtils.loadAnimation(button.getContext(), android.R.anim.fade_out); 69 | anim.setInterpolator(INTERPOLATOR); 70 | anim.setDuration(200L); 71 | anim.setAnimationListener(new Animation.AnimationListener() { 72 | public void onAnimationStart(Animation animation) { 73 | FABCircleProgressBehavior.this.mIsAnimatingOut = true; 74 | } 75 | 76 | public void onAnimationEnd(Animation animation) { 77 | FABCircleProgressBehavior.this.mIsAnimatingOut = false; 78 | button.setVisibility(View.GONE); 79 | } 80 | 81 | @Override 82 | public void onAnimationRepeat(final Animation animation) { 83 | } 84 | }); 85 | button.startAnimation(anim); 86 | } 87 | } 88 | } 89 | 90 | // Same animation that FloatingActionButton.Behavior uses to show the FAB when the AppBarLayout enters 91 | @TargetApi(Build.VERSION_CODES.KITKAT) 92 | private void animateIn(View button) { 93 | if (((FABProgressCircle) button).getVisibility() == View.INVISIBLE) { 94 | if (Build.VERSION.SDK_INT >= 14) { 95 | ViewCompat.animate(button).scaleX(1.0F).scaleY(1.0F).alpha(1.0F) 96 | .setInterpolator(INTERPOLATOR).withLayer().setListener(null) 97 | .start(); 98 | } else { 99 | Animation anim = AnimationUtils.loadAnimation(button.getContext(), android.R.anim.fade_in); 100 | anim.setDuration(200L); 101 | anim.setInterpolator(INTERPOLATOR); 102 | button.startAnimation(anim); 103 | } 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /app/src/main/java/me/itangqi/buildingblocks/view/widget/GlidePaletteListenerImp.java: -------------------------------------------------------------------------------- 1 | package me.itangqi.buildingblocks.view.widget; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.graphics.Bitmap; 6 | import android.graphics.Color; 7 | import android.os.Build; 8 | import android.support.design.widget.CollapsingToolbarLayout; 9 | import android.support.v7.graphics.Palette; 10 | import android.util.Log; 11 | import android.view.Window; 12 | import android.view.WindowManager; 13 | import android.widget.ImageView; 14 | 15 | import com.bumptech.glide.request.RequestListener; 16 | import com.bumptech.glide.request.target.Target; 17 | 18 | import me.itangqi.buildingblocks.domain.application.App; 19 | 20 | /** 21 | * Created by Troy on 2015/10/3. 22 | */ 23 | public class GlidePaletteListenerImp implements RequestListener { 24 | 25 | public static final String TAG = "GlidePaletteListener"; 26 | 27 | private ImageView mImageView; 28 | private Activity mActivity; 29 | private CollapsingToolbarLayout mToolbarLayout; 30 | 31 | public GlidePaletteListenerImp(ImageView imageView, Activity activity, CollapsingToolbarLayout toolbarLayout) { 32 | mImageView = imageView; 33 | mActivity = activity; 34 | mToolbarLayout = toolbarLayout; 35 | } 36 | 37 | @Override 38 | public boolean onException(Exception e, String model, Target target, boolean isFirstResource) { 39 | Log.d(TAG, "onException--->Target:isFirstResource->" + isFirstResource); 40 | return false; 41 | } 42 | 43 | @Override 44 | public boolean onResourceReady(Bitmap resource, String model, Target target, boolean isFromMemoryCache, boolean isFirstResource) { 45 | Palette.Builder builder = Palette.from(resource); 46 | builder.generate(new Palette.PaletteAsyncListener() { 47 | @Override 48 | public void onGenerated(Palette palette) { 49 | Palette.Swatch swatch = palette.getVibrantSwatch(); 50 | if (swatch != null) { 51 | mToolbarLayout.setBackgroundColor(swatch.getRgb()); 52 | mToolbarLayout.setContentScrimColor(swatch.getRgb()); 53 | mToolbarLayout.setCollapsedTitleTextColor(swatch.getTitleTextColor()); 54 | mToolbarLayout.setStatusBarScrimColor(deeper(swatch.getRgb())); 55 | if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) { 56 | Window window = mActivity.getWindow(); 57 | window.setStatusBarColor(deeper(swatch.getRgb())); 58 | window.setNavigationBarColor(deeper(swatch.getRgb())); 59 | } 60 | } 61 | 62 | } 63 | }); 64 | return false; 65 | } 66 | 67 | private int deeper(int rgb) { 68 | int alpha = rgb >> 24; 69 | int red = rgb >> 16 & 0xFF; 70 | int green = rgb >> 8 & 0xFF; 71 | int blue = rgb & 0xFF; 72 | red = (int) Math.floor(red * (0.9)); 73 | green = (int) Math.floor(green * (0.9)); 74 | blue = (int) Math.floor(blue * (0.9)); 75 | return Color.argb(alpha, red, green, blue); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /app/src/main/java/me/itangqi/buildingblocks/view/widget/RecyclerViewItemDecoration.java: -------------------------------------------------------------------------------- 1 | package me.itangqi.buildingblocks.view.widget; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.drawable.Drawable; 6 | import android.support.v7.widget.RecyclerView; 7 | import android.view.View; 8 | 9 | import me.itangqi.buildingblocks.R; 10 | import me.itangqi.buildingblocks.domain.utils.ThemeUtils; 11 | 12 | /** 13 | * Simple RecyclerView Divider 14 | * Created by tangqi on 9/16/15. 15 | */ 16 | public class RecyclerViewItemDecoration extends RecyclerView.ItemDecoration { 17 | private Drawable mDivider; 18 | 19 | public RecyclerViewItemDecoration(Context context) { 20 | if (!ThemeUtils.isLight) 21 | mDivider = context.getResources().getDrawable(R.drawable.item_divider_white); 22 | else mDivider = context.getResources().getDrawable(R.drawable.item_divider_black); 23 | } 24 | 25 | @Override 26 | public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { 27 | int left = parent.getPaddingLeft(); 28 | int right = parent.getWidth() - parent.getPaddingRight(); 29 | 30 | int childCount = parent.getChildCount(); 31 | for (int i = 0; i < childCount; i++) { 32 | View child = parent.getChildAt(i); 33 | 34 | RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams(); 35 | 36 | int top = child.getBottom() + params.bottomMargin; 37 | int bottom = top + mDivider.getIntrinsicHeight(); 38 | 39 | mDivider.setBounds(left, top, right, bottom); 40 | mDivider.draw(c); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/me/itangqi/buildingblocks/view/widget/ScrollingFABBehavior.java: -------------------------------------------------------------------------------- 1 | package me.itangqi.buildingblocks.view.widget; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.support.design.widget.AppBarLayout; 6 | import android.support.design.widget.CoordinatorLayout; 7 | import android.support.design.widget.FloatingActionButton; 8 | import android.util.AttributeSet; 9 | import android.view.View; 10 | 11 | import me.itangqi.buildingblocks.R; 12 | 13 | public class ScrollingFABBehavior extends FloatingActionButton.Behavior { 14 | private int toolbarHeight; 15 | 16 | public ScrollingFABBehavior(Context context, AttributeSet attrs) { 17 | super(); 18 | this.toolbarHeight = getToolbarHeight(context); 19 | } 20 | 21 | @Override 22 | public boolean layoutDependsOn(CoordinatorLayout parent, FloatingActionButton fab, View dependency) { 23 | return super.layoutDependsOn(parent, fab, dependency) || (dependency instanceof AppBarLayout); 24 | } 25 | 26 | @Override 27 | public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton fab, View dependency) { 28 | boolean returnValue = super.onDependentViewChanged(parent, fab, dependency); 29 | if (dependency instanceof AppBarLayout) { 30 | CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) fab.getLayoutParams(); 31 | int fabBottomMargin = lp.bottomMargin; 32 | int distanceToScroll = fab.getHeight() + fabBottomMargin; 33 | float ratio = dependency.getY() / (float)toolbarHeight; 34 | fab.setTranslationY(-distanceToScroll * ratio); 35 | } 36 | return returnValue; 37 | } 38 | 39 | public int getToolbarHeight(Context context) { 40 | final TypedArray styledAttributes = context.getTheme().obtainStyledAttributes( 41 | new int[]{R.attr.actionBarSize}); 42 | int toolbarHeight = (int) styledAttributes.getDimension(0, 0); 43 | styledAttributes.recycle(); 44 | 45 | return toolbarHeight; 46 | } 47 | } -------------------------------------------------------------------------------- /app/src/main/res/color/state_selector_dark.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/color/state_selector_light.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangqi92/BuildingBlocks/36d6a9c534d37c4183aac148c7b0c9ee493c1f78/app/src/main/res/drawable-xhdpi/icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/drawer_action_collection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangqi92/BuildingBlocks/36d6a9c534d37c4183aac148c7b0c9ee493c1f78/app/src/main/res/drawable-xxhdpi/drawer_action_collection.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/drawer_action_read.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangqi92/BuildingBlocks/36d6a9c534d37c4183aac148c7b0c9ee493c1f78/app/src/main/res/drawable-xxhdpi/drawer_action_read.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/drawer_action_share.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangqi92/BuildingBlocks/36d6a9c534d37c4183aac148c7b0c9ee493c1f78/app/src/main/res/drawable-xxhdpi/drawer_action_share.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/drawer_header.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangqi92/BuildingBlocks/36d6a9c534d37c4183aac148c7b0c9ee493c1f78/app/src/main/res/drawable-xxhdpi/drawer_header.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_tile_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangqi92/BuildingBlocks/36d6a9c534d37c4183aac148c7b0c9ee493c1f78/app/src/main/res/drawable-xxhdpi/ic_tile_bg.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangqi92/BuildingBlocks/36d6a9c534d37c4183aac148c7b0c9ee493c1f78/app/src/main/res/drawable-xxhdpi/icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangqi92/BuildingBlocks/36d6a9c534d37c4183aac148c7b0c9ee493c1f78/app/src/main/res/drawable-xxxhdpi/icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_card_nopic.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_pick_photo.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/item_divider_black.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/item_divider_white.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/layout-v21/service_update_notification.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 13 | 17 | 18 | 23 | 24 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_base.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_gson_news.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 16 | 17 | 26 | 27 | 35 | 36 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 54 | 55 | 60 | 61 | 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 10 | 11 | 16 | 17 | 22 | 23 | 30 | 31 | 36 | 37 | 38 | 39 | 44 | 45 | 46 | 47 | 48 | 49 | 59 | 60 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main_dialog.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_pick_crop.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_place_auto_complete.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 23 | 24 | 25 | 26 | 31 | 32 | 33 | 39 | 40 | 45 | 46 |