├── .gitignore
├── LICENSE
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── github
│ │ └── mzule
│ │ └── androidweekly
│ │ └── ApplicationTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── assets
│ │ └── materialdesignicons-webfont.ttf
│ ├── java
│ │ └── com
│ │ │ └── github
│ │ │ └── mzule
│ │ │ └── androidweekly
│ │ │ ├── App.java
│ │ │ ├── api
│ │ │ ├── ApiCallback.java
│ │ │ ├── ArticleApi.java
│ │ │ ├── DictionaryApi.java
│ │ │ └── parser
│ │ │ │ ├── ArticleParser.java
│ │ │ │ ├── ArticleParsers.java
│ │ │ │ ├── DocumentProvider.java
│ │ │ │ ├── FresherArticlesParser.java
│ │ │ │ └── OlderArticlesParser.java
│ │ │ ├── dao
│ │ │ ├── ArticleDao.java
│ │ │ ├── FavoriteDao.java
│ │ │ ├── IssueListKeeper.java
│ │ │ ├── SearchHistoryKeeper.java
│ │ │ └── TextZoomKeeper.java
│ │ │ ├── entity
│ │ │ ├── Article.java
│ │ │ ├── Dict.java
│ │ │ ├── Favorite.java
│ │ │ ├── Issue.java
│ │ │ └── TranslateResult.java
│ │ │ ├── ui
│ │ │ ├── activity
│ │ │ │ ├── AboutActivity.java
│ │ │ │ ├── ArticleActivity.java
│ │ │ │ ├── BaseActivity.java
│ │ │ │ ├── FavoriteActivity.java
│ │ │ │ ├── MainActivity.java
│ │ │ │ ├── SearchActivity.java
│ │ │ │ └── SearchResultActivity.java
│ │ │ ├── adapter
│ │ │ │ ├── ArticleAdapter.java
│ │ │ │ ├── SearchHistoryAdapter.java
│ │ │ │ └── SlideAdapter.java
│ │ │ ├── view
│ │ │ │ ├── IconButton.java
│ │ │ │ ├── NaviBar.java
│ │ │ │ ├── PinnedSectionListView.java
│ │ │ │ ├── PopupView.java
│ │ │ │ ├── ProgressView.java
│ │ │ │ ├── TintStatusBar.java
│ │ │ │ ├── TranslateView.java
│ │ │ │ └── base
│ │ │ │ │ ├── BaseLinearLayout.java
│ │ │ │ │ └── BaseRelativeLayout.java
│ │ │ └── viewtype
│ │ │ │ ├── ArticleViewType.java
│ │ │ │ ├── BaseViewType.java
│ │ │ │ ├── SearchHistoryViewType.java
│ │ │ │ ├── SectionViewType.java
│ │ │ │ └── SlideIssueViewType.java
│ │ │ └── util
│ │ │ ├── DateUtil.java
│ │ │ ├── DensityUtil.java
│ │ │ ├── DictParser.java
│ │ │ ├── IOUtil.java
│ │ │ ├── JsonUtil.java
│ │ │ ├── Keyboard.java
│ │ │ ├── MD5.java
│ │ │ ├── StemUtil.java
│ │ │ ├── ThreadUtil.java
│ │ │ └── Tinter.java
│ └── res
│ │ ├── anim
│ │ ├── slide_in_left.xml
│ │ ├── slide_in_right.xml
│ │ ├── slide_out_left.xml
│ │ └── slide_out_right.xml
│ │ ├── color
│ │ ├── text_color_slide_date.xml
│ │ └── text_color_slide_issue.xml
│ │ ├── drawable-hdpi
│ │ └── ic_logo.png
│ │ ├── drawable-xxhdpi
│ │ ├── ic_slide_menu_n.png
│ │ └── ic_slide_menu_s.png
│ │ ├── drawable
│ │ ├── bg_button.xml
│ │ ├── bg_button_n.xml
│ │ ├── bg_button_s.xml
│ │ ├── bg_dialog.xml
│ │ └── ic_slide_menu.xml
│ │ ├── layout
│ │ ├── activity_about.xml
│ │ ├── activity_article.xml
│ │ ├── activity_favorite.xml
│ │ ├── activity_main.xml
│ │ ├── activity_search.xml
│ │ ├── activity_search_result.xml
│ │ ├── item_article.xml
│ │ ├── item_search_history.xml
│ │ ├── item_section.xml
│ │ ├── item_slide_issue.xml
│ │ ├── view_navi_bar.xml
│ │ └── view_translate.xml
│ │ ├── mipmap-xxhdpi
│ │ └── ic_launcher.png
│ │ ├── values-w820dp
│ │ └── dimens.xml
│ │ └── values
│ │ ├── attrs.xml
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── ids.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── github
│ └── mzule
│ └── androidweekly
│ └── ExampleUnitTest.java
├── art
├── article.jpg
├── favorite.jpg
├── home.jpg
├── issue.jpg
├── search.jpg
└── translation.gif
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── snowball
├── .gitignore
├── build.gradle
└── src
└── main
└── java
└── org
└── tartarus
└── snowball
├── Among.java
├── SnowballProgram.java
├── SnowballStemmer.java
├── TestApp.java
└── ext
└── EnglishStem.java
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | .idea
5 | .DS_Store
6 | /build
7 | /captures
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AndroidWeekly
2 |
3 | 一个第三方 androidweekly.net 的客户端,主要用 jsoup 抓取了 androidweekly.net 的数据进行展示,很适合在空暇时间阅读浏览。
4 |
5 | ## 目前实现的功能
6 |
7 | 1. 文章列表
8 | 2. 文章页面
9 | 3. 收藏文章
10 | 4. 调整文章字体大小
11 | 5. 文章页面内建词典
12 | 6. 文章分享
13 | 7. 已读文章全文搜索
14 | 8. 收藏导入导出
15 |
16 | 欢迎提 pull request 或 issue 添加新功能,修 bug.
17 |
18 | ## 下载
19 |
20 | [https://github.com/mzule/AndroidWeekly/releases](https://github.com/mzule/AndroidWeekly/releases)
21 |
22 | ## 运行效果
23 |
24 | 
25 | 
26 | 
27 | 
28 | 
29 | 
30 |
31 | ## 感谢
32 |
33 | 本项目用了部分开源项目,在此感谢。
34 |
35 | * [ButterKnife](https://github.com/JakeWharton/butterknife)
36 | * [EasyAdapter](https://github.com/mzule/EasyAdapter)
37 | * [glide](https://github.com/bumptech/glide)
38 | * [gson](https://github.com/google/gson)
39 | * [jsoup](http://jsoup.org/)
40 | * [LayoutAnnotation](https://github.com/mzule/LayoutAnnotation)
41 | * [material-icon-lib](https://github.com/code-mc/material-icon-lib)
42 | * [materialish-progress](https://github.com/pnikosis/materialish-progress)
43 | * [Snowball](http://snowballstem.org/)
44 |
45 | ## 许可
46 |
47 | Apache License 2.0
48 |
49 | *不可发布到应用市场*
50 |
51 | ## 联系我
52 |
53 | 任何相关问题都可以通过以下方式联系我。
54 |
55 | 1. 提 issue
56 | 1. 新浪微博 [http://weibo.com/mzule](http://weibo.com/mzule)
57 | 1. 个人博客 [http://caodongping.me](http://caodongping.me)
58 | 1. 邮件 "mzule".concat("4j").concat("@").concat("gmail.com")
59 |
60 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | apply plugin: 'android-apt'
3 |
4 | android {
5 | compileSdkVersion 23
6 | buildToolsVersion "23.0.2"
7 |
8 | defaultConfig {
9 | applicationId "com.github.mzule.androidweekly"
10 | minSdkVersion 15
11 | targetSdkVersion 23
12 | versionCode 1
13 | versionName "1.0.1"
14 | }
15 | buildTypes {
16 | release {
17 | minifyEnabled false
18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
19 | }
20 | }
21 | }
22 |
23 | dependencies {
24 | compile fileTree(include: ['*.jar'], dir: 'libs')
25 | testCompile 'junit:junit:4.12'
26 | compile 'com.android.support:appcompat-v7:23.1.1'
27 | compile 'com.github.mzule.easyadapter:easyadapter:1.1.1'
28 | compile 'com.jakewharton:butterknife:7.0.1'
29 | compile 'org.jsoup:jsoup:1.8.3'
30 | compile 'com.github.bumptech.glide:glide:3.7.0'
31 | compile 'com.google.code.gson:gson:2.5'
32 | compile 'net.steamcrafted:materialiconlib:1.0.9'
33 | compile 'com.pnikosis:materialish-progress:1.7'
34 | compile project(':snowball')
35 | compile 'com.github.mzule.layoutannotation:library:1.0.4'
36 | apt 'com.github.mzule.layoutannotation:compiler:1.0.3'
37 | }
38 |
--------------------------------------------------------------------------------
/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/baidu/Library/Android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/github/mzule/androidweekly/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.github.mzule.androidweekly;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/app/src/main/assets/materialdesignicons-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mzule/AndroidWeekly/f4446585472d21224ea439e39a7d1362dcabd49b/app/src/main/assets/materialdesignicons-webfont.ttf
--------------------------------------------------------------------------------
/app/src/main/java/com/github/mzule/androidweekly/App.java:
--------------------------------------------------------------------------------
1 | package com.github.mzule.androidweekly;
2 |
3 | import android.app.Application;
4 |
5 | /**
6 | * Created by CaoDongping on 3/24/16.
7 | */
8 | public class App extends Application {
9 | private static App instance;
10 |
11 | public static App getInstance() {
12 | return instance;
13 | }
14 |
15 | @Override
16 | public void onCreate() {
17 | super.onCreate();
18 | instance = this;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/mzule/androidweekly/api/ApiCallback.java:
--------------------------------------------------------------------------------
1 | package com.github.mzule.androidweekly.api;
2 |
3 | /**
4 | * Created by CaoDongping on 3/24/16.
5 | */
6 | public interface ApiCallback {
7 |
8 | void onSuccess(T data, boolean fromCache);
9 |
10 | void onFailure(Exception e);
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/mzule/androidweekly/api/ArticleApi.java:
--------------------------------------------------------------------------------
1 | package com.github.mzule.androidweekly.api;
2 |
3 |
4 | import android.os.Handler;
5 | import android.text.TextUtils;
6 |
7 | import com.github.mzule.androidweekly.api.parser.ArticleParsers;
8 | import com.github.mzule.androidweekly.dao.ArticleDao;
9 | import com.github.mzule.androidweekly.dao.IssueListKeeper;
10 | import com.github.mzule.androidweekly.entity.Article;
11 | import com.github.mzule.androidweekly.entity.Issue;
12 |
13 | import org.jsoup.Jsoup;
14 | import org.jsoup.nodes.Document;
15 | import org.jsoup.nodes.Element;
16 | import org.jsoup.select.Elements;
17 |
18 | import java.net.URL;
19 | import java.util.ArrayList;
20 | import java.util.List;
21 |
22 | /**
23 | * Created by mzule on 3/16/16.
24 | */
25 | public class ArticleApi {
26 | //TODO new thread to threadhandler
27 | private Handler handler = new Handler();
28 | private ArticleDao articleDao;
29 |
30 | public ArticleApi() {
31 | articleDao = new ArticleDao();
32 | }
33 |
34 | public void getPage(final String issue, final ApiCallback> callback) {
35 | new Thread() {
36 | @Override
37 | public void run() {
38 | try {
39 | if (!readCache()) {
40 | postSuccess(doGetPage(issue), callback);
41 | }
42 | } catch (final Exception e) {
43 | postError(e, callback);
44 | }
45 |
46 | }
47 |
48 | private boolean readCache() {
49 | if (!TextUtils.isEmpty(issue)) {
50 | List articles = articleDao.read(issue);
51 | if (!articles.isEmpty()) {
52 | postSuccess(new Response<>(make(articles), true), callback);
53 | return true;
54 | }
55 | }
56 | return false;
57 | }
58 |
59 | private List make(List articles) {
60 | List ret = new ArrayList<>();
61 | for (Article article : articles) {
62 | if (!ret.contains(article.getSection())) {
63 | ret.add(article.getSection());
64 | }
65 | ret.add(article);
66 | }
67 | return ret;
68 | }
69 | }.start();
70 | }
71 |
72 | public void getArchive(final ApiCallback> callback) {
73 | new Thread() {
74 | @Override
75 | public void run() {
76 | try {
77 | List issues = IssueListKeeper.read();
78 | if (issues != null && !issues.isEmpty()) {
79 | postSuccess(new Response<>(issues, true), callback);
80 | }
81 | postSuccess(doGetArchive(), callback);
82 | } catch (Exception e) {
83 | postError(e, callback);
84 | }
85 | }
86 | }.start();
87 | }
88 |
89 | private Response> doGetArchive() throws Exception {
90 | Document doc = Jsoup.parse(new URL("http://androidweekly.net/archive"), 30000);
91 | Elements lis = doc.getElementsByClass("archive-list").get(0).getElementsByTag("li");
92 | List issues = new ArrayList<>();
93 | for (Element li : lis) {
94 | String date = li.getElementsByTag("span").get(0).text();
95 | String name = li.getElementsByTag("h3").text().split("\\|")[0];
96 | String url = li.getElementsByTag("a").attr("href");
97 | issues.add(new Issue(name, url, date));
98 | }
99 | IssueListKeeper.save(issues);
100 | return new Response<>(issues, false);
101 | }
102 |
103 | private Response> doGetPage(String issue) throws Exception {
104 | List result = ArticleParsers.get(issue).parse(issue);
105 | for (Object obj : result) {
106 | if (obj instanceof Article) {
107 | articleDao.save((Article) obj);
108 | }
109 | }
110 | return new Response<>(result, false);
111 | }
112 |
113 | private void postSuccess(final Response result, final ApiCallback callback) {
114 | handler.post(new Runnable() {
115 | @Override
116 | public void run() {
117 | callback.onSuccess(result.data, result.fromCache);
118 | }
119 | });
120 | }
121 |
122 | private void postError(final Exception e, final ApiCallback callback) {
123 | e.printStackTrace();
124 | handler.post(new Runnable() {
125 | @Override
126 | public void run() {
127 | callback.onFailure(e);
128 | }
129 | });
130 | }
131 |
132 | static class Response {
133 | public T data;
134 | public boolean fromCache;
135 |
136 | public Response(T data, boolean fromCache) {
137 | this.data = data;
138 | this.fromCache = fromCache;
139 | }
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/mzule/androidweekly/api/DictionaryApi.java:
--------------------------------------------------------------------------------
1 | package com.github.mzule.androidweekly.api;
2 |
3 | import android.os.Handler;
4 |
5 | import com.github.mzule.androidweekly.entity.Dict;
6 | import com.github.mzule.androidweekly.util.DictParser;
7 |
8 | import java.net.URLEncoder;
9 |
10 | /**
11 | * Created by CaoDongping on 3/31/16.
12 | */
13 | public class DictionaryApi {
14 | private Handler handler = new Handler();
15 |
16 | public void look(String q, final ApiCallback callback) {
17 | final String url = "http://dict-co.iciba.com/api/dictionary.php?w=" + URLEncoder.encode(q) + "&key=A3950A86AC38B86FCDEE6EAC68931C25&type=xml";
18 |
19 | new Thread(new Runnable() {
20 | @Override
21 | public void run() {
22 | try {
23 | final Dict result = new DictParser(url).parse();
24 | handler.post(new Runnable() {
25 | @Override
26 | public void run() {
27 | callback.onSuccess(result, false);
28 | }
29 | });
30 | } catch (final Exception e) {
31 | e.printStackTrace();
32 | handler.post(new Runnable() {
33 | @Override
34 | public void run() {
35 | callback.onFailure(e);
36 | }
37 | });
38 | }
39 | }
40 | }).start();
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/mzule/androidweekly/api/parser/ArticleParser.java:
--------------------------------------------------------------------------------
1 | package com.github.mzule.androidweekly.api.parser;
2 |
3 | import java.io.IOException;
4 | import java.util.List;
5 |
6 | /**
7 | * Created by CaoDongping on 4/15/16.
8 | */
9 | public interface ArticleParser {
10 | List parse(String issue) throws IOException;
11 | }
12 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/mzule/androidweekly/api/parser/ArticleParsers.java:
--------------------------------------------------------------------------------
1 | package com.github.mzule.androidweekly.api.parser;
2 |
3 | import android.support.annotation.WorkerThread;
4 |
5 | /**
6 | * Created by CaoDongping on 4/15/16.
7 | */
8 | public class ArticleParsers {
9 | @WorkerThread
10 | public static ArticleParser get(String issue) {
11 | if (issue == null || Integer.parseInt(issue.split("-")[1]) > 102) {
12 | return new FresherArticlesParser();
13 | } else {
14 | return new OlderArticlesParser();
15 | }
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/mzule/androidweekly/api/parser/DocumentProvider.java:
--------------------------------------------------------------------------------
1 | package com.github.mzule.androidweekly.api.parser;
2 |
3 | import org.jsoup.Jsoup;
4 | import org.jsoup.nodes.Document;
5 |
6 | import java.io.IOException;
7 | import java.net.URL;
8 |
9 | /**
10 | * Created by CaoDongping on 4/15/16.
11 | */
12 | public class DocumentProvider {
13 | public static Document get(String issue) throws IOException {
14 | String url = "http://androidweekly.net/";
15 | if (issue != null) {
16 | url += issue;
17 | }
18 | return Jsoup.parse(new URL(url), 30000);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/mzule/androidweekly/api/parser/FresherArticlesParser.java:
--------------------------------------------------------------------------------
1 | package com.github.mzule.androidweekly.api.parser;
2 |
3 | import com.github.mzule.androidweekly.entity.Article;
4 |
5 | import org.jsoup.nodes.Document;
6 | import org.jsoup.nodes.Element;
7 | import org.jsoup.select.Elements;
8 |
9 | import java.io.IOException;
10 | import java.util.ArrayList;
11 | import java.util.List;
12 |
13 | /**
14 | * Created by CaoDongping on 4/15/16.
15 | */
16 | public class FresherArticlesParser implements ArticleParser {
17 |
18 | @Override
19 | public List parse(String issue) throws IOException {
20 | Document doc = DocumentProvider.get(issue);
21 | List articles = new ArrayList<>();
22 | Elements tables = doc.getElementsByTag("table");
23 | String currentSection = null;
24 | for (Element e : tables) {
25 | Elements h2 = e.getElementsByTag("h2");
26 | Elements h5 = e.getElementsByTag("h5");// 兼容issue-226 SPONSORED 在 h5 标签里面
27 | if (!h2.isEmpty() || !h5.isEmpty()) {
28 | currentSection = h2.size() > 0 ? h2.get(0).text() : h5.get(0).text();
29 | if (!articles.contains(currentSection)) {
30 | articles.add(currentSection);
31 | }
32 | } else {
33 | Elements tds = e.getElementsByTag("td");
34 | Element td = tds.get(tds.size() - 2);
35 | String imageUrl = null;
36 | if (tds.size() == 4) {
37 | imageUrl = tds.get(0).getElementsByTag("img").get(0).attr("src");
38 | }
39 | String title = td.getElementsByClass("article-headline").get(0).text();
40 | String brief = td.getElementsByTag("p").get(0).text();
41 | String link = td.getElementsByClass("article-headline").get(0).attr("href");
42 | String domain = td.getElementsByTag("span").get(0).text().replace("(", "").replace(")", "");
43 | if (issue == null) {
44 | String number = doc.getElementsByClass("issue-header").get(0).getElementsByTag("span").get(0).text();
45 | issue = "/issues/issue-" + number.replace("#", "");
46 | }
47 | Article article = new Article();
48 | article.setTitle(title);
49 | article.setBrief(brief);
50 | article.setLink(link);
51 | article.setDomain(domain);
52 | article.setIssue(issue);
53 | article.setImageUrl(imageUrl);
54 | article.setSection(currentSection);
55 | articles.add(article);
56 | }
57 | }
58 | return articles;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/mzule/androidweekly/api/parser/OlderArticlesParser.java:
--------------------------------------------------------------------------------
1 | package com.github.mzule.androidweekly.api.parser;
2 |
3 | import com.github.mzule.androidweekly.entity.Article;
4 |
5 | import org.jsoup.nodes.Document;
6 | import org.jsoup.nodes.Element;
7 | import org.jsoup.select.Elements;
8 |
9 | import java.io.IOException;
10 | import java.util.ArrayList;
11 | import java.util.List;
12 |
13 | /**
14 | * Created by CaoDongping on 4/15/16.
15 | */
16 | public class OlderArticlesParser implements ArticleParser {
17 |
18 | @Override
19 | public List parse(String issue) throws IOException {
20 | Document doc = DocumentProvider.get(issue);
21 | List articles = new ArrayList<>();
22 | Element root = doc.getElementsByClass("issue").get(0);
23 | while (root.children().size() == 1) {
24 | root = root.child(0);
25 | }
26 | String currentSection = null;
27 | for (Element e : root.children()) {
28 | if (e.tagName().equals("h2")) {
29 | currentSection = e.text();
30 | articles.add(currentSection);
31 | continue;
32 | }
33 | if (e.tagName().equals("div")) {
34 | Elements img = e.getElementsByTag("img");
35 | if (!img.isEmpty()) {
36 | Article article = new Article();
37 | article.setImageUrl(img.get(0).attr("src"));
38 | article.setTitle(e.getElementsByTag("a").get(1).text());
39 | article.setLink(e.getElementsByTag("a").get(1).attr("href"));
40 | article.setBrief(e.getElementsByTag("p").get(0).text());
41 | Elements span = e.getElementsByTag("span");
42 | if (!span.isEmpty()) {
43 | article.setDomain(span.get(0).text().replace("(", "").replace(")", ""));
44 | }
45 | article.setIssue(issue);
46 | article.setSection(currentSection);
47 | articles.add(article);
48 | }
49 | } else {
50 | Article article = new Article();
51 | Elements title = e.getElementsByTag("a");
52 | if (title.isEmpty()) {
53 | continue;
54 | }
55 | article.setTitle(title.get(0).text());
56 | Elements span = e.getElementsByTag("span");
57 | if (!span.isEmpty()) {
58 | article.setDomain(span.get(0).text().replace("(", "").replace(")", ""));
59 | }
60 | article.setLink(e.getElementsByTag("a").get(0).attr("href"));
61 | article.setBrief(e.text());
62 | article.setIssue(issue);
63 | article.setSection(currentSection);
64 | articles.add(article);
65 | }
66 | }
67 | return articles;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/mzule/androidweekly/dao/ArticleDao.java:
--------------------------------------------------------------------------------
1 | package com.github.mzule.androidweekly.dao;
2 |
3 | import android.content.ContentValues;
4 | import android.database.Cursor;
5 | import android.database.sqlite.SQLiteDatabase;
6 | import android.database.sqlite.SQLiteOpenHelper;
7 | import android.support.annotation.NonNull;
8 |
9 | import com.github.mzule.androidweekly.App;
10 | import com.github.mzule.androidweekly.entity.Article;
11 | import com.github.mzule.androidweekly.util.StemUtil;
12 |
13 | import java.util.ArrayList;
14 | import java.util.List;
15 |
16 | /**
17 | * Created by CaoDongping on 4/2/16.
18 | */
19 | public class ArticleDao extends SQLiteOpenHelper {
20 | private static final String NAME = "article";
21 | private static final int VERSION = 1;
22 | private static final int INDEX_TITLE = 1;
23 | private static final int INDEX_BRIEF = 2;
24 | private static final int INDEX_LINK = 3;
25 | private static final int INDEX_IMAGE_URL = 4;
26 | private static final int INDEX_DOMAIN = 5;
27 | private static final int INDEX_ISSUE = 6;
28 | private static final int INDEX_SECTION = 7;
29 |
30 | public ArticleDao() {
31 | super(App.getInstance(), NAME, null, VERSION);
32 | }
33 |
34 | public List read(String issue) {
35 | List articles = new ArrayList<>();
36 | Cursor cursor = getReadableDatabase().rawQuery("SELECT * FROM ARTICLE WHERE ISSUE=?", new String[]{issue});
37 | while (cursor.moveToNext()) {
38 | articles.add(read(cursor));
39 | }
40 | cursor.close();
41 | return articles;
42 | }
43 |
44 | public List search(String q) {
45 | q = StemUtil.stem(q);
46 | List articles = new ArrayList<>();
47 | Cursor cursor = getReadableDatabase().rawQuery("SELECT * FROM ARTICLE WHERE FTS MATCH ?", new String[]{q});
48 | while (cursor.moveToNext()) {
49 | articles.add(read(cursor));
50 | }
51 | cursor.close();
52 | return articles;
53 | }
54 |
55 | @NonNull
56 | private Article read(Cursor cursor) {
57 | Article article = new Article();
58 | article.setTitle(cursor.getString(INDEX_TITLE));
59 | article.setBrief(cursor.getString(INDEX_BRIEF));
60 | article.setLink(cursor.getString(INDEX_LINK));
61 | article.setImageUrl(cursor.getString(INDEX_IMAGE_URL));
62 | article.setDomain(cursor.getString(INDEX_DOMAIN));
63 | article.setIssue(cursor.getString(INDEX_ISSUE));
64 | article.setSection(cursor.getString(INDEX_SECTION));
65 | return article;
66 | }
67 |
68 | public void save(Article article) {
69 | if (!checkExist(article)) {
70 | ContentValues cv = new ContentValues();
71 | cv.put("TITLE", article.getTitle());
72 | cv.put("BRIEF", article.getBrief());
73 | cv.put("LINK", article.getLink());
74 | cv.put("IMAGE_URL", article.getImageUrl());
75 | cv.put("DOMAIN", article.getDomain());
76 | cv.put("ISSUE", article.getIssue());
77 | cv.put("SECTION", article.getSection());
78 | cv.put("TYPE", 0);// TODO add type for article
79 | cv.put("FTS", StemUtil.stem(article.getFTS()));
80 | getWritableDatabase().insert("ARTICLE", null, cv);
81 | }
82 | }
83 |
84 | private boolean checkExist(Article article) {
85 | Cursor cursor = getReadableDatabase().rawQuery("SELECT COUNT(*) FROM ARTICLE WHERE LINK=?", new String[]{article.getLink()});
86 | cursor.moveToNext();
87 | int count = cursor.getInt(0);
88 | cursor.close();
89 | return count > 0;
90 | }
91 |
92 | @Override
93 | public void onCreate(SQLiteDatabase db) {
94 | db.execSQL("CREATE VIRTUAL TABLE ARTICLE USING fts3(" +
95 | "ID INTEGER PRIMARY KEY AUTOINCREMENT, " +
96 | "TITLE VARCHAR(255), " +
97 | "BRIEF TEXT, " +
98 | "LINK VARCHAR(255), " +
99 | "IMAGE_URL VARCHAR(255), " +
100 | "DOMAIN VARCHAR(255), " +
101 | "ISSUE VARCHAR(255), " +
102 | "SECTION VARCHAR(255), " +
103 | "TYPE INTEGER, " +
104 | "FTS TEXT)");
105 | }
106 |
107 | @Override
108 | public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/mzule/androidweekly/dao/FavoriteDao.java:
--------------------------------------------------------------------------------
1 | package com.github.mzule.androidweekly.dao;
2 |
3 | import android.content.ContentValues;
4 | import android.database.Cursor;
5 | import android.database.sqlite.SQLiteDatabase;
6 | import android.database.sqlite.SQLiteOpenHelper;
7 | import android.os.Environment;
8 |
9 | import com.github.mzule.androidweekly.App;
10 | import com.github.mzule.androidweekly.entity.Article;
11 | import com.github.mzule.androidweekly.entity.Favorite;
12 | import com.github.mzule.androidweekly.util.IOUtil;
13 | import com.github.mzule.androidweekly.util.JsonUtil;
14 | import com.google.gson.reflect.TypeToken;
15 |
16 | import java.io.File;
17 | import java.io.FileWriter;
18 | import java.io.IOException;
19 | import java.util.ArrayList;
20 | import java.util.List;
21 |
22 | /**
23 | * Created by CaoDongping on 4/2/16.
24 | */
25 | public class FavoriteDao extends SQLiteOpenHelper {
26 | private static final String NAME = "favorite";
27 | private static final int VERSION = 1;
28 | private static final int INDEX_TITLE = 1;
29 | private static final int INDEX_BRIEF = 2;
30 | private static final int INDEX_LINK = 3;
31 | private static final int INDEX_IMAGE_URL = 4;
32 | private static final int INDEX_DOMAIN = 5;
33 | private static final int INDEX_ISSUE = 6;
34 | private static final int INDEX_SECTION = 7;
35 | private static final int INDEX_TYPE = 8;
36 | private static final int INDEX_TIME = 9;
37 |
38 | public FavoriteDao() {
39 | super(App.getInstance(), NAME, null, VERSION);
40 | }
41 |
42 | public List read() {
43 | List favorites = new ArrayList<>();
44 | Cursor cursor = getReadableDatabase().rawQuery("SELECT * FROM FAVORITE ORDER BY TIME DESC", null);
45 | while (cursor.moveToNext()) {
46 | Article article = new Article();
47 | article.setTitle(cursor.getString(INDEX_TITLE));
48 | article.setBrief(cursor.getString(INDEX_BRIEF));
49 | article.setLink(cursor.getString(INDEX_LINK));
50 | article.setImageUrl(cursor.getString(INDEX_IMAGE_URL));
51 | article.setDomain(cursor.getString(INDEX_DOMAIN));
52 | article.setIssue(cursor.getString(INDEX_ISSUE));
53 | article.setSection(cursor.getString(INDEX_SECTION));
54 | Favorite favorite = new Favorite(article);
55 | favorite.setTime(cursor.getLong(INDEX_TIME));
56 | favorites.add(favorite);
57 | }
58 | cursor.close();
59 | return favorites;
60 | }
61 |
62 | public boolean contains(Article article) {
63 | Cursor cursor = getReadableDatabase().rawQuery("SELECT COUNT(*) FROM FAVORITE WHERE LINK=?", new String[]{article.getLink()});
64 | cursor.moveToNext();
65 | int count = cursor.getInt(0);
66 | cursor.close();
67 | return count > 0;
68 | }
69 |
70 | public void save(Article article) {
71 | if (!contains(article)) {
72 | ContentValues cv = new ContentValues();
73 | cv.put("TITLE", article.getTitle());
74 | cv.put("BRIEF", article.getBrief());
75 | cv.put("LINK", article.getLink());
76 | cv.put("IMAGE_URL", article.getImageUrl());
77 | cv.put("DOMAIN", article.getDomain());
78 | cv.put("ISSUE", article.getIssue());
79 | cv.put("SECTION", article.getSection());
80 | cv.put("TYPE", 0);// TODO add type for article
81 | cv.put("TIME", System.currentTimeMillis());
82 | getWritableDatabase().insert("FAVORITE", null, cv);
83 | }
84 | }
85 |
86 | public void save(Favorite favorite) {
87 | Article article = favorite.getArticle();
88 | if (!contains(article)) {
89 | ContentValues cv = new ContentValues();
90 | cv.put("TITLE", article.getTitle());
91 | cv.put("BRIEF", article.getBrief());
92 | cv.put("LINK", article.getLink());
93 | cv.put("IMAGE_URL", article.getImageUrl());
94 | cv.put("DOMAIN", article.getDomain());
95 | cv.put("ISSUE", article.getIssue());
96 | cv.put("SECTION", article.getSection());
97 | cv.put("TYPE", 0);// TODO add type for article
98 | cv.put("TIME", favorite.getTime());
99 | getWritableDatabase().insert("FAVORITE", null, cv);
100 | }
101 | }
102 |
103 | public void delete(Article article) {
104 | getWritableDatabase().delete("FAVORITE", "LINK=?", new String[]{article.getLink()});
105 | }
106 |
107 | public void exportToFile() throws IOException {
108 | List all = read();
109 | String json = JsonUtil.toJson(all);
110 | File file = new File(Environment.getExternalStorageDirectory(), "androidweeklyfavorite.json");
111 | if (!file.exists()) {
112 | file.createNewFile();
113 | }
114 | FileWriter fileWriter = null;
115 | try {
116 | fileWriter = new FileWriter(file);
117 | fileWriter.write(json);
118 | fileWriter.flush();
119 | } finally {
120 | IOUtil.close(fileWriter);
121 | }
122 | }
123 |
124 | public void importFromFile() throws IOException {
125 | File file = new File(Environment.getExternalStorageDirectory(), "androidweeklyfavorite.json");
126 | String json = IOUtil.read(file);
127 | List favorites = JsonUtil.fromJson(json, new TypeToken>() {
128 | }.getType());
129 | if (favorites != null) {
130 | for (Favorite favorite : favorites) {
131 | save(favorite);
132 | }
133 | }
134 | }
135 |
136 | @Override
137 | public void onCreate(SQLiteDatabase db) {
138 | // 不创建一个与Article的外键引用是因为Article表是cache,可能会被清除。
139 | db.execSQL("CREATE TABLE IF NOT EXISTS FAVORITE(" +
140 | "ID INTEGER PRIMARY KEY AUTOINCREMENT, " +
141 | "TITLE VARCHAR(255), " +
142 | "BRIEF TEXT, " +
143 | "LINK VARCHAR(255), " +
144 | "IMAGE_URL VARCHAR(255), " +
145 | "DOMAIN VARCHAR(255), " +
146 | "ISSUE VARCHAR(255), " +
147 | "SECTION VARCHAR(255), " +
148 | "TYPE INTEGER, " +
149 | "TIME INTEGER)");
150 | }
151 |
152 | @Override
153 | public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
154 |
155 | }
156 | }
157 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/mzule/androidweekly/dao/IssueListKeeper.java:
--------------------------------------------------------------------------------
1 | package com.github.mzule.androidweekly.dao;
2 |
3 | import android.content.Context;
4 | import android.content.SharedPreferences;
5 |
6 | import com.github.mzule.androidweekly.App;
7 | import com.github.mzule.androidweekly.entity.Issue;
8 | import com.github.mzule.androidweekly.util.JsonUtil;
9 | import com.google.gson.reflect.TypeToken;
10 |
11 | import java.util.Collections;
12 | import java.util.List;
13 |
14 | /**
15 | * Created by CaoDongping on 4/2/16.
16 | */
17 | public class IssueListKeeper {
18 |
19 | public static void save(List issues) {
20 | getSharedPreferences().edit().putString("issues", JsonUtil.toJson(issues)).apply();
21 | }
22 |
23 | public static List read() {
24 | String json = getSharedPreferences().getString("issues", null);
25 | List issues = JsonUtil.fromJson(json, new TypeToken>() {
26 | }.getType());
27 | return issues == null ? Collections.emptyList() : issues;
28 | }
29 |
30 | private static SharedPreferences getSharedPreferences() {
31 | return App.getInstance().getSharedPreferences("IssueList", Context.MODE_PRIVATE);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/mzule/androidweekly/dao/SearchHistoryKeeper.java:
--------------------------------------------------------------------------------
1 | package com.github.mzule.androidweekly.dao;
2 |
3 | import android.content.Context;
4 | import android.content.SharedPreferences;
5 | import android.text.TextUtils;
6 |
7 | import com.github.mzule.androidweekly.App;
8 | import com.github.mzule.androidweekly.util.JsonUtil;
9 | import com.google.gson.reflect.TypeToken;
10 |
11 | import java.util.ArrayList;
12 | import java.util.List;
13 |
14 | /**
15 | * Created by CaoDongping on 4/5/16.
16 | */
17 | public class SearchHistoryKeeper {
18 |
19 | private static final int MAX_SIZE = 5;
20 |
21 | public static void save(String q) {
22 | List exist = read();
23 | if (exist.contains(q)) {
24 | exist.remove(q);
25 | }
26 | exist.add(0, q);
27 | if (exist.size() > MAX_SIZE) {
28 | exist = exist.subList(0, MAX_SIZE);
29 | }
30 | getSharedPreferences().edit().putString("q", JsonUtil.toJson(exist)).apply();
31 | }
32 |
33 | public static List read() {
34 | String json = getSharedPreferences().getString("q", null);
35 | if (TextUtils.isEmpty(json)) {
36 | return new ArrayList<>();
37 | } else {
38 | return JsonUtil.fromJson(json, new TypeToken>() {
39 | }.getType());
40 | }
41 | }
42 |
43 | private static SharedPreferences getSharedPreferences() {
44 | return App.getInstance().getSharedPreferences("SearchHistory", Context.MODE_PRIVATE);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/mzule/androidweekly/dao/TextZoomKeeper.java:
--------------------------------------------------------------------------------
1 | package com.github.mzule.androidweekly.dao;
2 |
3 | import android.content.Context;
4 | import android.content.SharedPreferences;
5 |
6 | import com.github.mzule.androidweekly.App;
7 |
8 | /**
9 | * Created by CaoDongping on 3/26/16.
10 | */
11 | public class TextZoomKeeper {
12 | public static void save(int zoom) {
13 | getSharedPreferences().edit().putInt("zoom", zoom).apply();
14 | }
15 |
16 | public static int read(int def) {
17 | return getSharedPreferences().getInt("zoom", def);
18 | }
19 |
20 | private static SharedPreferences getSharedPreferences() {
21 | return App.getInstance().getSharedPreferences("TextZoom", Context.MODE_PRIVATE);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/mzule/androidweekly/entity/Article.java:
--------------------------------------------------------------------------------
1 | package com.github.mzule.androidweekly.entity;
2 |
3 | import java.io.Serializable;
4 | import java.util.Arrays;
5 |
6 | /**
7 | * Created by CaoDongping on 3/24/16.
8 | */
9 | public class Article implements Serializable {
10 | private String title;
11 | private String brief;
12 | private String link;
13 | private String imageUrl;
14 | private String domain;
15 | private String issue;
16 | private String section;
17 |
18 | public String getSection() {
19 | return section;
20 | }
21 |
22 | public void setSection(String section) {
23 | this.section = section;
24 | }
25 |
26 | public String getIssue() {
27 | return issue;
28 | }
29 |
30 | public void setIssue(String issue) {
31 | this.issue = issue;
32 | }
33 |
34 | public String getTitle() {
35 | return title;
36 | }
37 |
38 | public void setTitle(String title) {
39 | this.title = title;
40 | }
41 |
42 | public String getBrief() {
43 | return brief;
44 | }
45 |
46 | public void setBrief(String brief) {
47 | this.brief = brief;
48 | }
49 |
50 | public String getLink() {
51 | return link;
52 | }
53 |
54 | public void setLink(String link) {
55 | this.link = link;
56 | }
57 |
58 | public String getImageUrl() {
59 | return imageUrl;
60 | }
61 |
62 | public void setImageUrl(String imageUrl) {
63 | this.imageUrl = imageUrl;
64 | }
65 |
66 | public String getDomain() {
67 | return domain;
68 | }
69 |
70 | public void setDomain(String domain) {
71 | this.domain = domain;
72 | }
73 |
74 | public String getFTS() {
75 | return title + "\n" + brief;
76 | }
77 |
78 | @Override
79 | public String toString() {
80 | return Arrays.toString(new String[]{title, "\n", brief, "\n", link, "\n", imageUrl, "\n", domain, "\n", "\n"});
81 | }
82 |
83 | @Override
84 | public int hashCode() {
85 | return link == null ? 0 : link.hashCode();
86 | }
87 |
88 | @Override
89 | public boolean equals(Object o) {
90 | if (o instanceof Article) {
91 | Article other = (Article) o;
92 | return link == null ? other.link == null : link.equals(other.link);
93 | }
94 | return false;
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/mzule/androidweekly/entity/Dict.java:
--------------------------------------------------------------------------------
1 | package com.github.mzule.androidweekly.entity;
2 |
3 | import android.text.TextUtils;
4 |
5 | import java.util.ArrayList;
6 | import java.util.List;
7 |
8 | /**
9 | * Created by CaoDongping on 3/31/16.
10 | */
11 | public class Dict {
12 | private String key;
13 | private List ps;
14 | private List pos;
15 | private List pron;
16 | private List acceptation;
17 | private List sent;
18 | private String fy;
19 |
20 | public Dict() {
21 | ps = new ArrayList<>();
22 | pos = new ArrayList<>();
23 | pron = new ArrayList<>();
24 | acceptation = new ArrayList<>();
25 | sent = new ArrayList<>();
26 | }
27 |
28 | public String getContent() {
29 | StringBuilder sb = new StringBuilder();
30 | for (int i = 0; i < getPs().size(); i++) {
31 | sb.append(getPs().get(i)).append("\n");
32 | }
33 | if (!TextUtils.isEmpty(getFy())) {
34 | sb.append("\n");
35 | sb.append(getFy()).append("\n");
36 | }
37 | for (int i = 0; i < getPos().size(); i++) {
38 | sb.append("\n");
39 | sb.append(getPos().get(i)).append("\n");
40 | sb.append(getAcceptation().get(i)).append("\n");
41 | }
42 | for (int i = 0; i < getSent().size(); i++) {
43 | sb.append("\n");
44 | sb.append(getSent().get(i).getOrig()).append("\n");
45 | sb.append(getSent().get(i).getTrans()).append("\n");
46 | }
47 | return sb.toString().trim();
48 | }
49 |
50 | public void addPos(String pos) {
51 | this.pos.add(pos);
52 | }
53 |
54 | public void addAcceptation(String acceptation) {
55 | this.acceptation.add(acceptation);
56 | }
57 |
58 | public List getPos() {
59 | return pos;
60 | }
61 |
62 | public void setPos(List pos) {
63 | this.pos = pos;
64 | }
65 |
66 | public List getAcceptation() {
67 | return acceptation;
68 | }
69 |
70 | public void setAcceptation(List acceptation) {
71 | this.acceptation = acceptation;
72 | }
73 |
74 | public void addPs(String ps) {
75 | this.ps.add(ps);
76 | }
77 |
78 | public void addPron(String pron) {
79 | this.pron.add(pron);
80 | }
81 |
82 | public void addSent(Sent sent) {
83 | this.sent.add(sent);
84 | }
85 |
86 | public String getKey() {
87 | return key;
88 | }
89 |
90 | public void setKey(String key) {
91 | this.key = key;
92 | }
93 |
94 | public List getPs() {
95 | return ps;
96 | }
97 |
98 | public void setPs(List ps) {
99 | this.ps = ps;
100 | }
101 |
102 | public List getPron() {
103 | return pron;
104 | }
105 |
106 | public void setPron(List pron) {
107 | this.pron = pron;
108 | }
109 |
110 | public List getSent() {
111 | return sent;
112 | }
113 |
114 | public void setSent(List sent) {
115 | this.sent = sent;
116 | }
117 |
118 | public String getFy() {
119 | return fy;
120 | }
121 |
122 | public void setFy(String fy) {
123 | this.fy = fy;
124 | }
125 |
126 | public static class Sent {
127 | private String orig;
128 | private String trans;
129 |
130 | public String getOrig() {
131 | return orig;
132 | }
133 |
134 | public void setOrig(String orig) {
135 | this.orig = orig;
136 | }
137 |
138 | public String getTrans() {
139 | return trans;
140 | }
141 |
142 | public void setTrans(String trans) {
143 | this.trans = trans;
144 | }
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/mzule/androidweekly/entity/Favorite.java:
--------------------------------------------------------------------------------
1 | package com.github.mzule.androidweekly.entity;
2 |
3 | import java.io.Serializable;
4 |
5 | /**
6 | * Created by CaoDongping on 3/28/16.
7 | */
8 | public class Favorite implements Serializable {
9 | private Article article;
10 | private long time;
11 |
12 | public Favorite(Article article) {
13 | this.article = article;
14 | this.time = System.currentTimeMillis();
15 | }
16 |
17 | public Article getArticle() {
18 | return article;
19 | }
20 |
21 | public void setArticle(Article article) {
22 | this.article = article;
23 | }
24 |
25 | public long getTime() {
26 | return time;
27 | }
28 |
29 | public void setTime(long time) {
30 | this.time = time;
31 | }
32 |
33 | @Override
34 | public boolean equals(Object o) {
35 | return o instanceof Favorite && article.equals(((Favorite) o).article);
36 | }
37 |
38 | @Override
39 | public int hashCode() {
40 | return article.hashCode();
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/mzule/androidweekly/entity/Issue.java:
--------------------------------------------------------------------------------
1 | package com.github.mzule.androidweekly.entity;
2 |
3 | import java.io.Serializable;
4 |
5 | /**
6 | * Created by CaoDongping on 3/25/16.
7 | */
8 | public class Issue implements Serializable {
9 | private String date;
10 | private String name;
11 | private String url;
12 | private boolean active;
13 |
14 | public Issue(String name) {
15 | this.name = name;
16 | }
17 |
18 | public Issue(String name, boolean active) {
19 | this.name = name;
20 | this.active = active;
21 | }
22 |
23 | public Issue(String name, String url, String date) {
24 | this.name = name;
25 | this.url = url;
26 | this.date = date;
27 | }
28 |
29 | public String getUrl() {
30 | return url;
31 | }
32 |
33 | public void setUrl(String url) {
34 | this.url = url;
35 | }
36 |
37 | public String getDate() {
38 | return date;
39 | }
40 |
41 | public void setDate(String date) {
42 | this.date = date;
43 | }
44 |
45 | public String getName() {
46 | return name;
47 | }
48 |
49 | public void setName(String name) {
50 | this.name = name;
51 | }
52 |
53 | public boolean isActive() {
54 | return active;
55 | }
56 |
57 | public void setActive(boolean active) {
58 | this.active = active;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/mzule/androidweekly/entity/TranslateResult.java:
--------------------------------------------------------------------------------
1 | package com.github.mzule.androidweekly.entity;
2 |
3 | import com.google.gson.annotations.SerializedName;
4 |
5 | import java.util.List;
6 |
7 | /**
8 | * Created by CaoDongping on 3/30/16.
9 | */
10 | public class TranslateResult {
11 | private String from;
12 | private String to;
13 | @SerializedName("trans_result")
14 | private List result;
15 |
16 | public String getFrom() {
17 | return from;
18 | }
19 |
20 | public void setFrom(String from) {
21 | this.from = from;
22 | }
23 |
24 | public String getTo() {
25 | return to;
26 | }
27 |
28 | public void setTo(String to) {
29 | this.to = to;
30 | }
31 |
32 | public List getResult() {
33 | return result;
34 | }
35 |
36 | public void setResult(List result) {
37 | this.result = result;
38 | }
39 |
40 | public String getDst() {
41 | if (getResult() != null && !getResult().isEmpty()) {
42 | return getResult().get(0).getDst();
43 | }
44 | return null;
45 | }
46 |
47 | public static class Result {
48 | private String src;
49 | private String dst;
50 |
51 | public Result(String src, String dst) {
52 | this.src = src;
53 | this.dst = dst;
54 | }
55 |
56 | public String getSrc() {
57 | return src;
58 | }
59 |
60 | public void setSrc(String src) {
61 | this.src = src;
62 | }
63 |
64 | public String getDst() {
65 | return dst;
66 | }
67 |
68 | public void setDst(String dst) {
69 | this.dst = dst;
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/mzule/androidweekly/ui/activity/AboutActivity.java:
--------------------------------------------------------------------------------
1 | package com.github.mzule.androidweekly.ui.activity;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 | import android.text.Html;
6 | import android.text.method.LinkMovementMethod;
7 | import android.widget.TextView;
8 |
9 | import com.github.mzule.androidweekly.R;
10 | import com.github.mzule.layoutannotation.Layout;
11 |
12 | import butterknife.Bind;
13 |
14 | /**
15 | * Created by CaoDongping on 4/12/16.
16 | */
17 | @Layout(R.layout.activity_about)
18 | public class AboutActivity extends BaseActivity {
19 | @Bind(R.id.textView)
20 | TextView textView;
21 |
22 | public static Intent makeIntent(Context context) {
23 | return new Intent(context, AboutActivity.class);
24 | }
25 |
26 | @Override
27 | protected void afterBind() {
28 | textView.setMovementMethod(LinkMovementMethod.getInstance());
29 | textView.setText(Html.fromHtml("Copyright: http://androidweekly.net/ " +
30 | "Github Host: https://github.com/mzule/AndroidWeekly " +
31 | "Contributor: mzule / maoruibin "));
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/mzule/androidweekly/ui/activity/ArticleActivity.java:
--------------------------------------------------------------------------------
1 | package com.github.mzule.androidweekly.ui.activity;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.content.ClipData;
5 | import android.content.ClipboardManager;
6 | import android.content.Context;
7 | import android.content.Intent;
8 | import android.support.v4.view.GravityCompat;
9 | import android.support.v4.widget.DrawerLayout;
10 | import android.view.View;
11 | import android.webkit.WebSettings;
12 | import android.webkit.WebView;
13 | import android.webkit.WebViewClient;
14 | import android.widget.Toast;
15 |
16 | import com.github.mzule.androidweekly.R;
17 | import com.github.mzule.androidweekly.dao.FavoriteDao;
18 | import com.github.mzule.androidweekly.dao.TextZoomKeeper;
19 | import com.github.mzule.androidweekly.entity.Article;
20 | import com.github.mzule.androidweekly.ui.view.ProgressView;
21 | import com.github.mzule.androidweekly.ui.view.TranslateView;
22 | import com.github.mzule.layoutannotation.Layout;
23 |
24 | import butterknife.Bind;
25 | import butterknife.OnClick;
26 |
27 | /**
28 | * Created by CaoDongping on 3/24/16.
29 | */
30 | @Layout(R.layout.activity_article)
31 | public class ArticleActivity extends BaseActivity {
32 | @Bind(R.id.webView)
33 | WebView webView;
34 | @Bind(R.id.progressView)
35 | ProgressView progressView;
36 | @Bind(R.id.drawerLayout)
37 | DrawerLayout drawerLayout;
38 | @Bind(R.id.favoriteButton)
39 | View favoriteButton;
40 | private Article article;
41 | private WebSettings settings;
42 | private boolean changed;
43 | private FavoriteDao favoriteDao;
44 |
45 | public static Intent makeIntent(Context context, Article article) {
46 | Intent intent = new Intent(context, ArticleActivity.class);
47 | intent.putExtra("article", article);
48 | return intent;
49 | }
50 |
51 | @OnClick(R.id.favoriteButton)
52 | void favorite(View v) {
53 | changed = true;
54 | v.setSelected(!v.isSelected());
55 | if (v.isSelected()) {
56 | favoriteDao.save(article);
57 | } else {
58 | favoriteDao.delete(article);
59 | }
60 | drawerLayout.closeDrawers();
61 | }
62 |
63 | @OnClick(R.id.increaseButton)
64 | void increate() {
65 | settings.setTextZoom(settings.getTextZoom() + 5);
66 | }
67 |
68 | @OnClick(R.id.decreaseButton)
69 | void decrease() {
70 | settings.setTextZoom(settings.getTextZoom() - 5);
71 | }
72 |
73 | @OnClick(R.id.translateButton)
74 | void translateAndPaste(View v) {
75 | new TranslateView(this).attachTo(this, null);
76 | drawerLayout.closeDrawers();
77 | }
78 |
79 | @OnClick(R.id.shareButton)
80 | void share() {
81 | Intent intent = new Intent(Intent.ACTION_SEND);
82 | intent.putExtra(Intent.EXTRA_TEXT, article.getTitle() + " " + article.getLink());
83 | intent.setType("text/plain");
84 | startActivity(Intent.createChooser(intent, "SHARE"));
85 |
86 | drawerLayout.closeDrawers();
87 | }
88 |
89 | @OnClick(R.id.copyUrlButton)
90 | void copyUrl() {
91 | ClipboardManager clipboard = (ClipboardManager)getSystemService(Context.CLIPBOARD_SERVICE);
92 | ClipData clip = ClipData.newPlainText("label",article.getLink());
93 | clipboard.setPrimaryClip(clip);
94 | drawerLayout.closeDrawers();
95 | Toast.makeText(ArticleActivity.this, "已复制文章链接到粘贴板", Toast.LENGTH_SHORT).show();
96 | }
97 |
98 | @SuppressLint("SetJavaScriptEnabled")
99 | @Override
100 | protected void afterBind() {
101 | favoriteDao = new FavoriteDao();
102 | article = (Article) getIntent().getSerializableExtra("article");
103 | settings = webView.getSettings();
104 | webView.loadUrl(article.getLink());
105 | webView.setWebViewClient(new WebViewClient() {
106 | @Override
107 | public void onPageFinished(WebView view, String url) {
108 | progressView.stop();
109 | }
110 | });
111 | settings.setTextZoom(TextZoomKeeper.read(settings.getTextZoom()));
112 | settings.setJavaScriptEnabled(true);
113 | settings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
114 | settings.setDomStorageEnabled(true);
115 | favoriteButton.setSelected(favoriteDao.contains(article));
116 | }
117 |
118 | @Override
119 | protected void onStop() {
120 | super.onStop();
121 | TextZoomKeeper.save(settings.getTextZoom());
122 | }
123 |
124 | @Override
125 | public void finish() {
126 | Intent intent = new Intent();
127 | intent.putExtra("changed", changed);
128 | setResult(RESULT_OK, intent);
129 | super.finish();
130 | }
131 |
132 | @Override
133 | public void onBackPressed() {
134 | if (drawerLayout.isDrawerOpen(GravityCompat.START)) {
135 | drawerLayout.closeDrawers();
136 | } else if (webView.canGoBack()) {
137 | webView.goBack();
138 | } else {
139 | super.onBackPressed();
140 | }
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/mzule/androidweekly/ui/activity/BaseActivity.java:
--------------------------------------------------------------------------------
1 | package com.github.mzule.androidweekly.ui.activity;
2 |
3 | import android.os.Bundle;
4 | import android.support.v4.app.FragmentActivity;
5 | import android.view.View;
6 |
7 | import com.github.mzule.androidweekly.R;
8 | import com.github.mzule.androidweekly.ui.view.PopupView;
9 | import com.github.mzule.androidweekly.util.Tinter;
10 | import com.github.mzule.layoutannotation.LayoutBinder;
11 |
12 | import butterknife.ButterKnife;
13 |
14 | /**
15 | * Created by CaoDongping on 3/24/16.
16 | */
17 | public abstract class BaseActivity extends FragmentActivity {
18 | @Override
19 | protected void onCreate(Bundle savedInstanceState) {
20 | super.onCreate(savedInstanceState);
21 | LayoutBinder.bind(this);
22 | ButterKnife.bind(this);
23 | Tinter.enableIfSupport(this);
24 | afterBind();
25 | }
26 |
27 | public void back(View v) {
28 | onBackPressed();
29 | }
30 |
31 | @Override
32 | public void onBackPressed() {
33 | PopupView popupView = (PopupView) findViewById(R.id.popup_view_id);
34 | if (popupView == null) {
35 | super.onBackPressed();
36 | } else {
37 | popupView.finish();
38 | }
39 | }
40 |
41 | protected abstract void afterBind();
42 | }
43 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/mzule/androidweekly/ui/activity/FavoriteActivity.java:
--------------------------------------------------------------------------------
1 | package com.github.mzule.androidweekly.ui.activity;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 | import android.support.v4.widget.DrawerLayout;
6 | import android.view.View;
7 | import android.widget.AdapterView;
8 | import android.widget.ListView;
9 | import android.widget.Toast;
10 |
11 | import com.github.mzule.androidweekly.R;
12 | import com.github.mzule.androidweekly.dao.FavoriteDao;
13 | import com.github.mzule.androidweekly.entity.Article;
14 | import com.github.mzule.androidweekly.entity.Favorite;
15 | import com.github.mzule.androidweekly.ui.adapter.ArticleAdapter;
16 | import com.github.mzule.androidweekly.util.DateUtil;
17 | import com.github.mzule.layoutannotation.Layout;
18 |
19 | import java.io.IOException;
20 | import java.util.ArrayList;
21 | import java.util.List;
22 |
23 | import butterknife.Bind;
24 | import butterknife.OnClick;
25 | import butterknife.OnItemClick;
26 |
27 | /**
28 | * Created by CaoDongping on 3/28/16.
29 | */
30 | @Layout(R.layout.activity_favorite)
31 | public class FavoriteActivity extends BaseActivity {
32 | private static final int REQUEST_CODE_OPEN_ARTICLE = 0x1;
33 | @Bind(R.id.drawerLayout)
34 | DrawerLayout drawerLayout;
35 | @Bind(R.id.listView)
36 | ListView listView;
37 | private FavoriteDao favoriteDao;
38 | private ArticleAdapter adapter;
39 |
40 | public static Intent makeIntent(Context context) {
41 | return new Intent(context, FavoriteActivity.class);
42 | }
43 |
44 | @OnItemClick(R.id.listView)
45 | void onItemClick(AdapterView> parent, View view, int position, long id) {
46 | Object item = parent.getAdapter().getItem(position);
47 | if (item instanceof Article) {
48 | startActivityForResult(ArticleActivity.makeIntent(this, (Article) item), REQUEST_CODE_OPEN_ARTICLE);
49 | }
50 | }
51 |
52 | @OnClick(R.id.exportButton)
53 | void exportToFile() {
54 | try {
55 | favoriteDao.exportToFile();
56 | } catch (IOException e) {
57 | e.printStackTrace();
58 | }
59 | Toast.makeText(this, "Export complete", Toast.LENGTH_SHORT).show();
60 | drawerLayout.closeDrawers();
61 | }
62 |
63 | @OnClick(R.id.importButton)
64 | void importFromFile() {
65 | try {
66 | favoriteDao.importFromFile();
67 | } catch (IOException e) {
68 | e.printStackTrace();
69 | }
70 | adapter.clear();
71 | adapter.addAndNotify(extract(favoriteDao.read()));
72 | Toast.makeText(this, "Import complete", Toast.LENGTH_SHORT).show();
73 | drawerLayout.closeDrawers();
74 | }
75 |
76 | @Override
77 | protected void afterBind() {
78 | favoriteDao = new FavoriteDao();
79 | adapter = new ArticleAdapter(this);
80 | listView.setAdapter(adapter);
81 | renderFavorites();
82 | }
83 |
84 | private void renderFavorites() {
85 | List favorites = favoriteDao.read();
86 | adapter.clear();
87 | adapter.addAndNotify(extract(favorites));
88 | }
89 |
90 | private List extract(List favorites) {
91 | List ret = new ArrayList<>();
92 | for (Favorite favorite : favorites) {
93 | String time = DateUtil.format(favorite.getTime());
94 | if (!ret.contains(time)) {
95 | ret.add(time);
96 | }
97 | ret.add(favorite.getArticle());
98 | }
99 | return ret;
100 | }
101 |
102 | @Override
103 | protected void onActivityResult(int requestCode, int resultCode, Intent data) {
104 | super.onActivityResult(requestCode, resultCode, data);
105 | if (resultCode == RESULT_OK && requestCode == REQUEST_CODE_OPEN_ARTICLE) {
106 | if (data.getBooleanExtra("changed", false)) {
107 | renderFavorites();
108 | }
109 | }
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/mzule/androidweekly/ui/activity/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.github.mzule.androidweekly.ui.activity;
2 |
3 | import android.support.v4.view.GravityCompat;
4 | import android.support.v4.widget.DrawerLayout;
5 | import android.view.View;
6 | import android.widget.AdapterView;
7 | import android.widget.ListView;
8 |
9 | import com.github.mzule.androidweekly.R;
10 | import com.github.mzule.androidweekly.api.ApiCallback;
11 | import com.github.mzule.androidweekly.api.ArticleApi;
12 | import com.github.mzule.androidweekly.entity.Article;
13 | import com.github.mzule.androidweekly.entity.Issue;
14 | import com.github.mzule.androidweekly.ui.adapter.ArticleAdapter;
15 | import com.github.mzule.androidweekly.ui.adapter.SlideAdapter;
16 | import com.github.mzule.androidweekly.ui.view.ProgressView;
17 | import com.github.mzule.layoutannotation.Layout;
18 |
19 | import java.util.List;
20 |
21 | import butterknife.Bind;
22 | import butterknife.OnClick;
23 | import butterknife.OnItemClick;
24 |
25 | @Layout(R.layout.activity_main)
26 | public class MainActivity extends BaseActivity {
27 | @Bind(R.id.drawerLayout)
28 | DrawerLayout drawerLayout;
29 | @Bind(R.id.listView)
30 | ListView listView;
31 | @Bind(R.id.progressView)
32 | ProgressView progressView;
33 | @Bind(R.id.slideListView)
34 | ListView slideListView;
35 | private ArticleAdapter adapter;
36 | private SlideAdapter slideAdapter;
37 | private List issues;
38 | private ArticleApi articleApi;
39 |
40 | @OnItemClick(R.id.slideListView)
41 | void onSlideItemClick(AdapterView> parent, View view, int position, long id) {
42 | Issue issue = (Issue) parent.getAdapter().getItem(position);
43 | active(issue);
44 | slideAdapter.notifyDataSetChanged();
45 |
46 | sendArticleListRequest(issue.getUrl());
47 |
48 | drawerLayout.closeDrawers();
49 | progressView.start();
50 | }
51 |
52 | @OnItemClick(R.id.listView)
53 | void onItemClick(AdapterView> parent, View view, int position, long id) {
54 | Object item = parent.getAdapter().getItem(position);
55 | if (item instanceof Article) {
56 | startActivity(ArticleActivity.makeIntent(this, (Article) item));
57 | }
58 | }
59 |
60 | @OnClick(R.id.slideMenuButton)
61 | void onSlideMenuClick() {
62 | drawerLayout.openDrawer(GravityCompat.START);
63 | }
64 |
65 | @OnClick(R.id.favoriteButton)
66 | void onFavoriteClick() {
67 | startActivity(FavoriteActivity.makeIntent(this));
68 | drawerLayout.closeDrawers();
69 | }
70 |
71 | @OnClick(R.id.searchButton)
72 | void onSearchClicK() {
73 | startActivity(SearchActivity.makeIntent(this));
74 | drawerLayout.closeDrawers();
75 | }
76 |
77 | @OnClick(R.id.aboutButton)
78 | void onAboutClick() {
79 | startActivity(AboutActivity.makeIntent(this));
80 | drawerLayout.closeDrawers();
81 | }
82 |
83 | @Override
84 | protected void afterBind() {
85 | articleApi = new ArticleApi();
86 |
87 | adapter = new ArticleAdapter(this);
88 | listView.setAdapter(adapter);
89 |
90 | slideAdapter = new SlideAdapter(this);
91 | slideListView.setAdapter(slideAdapter);
92 |
93 | sendArticleListRequest(null);
94 | sendIssueListRequest();
95 | }
96 |
97 | private void active(Issue issue) {
98 | for (Issue i : issues) {
99 | i.setActive(i == issue);
100 | }
101 | }
102 |
103 | private void sendArticleListRequest(String issue) {
104 | articleApi.getPage(issue, new ApiCallback>() {
105 | @Override
106 | public void onSuccess(List data, boolean fromCache) {
107 | adapter.clear();
108 | adapter.addAndNotify(data);
109 | listView.setSelection(0);
110 | progressView.stop();
111 | }
112 |
113 | @Override
114 | public void onFailure(Exception e) {
115 | progressView.stop();
116 | }
117 | });
118 | }
119 |
120 | private void sendIssueListRequest() {
121 | articleApi.getArchive(new ApiCallback>() {
122 | @Override
123 | public void onSuccess(List data, boolean fromCache) {
124 | issues = data;
125 | slideAdapter.clear();
126 | slideAdapter.addAndNotify(issues);
127 | active(issues.get(0));
128 | }
129 |
130 | @Override
131 | public void onFailure(Exception e) {
132 | }
133 | });
134 | }
135 |
136 | @Override
137 | public void onBackPressed() {
138 | if (drawerLayout.isDrawerOpen(GravityCompat.START)) {
139 | drawerLayout.closeDrawers();
140 | } else {
141 | super.onBackPressed();
142 | }
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/mzule/androidweekly/ui/activity/SearchActivity.java:
--------------------------------------------------------------------------------
1 | package com.github.mzule.androidweekly.ui.activity;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 | import android.view.KeyEvent;
6 | import android.view.View;
7 | import android.widget.AdapterView;
8 | import android.widget.EditText;
9 | import android.widget.ListView;
10 | import android.widget.TextView;
11 |
12 | import com.github.mzule.androidweekly.R;
13 | import com.github.mzule.androidweekly.dao.SearchHistoryKeeper;
14 | import com.github.mzule.androidweekly.ui.adapter.SearchHistoryAdapter;
15 | import com.github.mzule.layoutannotation.Layout;
16 |
17 | import butterknife.Bind;
18 | import butterknife.OnItemClick;
19 |
20 | /**
21 | * Created by CaoDongping on 4/5/16.
22 | */
23 | @Layout(R.layout.activity_search)
24 | public class SearchActivity extends BaseActivity {
25 | @Bind(R.id.queryInput)
26 | EditText queryInput;
27 | @Bind(R.id.listView)
28 | ListView listView;
29 | private SearchHistoryAdapter adapter;
30 |
31 | public static Intent makeIntent(Context context) {
32 | return new Intent(context, SearchActivity.class);
33 | }
34 |
35 | @OnItemClick(R.id.listView)
36 | void onItemClick(AdapterView> parent, View view, int position, long id) {
37 | String q = (String) parent.getAdapter().getItem(position);
38 | SearchHistoryKeeper.save(q);
39 | startActivity(SearchResultActivity.makeIntent(this, q));
40 | }
41 |
42 | @Override
43 | protected void afterBind() {
44 | adapter = new SearchHistoryAdapter(this);
45 | listView.setAdapter(adapter);
46 | queryInput.setOnEditorActionListener(new TextView.OnEditorActionListener() {
47 | @Override
48 | public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
49 | String q = queryInput.getText().toString().trim();
50 | startActivity(SearchResultActivity.makeIntent(SearchActivity.this, q));
51 | SearchHistoryKeeper.save(q);
52 | queryInput.setText("");
53 | return true;
54 | }
55 | });
56 | }
57 |
58 | @Override
59 | protected void onStart() {
60 | super.onStart();
61 | adapter.clear();
62 | adapter.addAndNotify(SearchHistoryKeeper.read());
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/mzule/androidweekly/ui/activity/SearchResultActivity.java:
--------------------------------------------------------------------------------
1 | package com.github.mzule.androidweekly.ui.activity;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 | import android.view.View;
6 | import android.widget.AdapterView;
7 | import android.widget.ListView;
8 |
9 | import com.github.mzule.androidweekly.R;
10 | import com.github.mzule.androidweekly.dao.ArticleDao;
11 | import com.github.mzule.androidweekly.entity.Article;
12 | import com.github.mzule.androidweekly.ui.adapter.ArticleAdapter;
13 | import com.github.mzule.androidweekly.ui.view.NaviBar;
14 | import com.github.mzule.layoutannotation.Layout;
15 |
16 | import java.util.List;
17 |
18 | import butterknife.Bind;
19 | import butterknife.OnItemClick;
20 |
21 | /**
22 | * Created by CaoDongping on 4/5/16.
23 | */
24 | @Layout(R.layout.activity_search_result)
25 | public class SearchResultActivity extends BaseActivity {
26 | @Bind(R.id.naviBar)
27 | NaviBar naviBar;
28 | @Bind(R.id.listView)
29 | ListView listView;
30 |
31 | public static Intent makeIntent(Context context, String q) {
32 | Intent intent = new Intent(context, SearchResultActivity.class);
33 | intent.putExtra("q", q);
34 | return intent;
35 | }
36 |
37 | @OnItemClick(R.id.listView)
38 | void onItemClick(AdapterView> parent, View view, int position, long id) {
39 | Object item = parent.getAdapter().getItem(position);
40 | if (item instanceof Article) {
41 | startActivity(ArticleActivity.makeIntent(this, (Article) item));
42 | }
43 | }
44 |
45 | @Override
46 | protected void afterBind() {
47 | String q = getIntent().getStringExtra("q");
48 | naviBar.setLeftText(q.toUpperCase());
49 |
50 | ArticleDao articleDao = new ArticleDao();
51 | List result = articleDao.search(q);
52 | ArticleAdapter adapter = new ArticleAdapter(this);
53 | listView.setAdapter(adapter);
54 | adapter.addAndNotify(result);
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/mzule/androidweekly/ui/adapter/ArticleAdapter.java:
--------------------------------------------------------------------------------
1 | package com.github.mzule.androidweekly.ui.adapter;
2 |
3 | import android.content.Context;
4 |
5 | import com.github.mzule.androidweekly.entity.Article;
6 | import com.github.mzule.androidweekly.ui.view.PinnedSectionListView;
7 | import com.github.mzule.androidweekly.ui.viewtype.ArticleViewType;
8 | import com.github.mzule.androidweekly.ui.viewtype.SectionViewType;
9 | import com.github.mzule.easyadapter.TypePerEntityAdapter;
10 |
11 | /**
12 | * Created by CaoDongping on 3/24/16.
13 | */
14 | public class ArticleAdapter extends TypePerEntityAdapter implements PinnedSectionListView.PinnedSectionListAdapter {
15 |
16 | public ArticleAdapter(Context context) {
17 | super(context);
18 | }
19 |
20 | @Override
21 | protected void mapEntityViewTypes() {
22 | mapEntityViewType(String.class, SectionViewType.class);
23 | mapEntityViewType(Article.class, ArticleViewType.class);
24 | }
25 |
26 | @Override
27 | public boolean isItemViewTypePinned(int viewType) {
28 | return getRawViewType(SectionViewType.class) == viewType;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/mzule/androidweekly/ui/adapter/SearchHistoryAdapter.java:
--------------------------------------------------------------------------------
1 | package com.github.mzule.androidweekly.ui.adapter;
2 |
3 | import android.content.Context;
4 |
5 | import com.github.mzule.androidweekly.ui.viewtype.SearchHistoryViewType;
6 | import com.github.mzule.easyadapter.SingleTypeAdapter;
7 | import com.github.mzule.easyadapter.ViewType;
8 |
9 | /**
10 | * Created by CaoDongping on 4/5/16.
11 | */
12 | public class SearchHistoryAdapter extends SingleTypeAdapter {
13 | public SearchHistoryAdapter(Context context) {
14 | super(context);
15 | }
16 |
17 | @Override
18 | protected Class extends ViewType> singleViewType() {
19 | return SearchHistoryViewType.class;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/mzule/androidweekly/ui/adapter/SlideAdapter.java:
--------------------------------------------------------------------------------
1 | package com.github.mzule.androidweekly.ui.adapter;
2 |
3 | import android.content.Context;
4 |
5 | import com.github.mzule.androidweekly.entity.Issue;
6 | import com.github.mzule.androidweekly.ui.viewtype.SlideIssueViewType;
7 | import com.github.mzule.easyadapter.SingleTypeAdapter;
8 | import com.github.mzule.easyadapter.ViewType;
9 |
10 | /**
11 | * Created by CaoDongping on 3/25/16.
12 | */
13 | public class SlideAdapter extends SingleTypeAdapter {
14 |
15 | public SlideAdapter(Context context) {
16 | super(context);
17 | }
18 |
19 | @Override
20 | protected Class extends ViewType> singleViewType() {
21 | return SlideIssueViewType.class;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/mzule/androidweekly/ui/view/IconButton.java:
--------------------------------------------------------------------------------
1 | package com.github.mzule.androidweekly.ui.view;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 |
6 | import com.github.mzule.androidweekly.R;
7 |
8 | import net.steamcrafted.materialiconlib.MaterialIconView;
9 |
10 | /**
11 | * Created by CaoDongping on 3/27/16.
12 | */
13 | public class IconButton extends MaterialIconView {
14 | public IconButton(Context context) {
15 | super(context);
16 | }
17 |
18 | public IconButton(Context context, AttributeSet attrs) {
19 | super(context, attrs);
20 | }
21 |
22 | public IconButton(Context context, AttributeSet attrs, int defStyle) {
23 | super(context, attrs, defStyle);
24 | }
25 |
26 | @Override
27 | public void setPressed(boolean pressed) {
28 | super.setPressed(pressed);
29 | if (pressed) {
30 | setColor(getResources().getColor(R.color.white));
31 | setBackgroundColor(getResources().getColor(R.color.colorPrimary));
32 | } else if (!isSelected()) {
33 | setColor(getResources().getColor(R.color.colorPrimary));
34 | setBackgroundColor(getResources().getColor(R.color.transparent));
35 | }
36 | }
37 |
38 | @Override
39 | public void setSelected(boolean selected) {
40 | super.setSelected(selected);
41 | if (selected) {
42 | setColor(getResources().getColor(R.color.white));
43 | setBackgroundResource(R.color.colorPrimary);
44 | } else {
45 | setColor(getResources().getColor(R.color.colorPrimary));
46 | setBackgroundResource(R.color.transparent);
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/mzule/androidweekly/ui/view/NaviBar.java:
--------------------------------------------------------------------------------
1 | package com.github.mzule.androidweekly.ui.view;
2 |
3 | import android.app.Activity;
4 | import android.content.Context;
5 | import android.content.res.TypedArray;
6 | import android.util.AttributeSet;
7 | import android.widget.TextView;
8 |
9 | import com.github.mzule.androidweekly.R;
10 | import com.github.mzule.androidweekly.ui.view.base.BaseLinearLayout;
11 | import com.github.mzule.layoutannotation.Layout;
12 |
13 | import butterknife.Bind;
14 | import butterknife.OnClick;
15 |
16 | /**
17 | * Created by CaoDongping on 4/5/16.
18 | */
19 | @Layout(R.layout.view_navi_bar)
20 | public class NaviBar extends BaseLinearLayout {
21 |
22 | @Bind(R.id.leftTextView)
23 | TextView leftTextView;
24 | @Bind(R.id.rightTextView)
25 | TextView rightTextView;
26 |
27 | public NaviBar(Context context) {
28 | super(context);
29 | }
30 |
31 | public NaviBar(Context context, AttributeSet attrs) {
32 | super(context, attrs);
33 | }
34 |
35 | public NaviBar(Context context, AttributeSet attrs, int defStyleAttr) {
36 | super(context, attrs, defStyleAttr);
37 | }
38 |
39 | @Override
40 | protected void init(Context context, AttributeSet attrs) {
41 | super.init(context, attrs);
42 | if (attrs != null) {
43 | TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.NaviBar);
44 | leftTextView.setText(a.getString(R.styleable.NaviBar_nb_left_text));
45 | rightTextView.setText(a.getString(R.styleable.NaviBar_nb_right_text));
46 | a.recycle();
47 | }
48 | }
49 |
50 | @OnClick(R.id.backButton)
51 | void back() {
52 | if (getContext() instanceof Activity) {
53 | ((Activity) getContext()).finish();
54 | }
55 | }
56 |
57 | public void setLeftText(String text) {
58 | leftTextView.setText(text);
59 | }
60 |
61 | public void setRightText(String text) {
62 | rightTextView.setText(text);
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/mzule/androidweekly/ui/view/PinnedSectionListView.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2013 Sergej Shafarenka, halfbit.de
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file kt 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 com.github.mzule.androidweekly.ui.view;
18 |
19 | import android.content.Context;
20 | import android.database.DataSetObserver;
21 | import android.graphics.Canvas;
22 | import android.graphics.Color;
23 | import android.graphics.PointF;
24 | import android.graphics.Rect;
25 | import android.graphics.drawable.GradientDrawable;
26 | import android.graphics.drawable.GradientDrawable.Orientation;
27 | import android.os.Parcelable;
28 | import android.util.AttributeSet;
29 | import android.view.MotionEvent;
30 | import android.view.SoundEffectConstants;
31 | import android.view.View;
32 | import android.view.ViewConfiguration;
33 | import android.view.accessibility.AccessibilityEvent;
34 | import android.widget.AbsListView;
35 | import android.widget.HeaderViewListAdapter;
36 | import android.widget.ListAdapter;
37 | import android.widget.ListView;
38 | import android.widget.SectionIndexer;
39 |
40 | import com.github.mzule.androidweekly.BuildConfig;
41 |
42 |
43 | /**
44 | * ListView, which is capable to pin section views at its top while the rest is still scrolled.
45 | */
46 | public class PinnedSectionListView extends ListView {
47 |
48 | //-- inner classes
49 |
50 | // fields used for handling touch events
51 | private final Rect mTouchRect = new Rect();
52 | private final PointF mTouchPoint = new PointF();
53 |
54 | //-- class fields
55 | /**
56 | * Delegating listener, can be null.
57 | */
58 | OnScrollListener mDelegateOnScrollListener;
59 | /**
60 | * Shadow for being recycled, can be null.
61 | */
62 | PinnedSection mRecycleSection;
63 | /**
64 | * shadow instance with a pinned view, can be null.
65 | */
66 | PinnedSection mPinnedSection;
67 | /**
68 | * Pinned view Y-translation. We use it to stick pinned view to the next section.
69 | */
70 | int mTranslateY;
71 | private int mTouchSlop;
72 | private View mTouchTarget;
73 | private MotionEvent mDownEvent;
74 | // fields used for drawing shadow under a pinned section
75 | private GradientDrawable mShadowDrawable;
76 | private int mSectionsDistanceY;
77 | /**
78 | * Scroll listener which does the magic
79 | */
80 | private final OnScrollListener mOnScrollListener = new OnScrollListener() {
81 |
82 | @Override
83 | public void onScrollStateChanged(AbsListView view, int scrollState) {
84 | if (mDelegateOnScrollListener != null) { // delegate
85 | mDelegateOnScrollListener.onScrollStateChanged(view, scrollState);
86 | }
87 | }
88 |
89 | @Override
90 | public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
91 |
92 | if (mDelegateOnScrollListener != null) { // delegate
93 | mDelegateOnScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
94 | }
95 |
96 | // get expected adapter or fail fast
97 | ListAdapter adapter = getAdapter();
98 | if (adapter == null || visibleItemCount == 0) return; // nothing to do
99 |
100 | final boolean isFirstVisibleItemSection =
101 | isItemViewTypePinned(adapter, adapter.getItemViewType(firstVisibleItem));
102 |
103 | if (isFirstVisibleItemSection) {
104 | View sectionView = getChildAt(0);
105 | if (sectionView.getTop() == getPaddingTop()) { // view sticks to the top, no need for pinned shadow
106 | destroyPinnedShadow();
107 | } else { // section doesn't stick to the top, make sure we have a pinned shadow
108 | ensureShadowForPosition(firstVisibleItem, firstVisibleItem, visibleItemCount);
109 | }
110 |
111 | } else { // section is not at the first visible position
112 | int sectionPosition = findCurrentSectionPosition(firstVisibleItem);
113 | if (sectionPosition > -1) { // we have section position
114 | ensureShadowForPosition(sectionPosition, firstVisibleItem, visibleItemCount);
115 | } else { // there is no section for the first visible item, destroy shadow
116 | destroyPinnedShadow();
117 | }
118 | }
119 | }
120 |
121 | ;
122 |
123 | };
124 | /**
125 | * Default change observer.
126 | */
127 | private final DataSetObserver mDataSetObserver = new DataSetObserver() {
128 | @Override
129 | public void onChanged() {
130 | recreatePinnedShadow();
131 | }
132 |
133 | ;
134 |
135 | @Override
136 | public void onInvalidated() {
137 | recreatePinnedShadow();
138 | }
139 | };
140 | private int mShadowHeight;
141 |
142 | public PinnedSectionListView(Context context, AttributeSet attrs) {
143 | super(context, attrs);
144 | initView();
145 | }
146 |
147 | public PinnedSectionListView(Context context, AttributeSet attrs, int defStyle) {
148 | super(context, attrs, defStyle);
149 | initView();
150 | }
151 |
152 | //-- constructors
153 |
154 | public static boolean isItemViewTypePinned(ListAdapter adapter, int viewType) {
155 | if (adapter instanceof HeaderViewListAdapter) {
156 | adapter = ((HeaderViewListAdapter) adapter).getWrappedAdapter();
157 | }
158 | return ((PinnedSectionListAdapter) adapter).isItemViewTypePinned(viewType);
159 | }
160 |
161 | private void initView() {
162 | setOnScrollListener(mOnScrollListener);
163 | mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
164 | initShadow(false);
165 | }
166 |
167 | public void setShadowVisible(boolean visible) {
168 | initShadow(visible);
169 | if (mPinnedSection != null) {
170 | View v = mPinnedSection.view;
171 | invalidate(v.getLeft(), v.getTop(), v.getRight(), v.getBottom() + mShadowHeight);
172 | }
173 | }
174 |
175 | //-- public API methods
176 |
177 | public void initShadow(boolean visible) {
178 | if (visible) {
179 | if (mShadowDrawable == null) {
180 | mShadowDrawable = new GradientDrawable(Orientation.TOP_BOTTOM,
181 | new int[]{Color.parseColor("#ffa0a0a0"), Color.parseColor("#50a0a0a0"), Color.parseColor("#00a0a0a0")});
182 | mShadowHeight = (int) (8 * getResources().getDisplayMetrics().density);
183 | }
184 | } else {
185 | if (mShadowDrawable != null) {
186 | mShadowDrawable = null;
187 | mShadowHeight = 0;
188 | }
189 | }
190 | }
191 |
192 | //-- pinned section drawing methods
193 |
194 | /**
195 | * Create shadow wrapper with a pinned view for a view at given position
196 | */
197 | void createPinnedShadow(int position) {
198 |
199 | // try to recycle shadow
200 | PinnedSection pinnedShadow = mRecycleSection;
201 | mRecycleSection = null;
202 |
203 | // create new shadow, if needed
204 | if (pinnedShadow == null) pinnedShadow = new PinnedSection();
205 | // request new view using recycled view, if such
206 | View pinnedView = getAdapter().getView(position, pinnedShadow.view, PinnedSectionListView.this);
207 |
208 | // read layout parameters
209 | LayoutParams layoutParams = (LayoutParams) pinnedView.getLayoutParams();
210 | if (layoutParams == null) {
211 | layoutParams = (LayoutParams) generateDefaultLayoutParams();
212 | pinnedView.setLayoutParams(layoutParams);
213 | }
214 |
215 | int heightMode = MeasureSpec.getMode(layoutParams.height);
216 | int heightSize = MeasureSpec.getSize(layoutParams.height);
217 |
218 | if (heightMode == MeasureSpec.UNSPECIFIED) heightMode = MeasureSpec.EXACTLY;
219 |
220 | int maxHeight = getHeight() - getListPaddingTop() - getListPaddingBottom();
221 | if (heightSize > maxHeight) heightSize = maxHeight;
222 |
223 | // measure & layout
224 | int ws = MeasureSpec.makeMeasureSpec(getWidth() - getListPaddingLeft() - getListPaddingRight(), MeasureSpec.EXACTLY);
225 | int hs = MeasureSpec.makeMeasureSpec(heightSize, heightMode);
226 | pinnedView.measure(ws, hs);
227 | pinnedView.layout(0, 0, pinnedView.getMeasuredWidth(), pinnedView.getMeasuredHeight());
228 | mTranslateY = 0;
229 |
230 | // initialize pinned shadow
231 | pinnedShadow.view = pinnedView;
232 | pinnedShadow.position = position;
233 | pinnedShadow.id = getAdapter().getItemId(position);
234 |
235 | // store pinned shadow
236 | mPinnedSection = pinnedShadow;
237 | }
238 |
239 | /**
240 | * Destroy shadow wrapper for currently pinned view
241 | */
242 | void destroyPinnedShadow() {
243 | if (mPinnedSection != null) {
244 | // keep shadow for being recycled later
245 | mRecycleSection = mPinnedSection;
246 | mPinnedSection = null;
247 | }
248 | }
249 |
250 | /**
251 | * Makes sure we have an actual pinned shadow for given position.
252 | */
253 | void ensureShadowForPosition(int sectionPosition, int firstVisibleItem, int visibleItemCount) {
254 | if (visibleItemCount < 2) { // no need for creating shadow at all, we have a single visible item
255 | destroyPinnedShadow();
256 | return;
257 | }
258 |
259 | if (mPinnedSection != null
260 | && mPinnedSection.position != sectionPosition) { // invalidate shadow, if required
261 | destroyPinnedShadow();
262 | }
263 |
264 | if (mPinnedSection == null) { // create shadow, if empty
265 | createPinnedShadow(sectionPosition);
266 | }
267 |
268 | // align shadow according to next section position, if needed
269 | int nextPosition = sectionPosition + 1;
270 | if (nextPosition < getCount()) {
271 | int nextSectionPosition = findFirstVisibleSectionPosition(nextPosition,
272 | visibleItemCount - (nextPosition - firstVisibleItem));
273 | if (nextSectionPosition > -1) {
274 | View nextSectionView = getChildAt(nextSectionPosition - firstVisibleItem);
275 | final int bottom = mPinnedSection.view.getBottom() + getPaddingTop();
276 | mSectionsDistanceY = nextSectionView.getTop() - bottom;
277 | if (mSectionsDistanceY < 0) {
278 | // next section overlaps pinned shadow, move it up
279 | mTranslateY = mSectionsDistanceY;
280 | } else {
281 | // next section does not overlap with pinned, stick to top
282 | mTranslateY = 0;
283 | }
284 | } else {
285 | // no other sections are visible, stick to top
286 | mTranslateY = 0;
287 | mSectionsDistanceY = Integer.MAX_VALUE;
288 | }
289 | }
290 |
291 | }
292 |
293 | int findFirstVisibleSectionPosition(int firstVisibleItem, int visibleItemCount) {
294 | ListAdapter adapter = getAdapter();
295 |
296 | int adapterDataCount = adapter.getCount();
297 | if (getLastVisiblePosition() >= adapterDataCount)
298 | return -1; // dataset has changed, no candidate
299 |
300 | if (firstVisibleItem + visibleItemCount >= adapterDataCount) {//added to prevent index Outofbound (in case)
301 | visibleItemCount = adapterDataCount - firstVisibleItem;
302 | }
303 |
304 | for (int childIndex = 0; childIndex < visibleItemCount; childIndex++) {
305 | int position = firstVisibleItem + childIndex;
306 | int viewType = adapter.getItemViewType(position);
307 | if (isItemViewTypePinned(adapter, viewType)) return position;
308 | }
309 | return -1;
310 | }
311 |
312 | int findCurrentSectionPosition(int fromPosition) {
313 | ListAdapter adapter = getAdapter();
314 |
315 | if (fromPosition >= adapter.getCount()) return -1; // dataset has changed, no candidate
316 |
317 | if (adapter instanceof SectionIndexer) {
318 | // try fast way by asking section indexer
319 | SectionIndexer indexer = (SectionIndexer) adapter;
320 | int sectionPosition = indexer.getSectionForPosition(fromPosition);
321 | int itemPosition = indexer.getPositionForSection(sectionPosition);
322 | int typeView = adapter.getItemViewType(itemPosition);
323 | if (isItemViewTypePinned(adapter, typeView)) {
324 | return itemPosition;
325 | } // else, no luck
326 | }
327 |
328 | // try slow way by looking through to the next section item above
329 | for (int position = fromPosition; position >= 0; position--) {
330 | int viewType = adapter.getItemViewType(position);
331 | if (isItemViewTypePinned(adapter, viewType)) return position;
332 | }
333 | return -1; // no candidate found
334 | }
335 |
336 | void recreatePinnedShadow() {
337 | destroyPinnedShadow();
338 | ListAdapter adapter = getAdapter();
339 | if (adapter != null && adapter.getCount() > 0) {
340 | int firstVisiblePosition = getFirstVisiblePosition();
341 | int sectionPosition = findCurrentSectionPosition(firstVisiblePosition);
342 | if (sectionPosition == -1) return; // no views to pin, exit
343 | ensureShadowForPosition(sectionPosition,
344 | firstVisiblePosition, getLastVisiblePosition() - firstVisiblePosition);
345 | }
346 | }
347 |
348 | @Override
349 | public void setOnScrollListener(OnScrollListener listener) {
350 | if (listener == mOnScrollListener) {
351 | super.setOnScrollListener(listener);
352 | } else {
353 | mDelegateOnScrollListener = listener;
354 | }
355 | }
356 |
357 | @Override
358 | public void onRestoreInstanceState(Parcelable state) {
359 | super.onRestoreInstanceState(state);
360 | post(new Runnable() {
361 | @Override
362 | public void run() { // restore pinned view after configuration change
363 | recreatePinnedShadow();
364 | }
365 | });
366 | }
367 |
368 | @Override
369 | public void setAdapter(ListAdapter adapter) {
370 |
371 | // assert adapter in debug mode
372 | if (BuildConfig.DEBUG && adapter != null) {
373 | if (!(adapter instanceof PinnedSectionListAdapter))
374 | throw new IllegalArgumentException("Does your adapter implement PinnedSectionListAdapter?");
375 | if (adapter.getViewTypeCount() < 2)
376 | throw new IllegalArgumentException("Does your adapter handle at least two types" +
377 | " of views in getViewTypeCount() method: items and sections?");
378 | }
379 |
380 | // unregister observer at old adapter and register on new one
381 | ListAdapter oldAdapter = getAdapter();
382 | if (oldAdapter != null) oldAdapter.unregisterDataSetObserver(mDataSetObserver);
383 | if (adapter != null) adapter.registerDataSetObserver(mDataSetObserver);
384 |
385 | // destroy pinned shadow, if new adapter is not same as old one
386 | if (oldAdapter != adapter) destroyPinnedShadow();
387 |
388 | super.setAdapter(adapter);
389 | }
390 |
391 | @Override
392 | protected void onLayout(boolean changed, int l, int t, int r, int b) {
393 | super.onLayout(changed, l, t, r, b);
394 | if (mPinnedSection != null) {
395 | int parentWidth = r - l - getPaddingLeft() - getPaddingRight();
396 | int shadowWidth = mPinnedSection.view.getWidth();
397 | if (parentWidth != shadowWidth) {
398 | recreatePinnedShadow();
399 | }
400 | }
401 | }
402 |
403 | @Override
404 | protected void dispatchDraw(Canvas canvas) {
405 | super.dispatchDraw(canvas);
406 |
407 | if (mPinnedSection != null) {
408 |
409 | // prepare variables
410 | int pLeft = getListPaddingLeft();
411 | int pTop = getListPaddingTop();
412 | View view = mPinnedSection.view;
413 |
414 | // draw child
415 | canvas.save();
416 |
417 | int clipHeight = view.getHeight() +
418 | (mShadowDrawable == null ? 0 : Math.min(mShadowHeight, mSectionsDistanceY));
419 | canvas.clipRect(pLeft, pTop, pLeft + view.getWidth(), pTop + clipHeight);
420 |
421 | canvas.translate(pLeft, pTop + mTranslateY);
422 | drawChild(canvas, mPinnedSection.view, getDrawingTime());
423 |
424 | if (mShadowDrawable != null && mSectionsDistanceY > 0) {
425 | mShadowDrawable.setBounds(mPinnedSection.view.getLeft(),
426 | mPinnedSection.view.getBottom(),
427 | mPinnedSection.view.getRight(),
428 | mPinnedSection.view.getBottom() + mShadowHeight);
429 | mShadowDrawable.draw(canvas);
430 | }
431 |
432 | canvas.restore();
433 | }
434 | }
435 |
436 | @Override
437 | public boolean dispatchTouchEvent(MotionEvent ev) {
438 |
439 | final float x = ev.getX();
440 | final float y = ev.getY();
441 | final int action = ev.getAction();
442 |
443 | if (action == MotionEvent.ACTION_DOWN
444 | && mTouchTarget == null
445 | && mPinnedSection != null
446 | && isPinnedViewTouched(mPinnedSection.view, x, y)) { // create touch target
447 |
448 | // user touched pinned view
449 | mTouchTarget = mPinnedSection.view;
450 | mTouchPoint.x = x;
451 | mTouchPoint.y = y;
452 |
453 | // copy down event for eventually be used later
454 | mDownEvent = MotionEvent.obtain(ev);
455 | }
456 |
457 | if (mTouchTarget != null) {
458 | if (isPinnedViewTouched(mTouchTarget, x, y)) { // forward event to pinned view
459 | mTouchTarget.dispatchTouchEvent(ev);
460 | }
461 |
462 | if (action == MotionEvent.ACTION_UP) { // perform onClick on pinned view
463 | super.dispatchTouchEvent(ev);
464 | performPinnedItemClick();
465 | clearTouchTarget();
466 |
467 | } else if (action == MotionEvent.ACTION_CANCEL) { // cancel
468 | clearTouchTarget();
469 |
470 | } else if (action == MotionEvent.ACTION_MOVE) {
471 | if (Math.abs(y - mTouchPoint.y) > mTouchSlop) {
472 |
473 | // cancel sequence on touch target
474 | MotionEvent event = MotionEvent.obtain(ev);
475 | event.setAction(MotionEvent.ACTION_CANCEL);
476 | mTouchTarget.dispatchTouchEvent(event);
477 | event.recycle();
478 |
479 | // provide correct sequence to super class for further handling
480 | super.dispatchTouchEvent(mDownEvent);
481 | super.dispatchTouchEvent(ev);
482 | clearTouchTarget();
483 |
484 | }
485 | }
486 |
487 | return true;
488 | }
489 |
490 | // call super if this was not our pinned view
491 | return super.dispatchTouchEvent(ev);
492 | }
493 |
494 | //-- touch handling methods
495 |
496 | private boolean isPinnedViewTouched(View view, float x, float y) {
497 | view.getHitRect(mTouchRect);
498 |
499 | // by taping top or bottom padding, the list performs on click on a border item.
500 | // we don't add top padding here to keep behavior consistent.
501 | mTouchRect.top += mTranslateY;
502 |
503 | mTouchRect.bottom += mTranslateY + getPaddingTop();
504 | mTouchRect.left += getPaddingLeft();
505 | mTouchRect.right -= getPaddingRight();
506 | return mTouchRect.contains((int) x, (int) y);
507 | }
508 |
509 | private void clearTouchTarget() {
510 | mTouchTarget = null;
511 | if (mDownEvent != null) {
512 | mDownEvent.recycle();
513 | mDownEvent = null;
514 | }
515 | }
516 |
517 | private boolean performPinnedItemClick() {
518 | if (mPinnedSection == null) return false;
519 |
520 | OnItemClickListener listener = getOnItemClickListener();
521 | if (listener != null && getAdapter().isEnabled(mPinnedSection.position)) {
522 | View view = mPinnedSection.view;
523 | playSoundEffect(SoundEffectConstants.CLICK);
524 | if (view != null) {
525 | view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
526 | }
527 | listener.onItemClick(this, view, mPinnedSection.position, mPinnedSection.id);
528 | return true;
529 | }
530 | return false;
531 | }
532 |
533 | /**
534 | * List adapter to be implemented for being used with PinnedSectionListView adapter.
535 | */
536 | public static interface PinnedSectionListAdapter extends ListAdapter {
537 | /**
538 | * This method shall return 'true' if views of given type has to be pinned.
539 | */
540 | boolean isItemViewTypePinned(int viewType);
541 | }
542 |
543 | /**
544 | * Wrapper class for pinned section view and its position in the list.
545 | */
546 | static class PinnedSection {
547 | public View view;
548 | public int position;
549 | public long id;
550 | }
551 |
552 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/mzule/androidweekly/ui/view/PopupView.java:
--------------------------------------------------------------------------------
1 | package com.github.mzule.androidweekly.ui.view;
2 |
3 | import android.animation.Animator;
4 | import android.animation.AnimatorListenerAdapter;
5 | import android.animation.ObjectAnimator;
6 | import android.app.Activity;
7 | import android.content.Context;
8 | import android.view.View;
9 | import android.view.ViewGroup;
10 | import android.view.animation.AccelerateInterpolator;
11 | import android.widget.FrameLayout;
12 |
13 | import com.github.mzule.androidweekly.R;
14 | import com.github.mzule.androidweekly.ui.view.base.BaseRelativeLayout;
15 |
16 |
17 | /**
18 | * Created by CaoDongping on 11/30/15.
19 | */
20 | public abstract class PopupView extends BaseRelativeLayout {
21 | public static final int ANIMATION_DIRECTION_BOTTOM_TO_TOP = 0;
22 | public static final int ANIMATION_DIRECTION_TOP_TO_BOTTOM = 1;
23 | public static final int ANIMATION_DIRECTION_LEFT_TO_RIGHT = 2;
24 | public static final int ANIMATION_DIRECTION_RIGHT_TO_LEFT = 3;
25 | public static final int ANIMATE_DURATION = 200;
26 | private boolean inAnimation;
27 |
28 | public PopupView(Context context) {
29 | super(context, null, 0);
30 | }
31 |
32 | /**
33 | * 显示到Activity上面去
34 | *
35 | * @param activity
36 | * @param data
37 | * @return
38 | */
39 | public PopupView attachTo(Activity activity, T data) {
40 | FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
41 | return attachTo(activity, data, lp);
42 | }
43 |
44 | /**
45 | * 显示到Activity上面去
46 | *
47 | * @param activity
48 | * @param data
49 | * @param layoutParams
50 | * @return
51 | */
52 | public PopupView attachTo(Activity activity, T data, FrameLayout.LayoutParams layoutParams) {
53 | setId(R.id.popup_view_id);
54 | ViewGroup contentView = (ViewGroup) activity.findViewById(android.R.id.content);
55 | if (null != contentView.findViewById(R.id.popup_view_id)) {
56 | return this;
57 | }
58 | render(data);
59 | contentView.addView(this, layoutParams);
60 | animateIn();
61 | return this;
62 | }
63 |
64 | protected int getAnimationDirection() {
65 | return ANIMATION_DIRECTION_BOTTOM_TO_TOP;
66 | }
67 |
68 | /**
69 | * 动画显示
70 | */
71 | protected void animateIn() {
72 | if (inAnimation) {
73 | return;
74 | }
75 | setVisibility(INVISIBLE);
76 | inAnimation = true;
77 | post(new Runnable() {
78 | @Override
79 | public void run() {
80 | setVisibility(VISIBLE);
81 | ObjectAnimator alpha = ObjectAnimator.ofFloat(getMaskView(), "alpha", 0, 1);
82 | alpha.setDuration(ANIMATE_DURATION);
83 | alpha.start();
84 | int measuredHeight = getMainView().getMeasuredHeight();
85 | int measuredWidth = getMainView().getMeasuredWidth();
86 | ObjectAnimator translate;
87 | switch (getAnimationDirection()) {
88 | case ANIMATION_DIRECTION_BOTTOM_TO_TOP:
89 | translate = ObjectAnimator.ofFloat(getMainView(), "translationY", measuredHeight, 0);
90 | break;
91 | case ANIMATION_DIRECTION_TOP_TO_BOTTOM:
92 | translate = ObjectAnimator.ofFloat(getMainView(), "translationY", -measuredHeight, 0);
93 | break;
94 | case ANIMATION_DIRECTION_LEFT_TO_RIGHT:
95 | translate = ObjectAnimator.ofFloat(getMainView(), "translationX", -measuredWidth, 0);
96 | break;
97 | case ANIMATION_DIRECTION_RIGHT_TO_LEFT:
98 | translate = ObjectAnimator.ofFloat(getMainView(), "translationX", measuredWidth, 0);
99 | break;
100 | default:
101 | throw new RuntimeException("unknown animation direction");
102 | }
103 | translate.setInterpolator(new AccelerateInterpolator());
104 | translate.setDuration(ANIMATE_DURATION);
105 | translate.start();
106 | translate.addListener(new AnimatorListenerAdapter() {
107 | @Override
108 | public void onAnimationEnd(Animator animation) {
109 | inAnimation = false;
110 | }
111 | });
112 | }
113 | });
114 | }
115 |
116 | public void finish() {
117 | finish(null);
118 | }
119 |
120 | /**
121 | * 从当前Activity上移除
122 | */
123 | public void finish(final FinishCallback callback) {
124 | if (!inAnimation) {
125 | inAnimation = true;
126 | ObjectAnimator alpha = ObjectAnimator.ofFloat(getMainView(), "alpha", 1, 0);
127 | alpha.setDuration(ANIMATE_DURATION);
128 | alpha.start();
129 | int measuredHeight = getMainView().getMeasuredHeight();
130 | int measuredWidth = getMainView().getMeasuredWidth();
131 | ObjectAnimator translate;
132 | switch (getAnimationDirection()) {
133 | case ANIMATION_DIRECTION_BOTTOM_TO_TOP:
134 | translate = ObjectAnimator.ofFloat(getMainView(), "translationY", 0, measuredHeight);
135 | break;
136 | case ANIMATION_DIRECTION_TOP_TO_BOTTOM:
137 | translate = ObjectAnimator.ofFloat(getMainView(), "translationY", 0, -measuredHeight);
138 | break;
139 | case ANIMATION_DIRECTION_LEFT_TO_RIGHT:
140 | translate = ObjectAnimator.ofFloat(getMainView(), "translationX", 0, -measuredWidth);
141 | break;
142 | case ANIMATION_DIRECTION_RIGHT_TO_LEFT:
143 | translate = ObjectAnimator.ofFloat(getMainView(), "translationX", 0, measuredWidth);
144 | break;
145 | default:
146 | throw new RuntimeException("unknown animation direction");
147 | }
148 | translate.setInterpolator(new AccelerateInterpolator());
149 | translate.setDuration(ANIMATE_DURATION);
150 | translate.start();
151 | translate.addListener(new AnimatorListenerAdapter() {
152 | @Override
153 | public void onAnimationEnd(Animator animation) {
154 | if (getParent() instanceof ViewGroup) {
155 | ((ViewGroup) getParent()).removeView(PopupView.this);
156 | }
157 | inAnimation = false;
158 | if (callback != null) {
159 | callback.afterFinish();
160 | }
161 | }
162 | });
163 | }
164 | }
165 |
166 | /**
167 | * 渲染数据到UI
168 | *
169 | * @param data
170 | */
171 | protected abstract void render(T data);
172 |
173 | /**
174 | * 获取需要fadeIn显示的遮罩视图
175 | *
176 | * @return
177 | */
178 | protected abstract View getMaskView();
179 |
180 |
181 | /**
182 | * 获取需要translation的主UI视图
183 | *
184 | * @return
185 | */
186 | protected abstract View getMainView();
187 |
188 | public interface FinishCallback {
189 | void afterFinish();
190 | }
191 | }
192 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/mzule/androidweekly/ui/view/ProgressView.java:
--------------------------------------------------------------------------------
1 | package com.github.mzule.androidweekly.ui.view;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 | import android.view.View;
6 | import android.view.ViewGroup;
7 |
8 | import com.github.mzule.androidweekly.R;
9 | import com.github.mzule.androidweekly.util.DensityUtil;
10 | import com.pnikosis.materialishprogress.ProgressWheel;
11 |
12 |
13 | /**
14 | * Created by CaoDongping on 1/5/16.
15 | */
16 | public class ProgressView extends ProgressWheel {
17 | public ProgressView(Context context, AttributeSet attrs) {
18 | super(context, attrs);
19 | init();
20 | }
21 |
22 | public ProgressView(Context context) {
23 | super(context);
24 | init();
25 | }
26 |
27 | private void init() {
28 | if (isInEditMode()) {
29 | return;
30 | }
31 | setBarColor(getResources().getColor(R.color.colorPrimary));
32 | setBarWidth(DensityUtil.dp2px(2));
33 | spin();
34 | }
35 |
36 | public void start() {
37 | setVisibility(VISIBLE);
38 | }
39 |
40 | public void stop() {
41 | setVisibility(View.GONE);
42 | }
43 |
44 | @Override
45 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
46 | if (!isInEditMode() && getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT && getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT) {
47 | super.onMeasure(DensityUtil.dp2px(40), DensityUtil.dp2px(40));
48 | } else {
49 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/mzule/androidweekly/ui/view/TintStatusBar.java:
--------------------------------------------------------------------------------
1 | package com.github.mzule.androidweekly.ui.view;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 | import android.view.View;
6 | import android.view.ViewGroup;
7 |
8 | import com.github.mzule.androidweekly.util.Tinter;
9 |
10 | /**
11 | * 占位符View,在支持Tint的设备上,高度为信号栏的高度,在不支持Tint的设备上,高度为0,可以用来作为UI布局的占位符
12 | * Created by CaoDongping on 11/11/15.
13 | */
14 | public class TintStatusBar extends View {
15 | private int statusBarHeight;
16 |
17 | public TintStatusBar(Context context) {
18 | super(context);
19 | init();
20 | }
21 |
22 | public TintStatusBar(Context context, AttributeSet attrs) {
23 | super(context, attrs);
24 | init();
25 | }
26 |
27 | public TintStatusBar(Context context, AttributeSet attrs, int defStyleAttr) {
28 | super(context, attrs, defStyleAttr);
29 | init();
30 | }
31 |
32 | public void init() {
33 | statusBarHeight = getStatusBarHeight();
34 | }
35 |
36 | @Override
37 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
38 | if (getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT) {
39 | int height = 0;
40 | if (Tinter.isSupport()) {
41 | height = statusBarHeight;
42 | }
43 | heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
44 | }
45 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
46 | }
47 |
48 | public int getStatusBarHeight() {
49 | int result = 0;
50 | int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
51 | if (resourceId > 0) {
52 | result = getResources().getDimensionPixelSize(resourceId);
53 | }
54 | return result;
55 | }
56 |
57 | public void setHeightPercent(float percent) {
58 | ViewGroup.LayoutParams lp = getLayoutParams();
59 | lp.height = (int) (statusBarHeight * percent);
60 | setLayoutParams(lp);
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/mzule/androidweekly/ui/view/TranslateView.java:
--------------------------------------------------------------------------------
1 | package com.github.mzule.androidweekly.ui.view;
2 |
3 | import android.app.Activity;
4 | import android.content.ClipData;
5 | import android.content.ClipboardManager;
6 | import android.content.Context;
7 | import android.support.annotation.Nullable;
8 | import android.text.TextUtils;
9 | import android.util.Log;
10 | import android.view.View;
11 | import android.widget.EditText;
12 |
13 | import com.github.mzule.androidweekly.R;
14 | import com.github.mzule.androidweekly.api.ApiCallback;
15 | import com.github.mzule.androidweekly.api.DictionaryApi;
16 | import com.github.mzule.androidweekly.entity.Dict;
17 | import com.github.mzule.androidweekly.util.Keyboard;
18 | import com.github.mzule.layoutannotation.Layout;
19 |
20 | import butterknife.Bind;
21 | import butterknife.OnClick;
22 |
23 | /**
24 | * Created by CaoDongping on 3/30/16.
25 | */
26 | @Layout(R.layout.view_translate)
27 | public class TranslateView extends PopupView {
28 | @Bind(R.id.queryInput)
29 | EditText queryInput;
30 | @Bind(R.id.resultView)
31 | EditText resultView;
32 | @Bind(R.id.buttonPanel)
33 | View buttonPanel;
34 |
35 | public TranslateView(Context context) {
36 | super(context);
37 | }
38 |
39 | @OnClick(R.id.maskView)
40 | @Override
41 | public void finish() {
42 | super.finish();
43 | hideKeyboard();
44 | }
45 |
46 | private void hideKeyboard() {
47 | if (getContext() instanceof Activity) {
48 | Keyboard.hide((Activity) getContext());
49 | }
50 | }
51 |
52 | @OnClick(R.id.clearButton)
53 | void clear() {
54 | queryInput.setText("");
55 | resultView.setText("");
56 | queryInput.requestFocus();
57 | Keyboard.show(getContext());
58 | }
59 |
60 | @OnClick(R.id.translateButton)
61 | void translate() {
62 | new DictionaryApi().look(queryInput.getText().toString().toLowerCase(), new ApiCallback() {
63 | @Override
64 | public void onSuccess(Dict data, boolean fromCache) {
65 | resultView.setText(data.getContent());
66 | resultView.setSelection(0);
67 | hideKeyboard();
68 | updateResultViewMaxHeight();
69 | }
70 |
71 | @Override
72 | public void onFailure(Exception e) {
73 | resultView.setText(Log.getStackTraceString(e));
74 | }
75 | });
76 | }
77 |
78 | @Override
79 | protected void render(Void data) {
80 | String paste = getPasteText();
81 | queryInput.requestFocus();
82 | if (!TextUtils.isEmpty(paste)) {
83 | queryInput.setText(paste);
84 | queryInput.setSelection(paste.length());
85 | translate();
86 | }
87 | }
88 |
89 | private void updateResultViewMaxHeight() {
90 | int height = getResources().getDisplayMetrics().heightPixels;
91 | resultView.setMaxHeight(height - queryInput.getBottom() - buttonPanel.getMeasuredHeight() - queryInput.getPaddingLeft());
92 | }
93 |
94 | @Nullable
95 | private String getPasteText() {
96 | String paste = null;
97 | ClipboardManager manager = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
98 | ClipData clip = manager.getPrimaryClip();
99 | if (clip != null && clip.getItemCount() > 0 && clip.getItemAt(0) != null) {
100 | CharSequence text = clip.getItemAt(0).getText();
101 | paste = text == null ? "" : text.toString();
102 | }
103 | return paste;
104 | }
105 |
106 | @Override
107 | protected View getMaskView() {
108 | return findViewById(R.id.maskView);
109 | }
110 |
111 | @Override
112 | protected View getMainView() {
113 | return findViewById(R.id.mainView);
114 | }
115 |
116 | @Override
117 | protected int getAnimationDirection() {
118 | return ANIMATION_DIRECTION_TOP_TO_BOTTOM;
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/mzule/androidweekly/ui/view/base/BaseLinearLayout.java:
--------------------------------------------------------------------------------
1 | package com.github.mzule.androidweekly.ui.view.base;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 | import android.widget.LinearLayout;
6 |
7 | import com.github.mzule.layoutannotation.LayoutBinder;
8 |
9 | import butterknife.ButterKnife;
10 |
11 | /**
12 | * Created by CaoDongping on 8/30/16.
13 | */
14 |
15 | public class BaseLinearLayout extends LinearLayout {
16 |
17 | public BaseLinearLayout(Context context) {
18 | super(context);
19 | init(context, null);
20 | }
21 |
22 | public BaseLinearLayout(Context context, AttributeSet attrs) {
23 | super(context, attrs);
24 | init(context, attrs);
25 | }
26 |
27 | public BaseLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
28 | super(context, attrs, defStyleAttr);
29 | init(context, attrs);
30 | }
31 |
32 | protected void init(Context context, AttributeSet attrs) {
33 | LayoutBinder.bind(this);
34 | ButterKnife.bind(this);
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/mzule/androidweekly/ui/view/base/BaseRelativeLayout.java:
--------------------------------------------------------------------------------
1 | package com.github.mzule.androidweekly.ui.view.base;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 | import android.widget.RelativeLayout;
6 |
7 | import com.github.mzule.layoutannotation.LayoutBinder;
8 |
9 | import butterknife.ButterKnife;
10 |
11 | /**
12 | * Created by CaoDongping on 8/30/16.
13 | */
14 |
15 | public class BaseRelativeLayout extends RelativeLayout {
16 |
17 | public BaseRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
18 | super(context, attrs, defStyleAttr);
19 | init();
20 | }
21 |
22 | private void init() {
23 | LayoutBinder.bind(this);
24 | ButterKnife.bind(this);
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/mzule/androidweekly/ui/viewtype/ArticleViewType.java:
--------------------------------------------------------------------------------
1 | package com.github.mzule.androidweekly.ui.viewtype;
2 |
3 | import android.text.TextUtils;
4 | import android.view.View;
5 | import android.widget.ImageView;
6 | import android.widget.TextView;
7 |
8 | import com.bumptech.glide.Glide;
9 | import com.github.mzule.androidweekly.R;
10 | import com.github.mzule.androidweekly.entity.Article;
11 | import com.github.mzule.layoutannotation.Layout;
12 |
13 | import butterknife.Bind;
14 |
15 | /**
16 | * Created by CaoDongping on 3/24/16.
17 | */
18 | @Layout(R.layout.item_article)
19 | public class ArticleViewType extends BaseViewType {
20 | @Bind(R.id.nameView)
21 | TextView nameView;
22 | @Bind(R.id.briefView)
23 | TextView briefView;
24 | @Bind(R.id.domainView)
25 | TextView domainView;
26 | @Bind(R.id.iconView)
27 | ImageView iconView;
28 |
29 | @Override
30 | public void onRender(int position, Article data) {
31 | nameView.setText(data.getTitle());
32 | briefView.setText(data.getBrief());
33 | domainView.setText(data.getDomain());
34 | Glide.with(getContext()).load(data.getImageUrl()).centerCrop().placeholder(R.color.placeholder).into(iconView);
35 | iconView.setVisibility(TextUtils.isEmpty(data.getImageUrl()) ? View.GONE : View.VISIBLE);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/mzule/androidweekly/ui/viewtype/BaseViewType.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Baidu, Inc. All Rights Reserved.
3 | */
4 | package com.github.mzule.androidweekly.ui.viewtype;
5 |
6 | import com.github.mzule.easyadapter.ViewType;
7 | import com.github.mzule.layoutannotation.LayoutBinder;
8 |
9 | import butterknife.ButterKnife;
10 |
11 | /**
12 | * Created by CaoDongping on 8/29/16.
13 | */
14 |
15 | public abstract class BaseViewType extends ViewType {
16 | @Override
17 | public void onCreate() {
18 | setContentView(LayoutBinder.inflate(getContext(), this));
19 | ButterKnife.bind(this, getView());
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/mzule/androidweekly/ui/viewtype/SearchHistoryViewType.java:
--------------------------------------------------------------------------------
1 | package com.github.mzule.androidweekly.ui.viewtype;
2 |
3 | import android.widget.TextView;
4 |
5 | import com.github.mzule.androidweekly.R;
6 | import com.github.mzule.layoutannotation.Layout;
7 |
8 | import butterknife.Bind;
9 |
10 | /**
11 | * Created by CaoDongping on 4/5/16.
12 | */
13 | @Layout(R.layout.item_search_history)
14 | public class SearchHistoryViewType extends BaseViewType {
15 | @Bind(R.id.textView)
16 | TextView textView;
17 |
18 | @Override
19 | public void onRender(int position, String data) {
20 | textView.setText(data);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/mzule/androidweekly/ui/viewtype/SectionViewType.java:
--------------------------------------------------------------------------------
1 | package com.github.mzule.androidweekly.ui.viewtype;
2 |
3 | import android.widget.TextView;
4 |
5 | import com.github.mzule.androidweekly.R;
6 | import com.github.mzule.layoutannotation.Layout;
7 |
8 | import butterknife.Bind;
9 |
10 | /**
11 | * Created by CaoDongping on 3/24/16.
12 | */
13 | @Layout(R.layout.item_section)
14 | public class SectionViewType extends BaseViewType {
15 | @Bind(R.id.sectionName)
16 | TextView sectionName;
17 |
18 | @Override
19 | public void onRender(int position, String data) {
20 | sectionName.setText(data);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/mzule/androidweekly/ui/viewtype/SlideIssueViewType.java:
--------------------------------------------------------------------------------
1 | package com.github.mzule.androidweekly.ui.viewtype;
2 |
3 | import android.view.View;
4 | import android.widget.TextView;
5 |
6 | import com.github.mzule.androidweekly.R;
7 | import com.github.mzule.androidweekly.entity.Issue;
8 | import com.github.mzule.layoutannotation.Layout;
9 |
10 | import butterknife.Bind;
11 |
12 | /**
13 | * Created by CaoDongping on 3/25/16.
14 | */
15 | @Layout(R.layout.item_slide_issue)
16 | public class SlideIssueViewType extends BaseViewType {
17 | @Bind(R.id.nameView)
18 | TextView nameView;
19 | @Bind(R.id.dateView)
20 | TextView dateView;
21 | @Bind(R.id.rootLayout)
22 | View rootLayout;
23 |
24 | @Override
25 | public void onRender(int position, Issue data) {
26 | nameView.setText(data.getName());
27 | nameView.setSelected(data.isActive());
28 | dateView.setText(data.getDate());
29 | dateView.setSelected(data.isActive());
30 | int color = getContext().getResources().getColor(data.isActive() ? R.color.colorPrimary : R.color.transparent);
31 | rootLayout.setBackgroundColor(color);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/mzule/androidweekly/util/DateUtil.java:
--------------------------------------------------------------------------------
1 | package com.github.mzule.androidweekly.util;
2 |
3 | import android.annotation.SuppressLint;
4 |
5 | import java.text.DateFormat;
6 | import java.text.SimpleDateFormat;
7 |
8 | /**
9 | * Created by CaoDongping on 3/28/16.
10 | */
11 | public class DateUtil {
12 | @SuppressLint("SimpleDateFormat")
13 | private static final DateFormat format = new SimpleDateFormat("yyyy-MM-dd");
14 |
15 | public static String format(long time) {
16 | return format.format(time);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/mzule/androidweekly/util/DensityUtil.java:
--------------------------------------------------------------------------------
1 | package com.github.mzule.androidweekly.util;
2 |
3 |
4 | import com.github.mzule.androidweekly.App;
5 |
6 | /**
7 | * Created by CaoDongping on 11/4/15.
8 | */
9 | public class DensityUtil {
10 | public static int dp2px(float dp) {
11 | float density = App.getInstance().getResources().getDisplayMetrics().density;
12 | return (int) (dp * density + 0.5f);
13 | }
14 |
15 | public static float px2dp(float px) {
16 | float density = App.getInstance().getResources().getDisplayMetrics().density;
17 | return px / density;
18 | }
19 |
20 | public static int screenWidth() {
21 | return App.getInstance().getResources().getDisplayMetrics().widthPixels;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/mzule/androidweekly/util/DictParser.java:
--------------------------------------------------------------------------------
1 | package com.github.mzule.androidweekly.util;
2 |
3 | import android.util.Xml;
4 |
5 | import com.github.mzule.androidweekly.entity.Dict;
6 |
7 | import org.xmlpull.v1.XmlPullParser;
8 | import org.xmlpull.v1.XmlPullParserException;
9 |
10 | import java.io.IOException;
11 | import java.net.URL;
12 |
13 | /**
14 | * Created by CaoDongping on 3/31/16.
15 | */
16 | public class DictParser {
17 | private Dict dict;
18 | private String url;
19 |
20 | public DictParser(String url) {
21 | this.url = url;
22 | }
23 |
24 | public Dict parse() throws IOException, XmlPullParserException {
25 | XmlPullParser parser = Xml.newPullParser();
26 | parser.setInput(new URL(url).openStream(), null);
27 | parser.nextTag();
28 | dict = new Dict();
29 | readDict(parser);
30 | return dict;
31 | }
32 |
33 | private void readDict(XmlPullParser parser) throws IOException, XmlPullParserException {
34 | parser.require(XmlPullParser.START_TAG, null, "dict");
35 | while (parser.next() != XmlPullParser.END_TAG) {
36 | if (parser.getEventType() != XmlPullParser.START_TAG) {
37 | continue;
38 | }
39 | String name = parser.getName();
40 | switch (name) {
41 | case "key":
42 | dict.setKey(readText(parser));
43 | break;
44 | case "ps":
45 | dict.addPs(readText(parser));
46 | break;
47 | case "pron":
48 | dict.addPron(readText(parser));
49 | break;
50 | case "pos":
51 | dict.addPos(readText(parser));
52 | break;
53 | case "acceptation":
54 | dict.addAcceptation(readText(parser));
55 | break;
56 | case "fy":
57 | dict.setFy(readText(parser));
58 | break;
59 | case "sent":
60 | parseSent(parser);
61 | break;
62 |
63 | }
64 | }
65 | }
66 |
67 | private void parseSent(XmlPullParser parser) throws IOException, XmlPullParserException {
68 | Dict.Sent sent = new Dict.Sent();
69 | parser.require(XmlPullParser.START_TAG, null, "sent");
70 | while (parser.next() != XmlPullParser.END_TAG) {
71 | if (parser.getEventType() != XmlPullParser.START_TAG) {
72 | continue;
73 | }
74 | String name = parser.getName();
75 | switch (name) {
76 | case "orig":
77 | sent.setOrig(readText(parser));
78 | break;
79 | case "trans":
80 | sent.setTrans(readText(parser));
81 | break;
82 | }
83 | }
84 | dict.addSent(sent);
85 | }
86 |
87 |
88 | private String readText(XmlPullParser parser) throws IOException, XmlPullParserException {
89 | String result = null;
90 | if (parser.next() == XmlPullParser.TEXT) {
91 | result = parser.getText();
92 | parser.nextTag();
93 | }
94 | return result == null ? null : result.trim();
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/mzule/androidweekly/util/IOUtil.java:
--------------------------------------------------------------------------------
1 | package com.github.mzule.androidweekly.util;
2 |
3 | import java.io.BufferedReader;
4 | import java.io.Closeable;
5 | import java.io.File;
6 | import java.io.FileInputStream;
7 | import java.io.IOException;
8 | import java.io.InputStreamReader;
9 |
10 | /**
11 | * Created by mzule on 4/5/16.
12 | */
13 | public class IOUtil {
14 | public static void close(Closeable... closeables) {
15 | if (closeables == null) {
16 | return;
17 | }
18 | for (Closeable closeable : closeables) {
19 | if (closeable != null) {
20 | try {
21 | closeable.close();
22 | } catch (IOException e) {
23 | e.printStackTrace();
24 | }
25 | }
26 | }
27 | }
28 |
29 | public static void close(AutoCloseable... closeables) {
30 | if (closeables == null) {
31 | return;
32 | }
33 | for (AutoCloseable closeable : closeables) {
34 | if (closeable != null) {
35 | try {
36 | closeable.close();
37 | } catch (Exception e) {
38 | e.printStackTrace();
39 | }
40 | }
41 | }
42 | }
43 |
44 | public static String read(File file) throws IOException {
45 | StringBuilder sb = new StringBuilder();
46 | BufferedReader br = null;
47 | try {
48 | br = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
49 | String line;
50 | while ((line = br.readLine()) != null) {
51 | sb.append(line);
52 | }
53 | } finally {
54 | close(br);
55 | }
56 | return sb.toString();
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/mzule/androidweekly/util/JsonUtil.java:
--------------------------------------------------------------------------------
1 | package com.github.mzule.androidweekly.util;
2 |
3 | import android.support.annotation.WorkerThread;
4 |
5 | import com.google.gson.Gson;
6 |
7 | import java.io.BufferedReader;
8 | import java.io.InputStreamReader;
9 | import java.lang.reflect.Type;
10 | import java.net.URL;
11 |
12 | /**
13 | * Created by CaoDongping on 3/26/16.
14 | */
15 | public class JsonUtil {
16 | private static final Gson gson = new Gson();
17 |
18 | public static String toJson(Object obj) {
19 | try {
20 | return gson.toJson(obj);
21 | } catch (Throwable e) {
22 | return "";
23 | }
24 | }
25 |
26 | public static T fromJson(String json, Class cls) {
27 | try {
28 | return gson.fromJson(json, cls);
29 | } catch (Throwable e) {
30 | return null;
31 | }
32 | }
33 |
34 | public static T fromJson(String json, Type type) {
35 | try {
36 | return gson.fromJson(json, type);
37 | } catch (Throwable e) {
38 | return null;
39 | }
40 | }
41 |
42 | @WorkerThread
43 | public static T fromJson(URL url, Class cls) {
44 | try {
45 | return gson.fromJson(new BufferedReader(new InputStreamReader(url.openConnection().getInputStream())), cls);
46 | } catch (Throwable e) {
47 | return null;
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/mzule/androidweekly/util/Keyboard.java:
--------------------------------------------------------------------------------
1 | package com.github.mzule.androidweekly.util;
2 |
3 | import android.app.Activity;
4 | import android.content.Context;
5 | import android.view.View;
6 | import android.view.inputmethod.InputMethodManager;
7 |
8 | /**
9 | * Created by CaoDongping on 12/21/15.
10 | */
11 | public class Keyboard {
12 | public static void show(Context context) {
13 | InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
14 | imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
15 | }
16 |
17 | public static void hide(Activity activity) {
18 | View view = activity.getCurrentFocus();
19 | if (view != null) {
20 | InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
21 | imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/mzule/androidweekly/util/MD5.java:
--------------------------------------------------------------------------------
1 | package com.github.mzule.androidweekly.util;
2 |
3 | import java.security.MessageDigest;
4 | import java.security.NoSuchAlgorithmException;
5 |
6 | /**
7 | * Created by CaoDongping on 1/4/16.
8 | */
9 | public class MD5 {
10 |
11 | public static String encode(final String s) {
12 | final String MD5 = "MD5";
13 | try {
14 | // Create MD5 Hash
15 | MessageDigest digest = MessageDigest.getInstance(MD5);
16 | digest.update(s.getBytes());
17 | byte messageDigest[] = digest.digest();
18 | // Create Hex String
19 | StringBuilder hexString = new StringBuilder();
20 | for (byte aMessageDigest : messageDigest) {
21 | String h = Integer.toHexString(0xFF & aMessageDigest);
22 | while (h.length() < 2) {
23 | h = "0" + h;
24 | }
25 | hexString.append(h);
26 | }
27 | return hexString.toString();
28 |
29 | } catch (NoSuchAlgorithmException e) {
30 | e.printStackTrace();
31 | }
32 | return "";
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/mzule/androidweekly/util/StemUtil.java:
--------------------------------------------------------------------------------
1 | package com.github.mzule.androidweekly.util;
2 |
3 | import org.tartarus.snowball.ext.EnglishStem;
4 |
5 | /**
6 | * Created by CaoDongping on 4/2/16.
7 | */
8 | public class StemUtil {
9 | public static String stem(String input) {
10 | String[] words = input.replaceAll("[^a-zA-Z ]", " ").toLowerCase().split("\\s+");
11 | StringBuilder sb = new StringBuilder();
12 | EnglishStem stem = new EnglishStem();
13 | for (String w : words) {
14 | stem.setCurrent(w);
15 | stem.stem();
16 | sb.append(stem.getCurrent()).append(" ");
17 | }
18 | return sb.toString();
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/mzule/androidweekly/util/ThreadUtil.java:
--------------------------------------------------------------------------------
1 | package com.github.mzule.androidweekly.util;
2 |
3 | /**
4 | * Created by CaoDongping on 1/12/16.
5 | */
6 | public class ThreadUtil {
7 | public static void sleepUntilNextDraw(long startTime) {
8 | int cost = (int) (System.currentTimeMillis() - startTime);
9 | if (cost < 16) {
10 | try {
11 | Thread.sleep(16 - cost);
12 | } catch (InterruptedException e) {
13 | e.printStackTrace();
14 | }
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/mzule/androidweekly/util/Tinter.java:
--------------------------------------------------------------------------------
1 | package com.github.mzule.androidweekly.util;
2 |
3 | import android.annotation.TargetApi;
4 | import android.app.Activity;
5 | import android.os.Build;
6 | import android.view.Window;
7 | import android.view.WindowManager;
8 |
9 | import java.lang.reflect.Field;
10 | import java.lang.reflect.Method;
11 |
12 | /**
13 | *
14 | * 设置Activity的信号栏为透明风格的工具类
15 | *
16 | * Created by CaoDongping on 9/17/15.
17 | */
18 | public class Tinter {
19 |
20 | /**
21 | * 如果当前系统版本支持Tint,则开启,否则,不作任何事情.
22 | *
23 | * @param activity
24 | */
25 | public static void enableIfSupport(Activity activity) {
26 | if (isSupport()) {
27 | tint(activity, true);
28 | }
29 | }
30 |
31 | /**
32 | * 当前系统版本是否支持Tint
33 | *
34 | * @return
35 | */
36 | public static boolean isSupport() {
37 | return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
38 | }
39 |
40 | @TargetApi(19)
41 | private static void tint(Activity activity, boolean on) {
42 | Window win = activity.getWindow();
43 | WindowManager.LayoutParams winParams = win.getAttributes();
44 | final int bits = WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
45 | if (on) {
46 | winParams.flags |= bits;
47 | } else {
48 | winParams.flags &= ~bits;
49 | }
50 | win.setAttributes(winParams);
51 |
52 | Class extends Window> clazz = win.getClass();
53 | try {
54 | Class> layoutParams = Class.forName("android.view.MiuiWindowManager$LayoutParams");
55 | Field field = layoutParams.getField("EXTRA_FLAG_STATUS_BAR_DARK_MODE");
56 | int darkModeFlag = field.getInt(layoutParams);
57 | Method extraFlagField = clazz.getMethod("setExtraFlags", int.class, int.class);
58 | extraFlagField.invoke(win, darkModeFlag, darkModeFlag);
59 | } catch (Throwable e) {
60 | }
61 |
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/slide_in_left.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/slide_in_right.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/slide_out_left.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/slide_out_right.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/color/text_color_slide_date.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/color/text_color_slide_issue.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mzule/AndroidWeekly/f4446585472d21224ea439e39a7d1362dcabd49b/app/src/main/res/drawable-hdpi/ic_logo.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_slide_menu_n.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mzule/AndroidWeekly/f4446585472d21224ea439e39a7d1362dcabd49b/app/src/main/res/drawable-xxhdpi/ic_slide_menu_n.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_slide_menu_s.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mzule/AndroidWeekly/f4446585472d21224ea439e39a7d1362dcabd49b/app/src/main/res/drawable-xxhdpi/ic_slide_menu_s.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bg_button.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bg_button_n.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bg_button_s.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bg_dialog.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_slide_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_about.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_article.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
11 |
12 |
17 |
21 |
22 |
26 |
27 |
30 |
31 |
36 |
37 |
38 |
39 |
44 |
45 |
48 |
49 |
50 |
57 |
58 |
59 |
60 |
64 |
65 |
66 |
67 |
71 |
72 |
73 |
74 |
78 |
79 |
80 |
81 |
85 |
86 |
87 |
88 |
92 |
93 |
94 |
95 |
99 |
100 |
101 |
102 |
103 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_favorite.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
12 |
13 |
18 |
19 |
25 |
26 |
27 |
34 |
35 |
36 |
37 |
41 |
42 |
43 |
44 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
11 |
12 |
17 |
18 |
25 |
26 |
33 |
34 |
42 |
43 |
44 |
49 |
50 |
56 |
57 |
64 |
65 |
70 |
71 |
72 |
79 |
80 |
83 |
84 |
88 |
89 |
97 |
98 |
102 |
103 |
107 |
108 |
112 |
113 |
117 |
118 |
122 |
123 |
124 |
125 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_search.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
11 |
12 |
16 |
17 |
21 |
22 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
47 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_search_result.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_article.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
11 |
12 |
19 |
20 |
27 |
28 |
39 |
40 |
49 |
50 |
51 |
56 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_search_history.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_section.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
16 |
17 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_slide_issue.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
18 |
19 |
28 |
29 |
34 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/view_navi_bar.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
12 |
13 |
16 |
17 |
23 |
24 |
32 |
33 |
42 |
43 |
44 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/view_translate.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
11 |
12 |
21 |
22 |
26 |
27 |
41 |
42 |
45 |
46 |
59 |
60 |
63 |
64 |
69 |
70 |
80 |
81 |
82 |
83 |
93 |
94 |
95 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mzule/AndroidWeekly/f4446585472d21224ea439e39a7d1362dcabd49b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #36bce8
4 | #303F9F
5 | #464646
6 | #e1e1e1
7 | #d9d9d9
8 | #00000000
9 | #fcfcfc
10 | #ffffff
11 | #ddffffff
12 | #666666
13 | #999999
14 | #CCCCCC
15 | #22000000
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 | 10sp
3 | 12sp
4 | 14sp
5 | 16sp
6 | 12dp
7 | 6dp
8 | 4dp
9 | 40dp
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/values/ids.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Weekly
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
21 |
22 |
30 |
31 |
39 |
40 |
48 |
49 |
54 |
55 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/app/src/test/java/com/github/mzule/androidweekly/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.github.mzule.androidweekly;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * To work on unit tests, switch the Test Artifact in the Build Variants view.
9 | */
10 | public class ExampleUnitTest {
11 | @Test
12 | public void addition_isCorrect() throws Exception {
13 | assertEquals(4, 2 + 2);
14 | }
15 | }
--------------------------------------------------------------------------------
/art/article.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mzule/AndroidWeekly/f4446585472d21224ea439e39a7d1362dcabd49b/art/article.jpg
--------------------------------------------------------------------------------
/art/favorite.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mzule/AndroidWeekly/f4446585472d21224ea439e39a7d1362dcabd49b/art/favorite.jpg
--------------------------------------------------------------------------------
/art/home.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mzule/AndroidWeekly/f4446585472d21224ea439e39a7d1362dcabd49b/art/home.jpg
--------------------------------------------------------------------------------
/art/issue.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mzule/AndroidWeekly/f4446585472d21224ea439e39a7d1362dcabd49b/art/issue.jpg
--------------------------------------------------------------------------------
/art/search.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mzule/AndroidWeekly/f4446585472d21224ea439e39a7d1362dcabd49b/art/search.jpg
--------------------------------------------------------------------------------
/art/translation.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mzule/AndroidWeekly/f4446585472d21224ea439e39a7d1362dcabd49b/art/translation.gif
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:2.1.0'
9 | classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
10 | }
11 | }
12 |
13 | allprojects {
14 | repositories {
15 | jcenter()
16 | }
17 | }
18 |
19 | task clean(type: Delete) {
20 | delete rootProject.buildDir
21 | }
22 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | ## Project-wide Gradle settings.
2 | #
3 | # For more details on how to configure your build environment visit
4 | # http://www.gradle.org/docs/current/userguide/build_environment.html
5 | #
6 | # Specifies the JVM arguments used for the daemon process.
7 | # The setting is particularly useful for tweaking memory settings.
8 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
9 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
10 | #
11 | # When configured, Gradle will run in incubating parallel mode.
12 | # This option should only be used with decoupled projects. More details, visit
13 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
14 | # org.gradle.parallel=true
15 | #Thu May 12 10:00:05 CST 2016
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mzule/AndroidWeekly/f4446585472d21224ea439e39a7d1362dcabd49b/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sat Apr 09 16:59:39 CST 2016
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':snowball'
2 |
--------------------------------------------------------------------------------
/snowball/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/snowball/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'java'
2 |
3 | sourceCompatibility = 1.7
4 | targetCompatibility = 1.7
5 |
6 | dependencies {
7 | compile fileTree(dir: 'libs', include: ['*.jar'])
8 | }
--------------------------------------------------------------------------------
/snowball/src/main/java/org/tartarus/snowball/Among.java:
--------------------------------------------------------------------------------
1 | package org.tartarus.snowball;
2 |
3 | import java.lang.reflect.Method;
4 |
5 | public class Among {
6 | public final int s_size; /* search string */
7 | public final char[] s; /* search string */
8 | public final int substring_i; /* index to longest matching substring */
9 | public final int result; /* result of the lookup */
10 | public final Method method; /* method to use if substring matches */
11 | public final SnowballProgram methodobject; /* object to invoke method on */
12 |
13 | public Among(String s, int substring_i, int result,
14 | String methodname, SnowballProgram methodobject) {
15 | this.s_size = s.length();
16 | this.s = s.toCharArray();
17 | this.substring_i = substring_i;
18 | this.result = result;
19 | this.methodobject = methodobject;
20 | if (methodname.length() == 0) {
21 | this.method = null;
22 | } else {
23 | try {
24 | this.method = methodobject.getClass().
25 | getDeclaredMethod(methodname, new Class[0]);
26 | } catch (NoSuchMethodException e) {
27 | throw new RuntimeException(e);
28 | }
29 | }
30 | }
31 | };
32 |
--------------------------------------------------------------------------------
/snowball/src/main/java/org/tartarus/snowball/SnowballProgram.java:
--------------------------------------------------------------------------------
1 |
2 | package org.tartarus.snowball;
3 |
4 | import java.lang.reflect.InvocationTargetException;
5 |
6 | public class SnowballProgram {
7 | // current string
8 | protected StringBuffer current;
9 | protected int cursor;
10 | protected int limit;
11 | protected int limit_backward;
12 | protected int bra;
13 | protected int ket;
14 |
15 | protected SnowballProgram() {
16 | current = new StringBuffer();
17 | setCurrent("");
18 | }
19 |
20 | /**
21 | * Get the current string.
22 | */
23 | public String getCurrent() {
24 | String result = current.toString();
25 | // Make a new StringBuffer. If we reuse the old one, and a user of
26 | // the library keeps a reference to the buffer returned (for example,
27 | // by converting it to a String in a way which doesn't force a copy),
28 | // the buffer size will not decrease, and we will risk wasting a large
29 | // amount of memory.
30 | // Thanks to Wolfram Esser for spotting this problem.
31 | current = new StringBuffer();
32 | return result;
33 | }
34 |
35 | /**
36 | * Set the current string.
37 | */
38 | public void setCurrent(String value) {
39 | current.replace(0, current.length(), value);
40 | cursor = 0;
41 | limit = current.length();
42 | limit_backward = 0;
43 | bra = cursor;
44 | ket = limit;
45 | }
46 |
47 | protected void copy_from(SnowballProgram other) {
48 | current = other.current;
49 | cursor = other.cursor;
50 | limit = other.limit;
51 | limit_backward = other.limit_backward;
52 | bra = other.bra;
53 | ket = other.ket;
54 | }
55 |
56 | protected boolean in_grouping(char[] s, int min, int max) {
57 | if (cursor >= limit) return false;
58 | char ch = current.charAt(cursor);
59 | if (ch > max || ch < min) return false;
60 | ch -= min;
61 | if ((s[ch >> 3] & (0X1 << (ch & 0X7))) == 0) return false;
62 | cursor++;
63 | return true;
64 | }
65 |
66 | protected boolean in_grouping_b(char[] s, int min, int max) {
67 | if (cursor <= limit_backward) return false;
68 | char ch = current.charAt(cursor - 1);
69 | if (ch > max || ch < min) return false;
70 | ch -= min;
71 | if ((s[ch >> 3] & (0X1 << (ch & 0X7))) == 0) return false;
72 | cursor--;
73 | return true;
74 | }
75 |
76 | protected boolean out_grouping(char[] s, int min, int max) {
77 | if (cursor >= limit) return false;
78 | char ch = current.charAt(cursor);
79 | if (ch > max || ch < min) {
80 | cursor++;
81 | return true;
82 | }
83 | ch -= min;
84 | if ((s[ch >> 3] & (0X1 << (ch & 0X7))) == 0) {
85 | cursor++;
86 | return true;
87 | }
88 | return false;
89 | }
90 |
91 | protected boolean out_grouping_b(char[] s, int min, int max) {
92 | if (cursor <= limit_backward) return false;
93 | char ch = current.charAt(cursor - 1);
94 | if (ch > max || ch < min) {
95 | cursor--;
96 | return true;
97 | }
98 | ch -= min;
99 | if ((s[ch >> 3] & (0X1 << (ch & 0X7))) == 0) {
100 | cursor--;
101 | return true;
102 | }
103 | return false;
104 | }
105 |
106 | protected boolean in_range(int min, int max) {
107 | if (cursor >= limit) return false;
108 | char ch = current.charAt(cursor);
109 | if (ch > max || ch < min) return false;
110 | cursor++;
111 | return true;
112 | }
113 |
114 | protected boolean in_range_b(int min, int max) {
115 | if (cursor <= limit_backward) return false;
116 | char ch = current.charAt(cursor - 1);
117 | if (ch > max || ch < min) return false;
118 | cursor--;
119 | return true;
120 | }
121 |
122 | protected boolean out_range(int min, int max) {
123 | if (cursor >= limit) return false;
124 | char ch = current.charAt(cursor);
125 | if (!(ch > max || ch < min)) return false;
126 | cursor++;
127 | return true;
128 | }
129 |
130 | protected boolean out_range_b(int min, int max) {
131 | if (cursor <= limit_backward) return false;
132 | char ch = current.charAt(cursor - 1);
133 | if (!(ch > max || ch < min)) return false;
134 | cursor--;
135 | return true;
136 | }
137 |
138 | protected boolean eq_s(int s_size, String s) {
139 | if (limit - cursor < s_size) return false;
140 | int i;
141 | for (i = 0; i != s_size; i++) {
142 | if (current.charAt(cursor + i) != s.charAt(i)) return false;
143 | }
144 | cursor += s_size;
145 | return true;
146 | }
147 |
148 | protected boolean eq_s_b(int s_size, String s) {
149 | if (cursor - limit_backward < s_size) return false;
150 | int i;
151 | for (i = 0; i != s_size; i++) {
152 | if (current.charAt(cursor - s_size + i) != s.charAt(i)) return false;
153 | }
154 | cursor -= s_size;
155 | return true;
156 | }
157 |
158 | protected boolean eq_v(CharSequence s) {
159 | return eq_s(s.length(), s.toString());
160 | }
161 |
162 | protected boolean eq_v_b(CharSequence s) {
163 | return eq_s_b(s.length(), s.toString());
164 | }
165 |
166 | protected int find_among(Among v[], int v_size) {
167 | int i = 0;
168 | int j = v_size;
169 |
170 | int c = cursor;
171 | int l = limit;
172 |
173 | int common_i = 0;
174 | int common_j = 0;
175 |
176 | boolean first_key_inspected = false;
177 |
178 | while (true) {
179 | int k = i + ((j - i) >> 1);
180 | int diff = 0;
181 | int common = common_i < common_j ? common_i : common_j; // smaller
182 | Among w = v[k];
183 | int i2;
184 | for (i2 = common; i2 < w.s_size; i2++) {
185 | if (c + common == l) {
186 | diff = -1;
187 | break;
188 | }
189 | diff = current.charAt(c + common) - w.s[i2];
190 | if (diff != 0) break;
191 | common++;
192 | }
193 | if (diff < 0) {
194 | j = k;
195 | common_j = common;
196 | } else {
197 | i = k;
198 | common_i = common;
199 | }
200 | if (j - i <= 1) {
201 | if (i > 0) break; // v->s has been inspected
202 | if (j == i) break; // only one item in v
203 |
204 | // - but now we need to go round once more to get
205 | // v->s inspected. This looks messy, but is actually
206 | // the optimal approach.
207 |
208 | if (first_key_inspected) break;
209 | first_key_inspected = true;
210 | }
211 | }
212 | while (true) {
213 | Among w = v[i];
214 | if (common_i >= w.s_size) {
215 | cursor = c + w.s_size;
216 | if (w.method == null) return w.result;
217 | boolean res;
218 | try {
219 | Object resobj = w.method.invoke(w.methodobject,
220 | new Object[0]);
221 | res = resobj.toString().equals("true");
222 | } catch (InvocationTargetException e) {
223 | res = false;
224 | // FIXME - debug message
225 | } catch (IllegalAccessException e) {
226 | res = false;
227 | // FIXME - debug message
228 | }
229 | cursor = c + w.s_size;
230 | if (res) return w.result;
231 | }
232 | i = w.substring_i;
233 | if (i < 0) return 0;
234 | }
235 | }
236 |
237 | // find_among_b is for backwards processing. Same comments apply
238 | protected int find_among_b(Among v[], int v_size) {
239 | int i = 0;
240 | int j = v_size;
241 |
242 | int c = cursor;
243 | int lb = limit_backward;
244 |
245 | int common_i = 0;
246 | int common_j = 0;
247 |
248 | boolean first_key_inspected = false;
249 |
250 | while (true) {
251 | int k = i + ((j - i) >> 1);
252 | int diff = 0;
253 | int common = common_i < common_j ? common_i : common_j;
254 | Among w = v[k];
255 | int i2;
256 | for (i2 = w.s_size - 1 - common; i2 >= 0; i2--) {
257 | if (c - common == lb) {
258 | diff = -1;
259 | break;
260 | }
261 | diff = current.charAt(c - 1 - common) - w.s[i2];
262 | if (diff != 0) break;
263 | common++;
264 | }
265 | if (diff < 0) {
266 | j = k;
267 | common_j = common;
268 | } else {
269 | i = k;
270 | common_i = common;
271 | }
272 | if (j - i <= 1) {
273 | if (i > 0) break;
274 | if (j == i) break;
275 | if (first_key_inspected) break;
276 | first_key_inspected = true;
277 | }
278 | }
279 | while (true) {
280 | Among w = v[i];
281 | if (common_i >= w.s_size) {
282 | cursor = c - w.s_size;
283 | if (w.method == null) return w.result;
284 |
285 | boolean res;
286 | try {
287 | Object resobj = w.method.invoke(w.methodobject,
288 | new Object[0]);
289 | res = resobj.toString().equals("true");
290 | } catch (InvocationTargetException e) {
291 | res = false;
292 | // FIXME - debug message
293 | } catch (IllegalAccessException e) {
294 | res = false;
295 | // FIXME - debug message
296 | }
297 | cursor = c - w.s_size;
298 | if (res) return w.result;
299 | }
300 | i = w.substring_i;
301 | if (i < 0) return 0;
302 | }
303 | }
304 |
305 | /* to replace chars between c_bra and c_ket in current by the
306 | * chars in s.
307 | */
308 | protected int replace_s(int c_bra, int c_ket, String s) {
309 | int adjustment = s.length() - (c_ket - c_bra);
310 | current.replace(c_bra, c_ket, s);
311 | limit += adjustment;
312 | if (cursor >= c_ket) cursor += adjustment;
313 | else if (cursor > c_bra) cursor = c_bra;
314 | return adjustment;
315 | }
316 |
317 | protected void slice_check() {
318 | if (bra < 0 ||
319 | bra > ket ||
320 | ket > limit ||
321 | limit > current.length()) // this line could be removed
322 | {
323 | System.err.println("faulty slice operation");
324 | // FIXME: report error somehow.
325 | /*
326 | fprintf(stderr, "faulty slice operation:\n");
327 | debug(z, -1, 0);
328 | exit(1);
329 | */
330 | }
331 | }
332 |
333 | protected void slice_from(String s) {
334 | slice_check();
335 | replace_s(bra, ket, s);
336 | }
337 |
338 | protected void slice_from(CharSequence s) {
339 | slice_from(s.toString());
340 | }
341 |
342 | protected void slice_del() {
343 | slice_from("");
344 | }
345 |
346 | protected void insert(int c_bra, int c_ket, String s) {
347 | int adjustment = replace_s(c_bra, c_ket, s);
348 | if (c_bra <= bra) bra += adjustment;
349 | if (c_bra <= ket) ket += adjustment;
350 | }
351 |
352 | protected void insert(int c_bra, int c_ket, CharSequence s) {
353 | insert(c_bra, c_ket, s.toString());
354 | }
355 |
356 | /* Copy the slice into the supplied StringBuffer */
357 | protected StringBuffer slice_to(StringBuffer s) {
358 | slice_check();
359 | int len = ket - bra;
360 | s.replace(0, s.length(), current.substring(bra, ket));
361 | return s;
362 | }
363 |
364 | /* Copy the slice into the supplied StringBuilder */
365 | protected StringBuilder slice_to(StringBuilder s) {
366 | slice_check();
367 | int len = ket - bra;
368 | s.replace(0, s.length(), current.substring(bra, ket));
369 | return s;
370 | }
371 |
372 | protected StringBuffer assign_to(StringBuffer s) {
373 | s.replace(0, s.length(), current.substring(0, limit));
374 | return s;
375 | }
376 |
377 | protected StringBuilder assign_to(StringBuilder s) {
378 | s.replace(0, s.length(), current.substring(0, limit));
379 | return s;
380 | }
381 |
382 | /*
383 | extern void debug(struct SN_env * z, int number, int line_count)
384 | { int i;
385 | int limit = SIZE(z->p);
386 | //if (number >= 0) printf("%3d (line %4d): '", number, line_count);
387 | if (number >= 0) printf("%3d (line %4d): [%d]'", number, line_count,limit);
388 | for (i = 0; i <= limit; i++)
389 | { if (z->lb == i) printf("{");
390 | if (z->bra == i) printf("[");
391 | if (z->c == i) printf("|");
392 | if (z->ket == i) printf("]");
393 | if (z->l == i) printf("}");
394 | if (i < limit)
395 | { int ch = z->p[i];
396 | if (ch == 0) ch = '#';
397 | printf("%c", ch);
398 | }
399 | }
400 | printf("'\n");
401 | }
402 | */
403 |
404 | };
405 |
--------------------------------------------------------------------------------
/snowball/src/main/java/org/tartarus/snowball/SnowballStemmer.java:
--------------------------------------------------------------------------------
1 |
2 | package org.tartarus.snowball;
3 |
4 | public abstract class SnowballStemmer extends SnowballProgram {
5 | public abstract boolean stem();
6 | };
7 |
--------------------------------------------------------------------------------
/snowball/src/main/java/org/tartarus/snowball/TestApp.java:
--------------------------------------------------------------------------------
1 |
2 | package org.tartarus.snowball;
3 |
4 | import java.io.BufferedReader;
5 | import java.io.BufferedWriter;
6 | import java.io.FileInputStream;
7 | import java.io.FileOutputStream;
8 | import java.io.InputStreamReader;
9 | import java.io.OutputStream;
10 | import java.io.OutputStreamWriter;
11 | import java.io.Reader;
12 | import java.io.Writer;
13 |
14 | public class TestApp {
15 | private static void usage() {
16 | System.err.println("Usage: TestApp [-o ]");
17 | }
18 |
19 | public static void main(String[] args) throws Throwable {
20 | if (args.length < 2) {
21 | usage();
22 | return;
23 | }
24 |
25 | Class stemClass = Class.forName("org.tartarus.snowball.ext." +
26 | args[0] + "Stemmer");
27 | SnowballStemmer stemmer = (SnowballStemmer) stemClass.newInstance();
28 |
29 | Reader reader;
30 | reader = new InputStreamReader(new FileInputStream(args[1]));
31 | reader = new BufferedReader(reader);
32 |
33 | StringBuffer input = new StringBuffer();
34 |
35 | OutputStream outstream;
36 |
37 | if (args.length > 2) {
38 | if (args.length >= 4 && args[2].equals("-o")) {
39 | outstream = new FileOutputStream(args[3]);
40 | } else {
41 | usage();
42 | return;
43 | }
44 | } else {
45 | outstream = System.out;
46 | }
47 | Writer output = new OutputStreamWriter(outstream);
48 | output = new BufferedWriter(output);
49 |
50 | int repeat = 1;
51 | if (args.length > 4) {
52 | repeat = Integer.parseInt(args[4]);
53 | }
54 |
55 | Object[] emptyArgs = new Object[0];
56 | int character;
57 | while ((character = reader.read()) != -1) {
58 | char ch = (char) character;
59 | if (Character.isWhitespace((char) ch)) {
60 | if (input.length() > 0) {
61 | stemmer.setCurrent(input.toString());
62 | for (int i = repeat; i != 0; i--) {
63 | stemmer.stem();
64 | }
65 | output.write(stemmer.getCurrent());
66 | output.write('\n');
67 | input.delete(0, input.length());
68 | }
69 | } else {
70 | input.append(Character.toLowerCase(ch));
71 | }
72 | }
73 | output.flush();
74 | }
75 | }
76 |
--------------------------------------------------------------------------------