├── .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 | 
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 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
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 | 
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 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
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