├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── lsxiao │ │ └── com │ │ └── zhihudailyrrd │ │ └── ApplicationTest.java │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── lsxiao │ │ └── com │ │ └── zhihudailyrrd │ │ ├── DailyApp.java │ │ ├── base │ │ ├── AbsFullDialogFragment.java │ │ ├── BaseActivity.java │ │ ├── BaseDialogFragment.java │ │ ├── BaseFragment.java │ │ ├── BundleKey.java │ │ ├── DividerItemDecoration.java │ │ └── Events.java │ │ ├── flux │ │ ├── action │ │ │ ├── NewsAction.java │ │ │ ├── base │ │ │ │ └── BaseAction.java │ │ │ └── creator │ │ │ │ ├── ActionCreatorManager.java │ │ │ │ ├── NewsActionCreator.java │ │ │ │ └── base │ │ │ │ └── BaseActionCreator.java │ │ ├── dispatcher │ │ │ └── Dispatcher.java │ │ └── store │ │ │ ├── NewsDetailStore.java │ │ │ ├── NewsListStore.java │ │ │ └── base │ │ │ └── BaseStore.java │ │ ├── inject │ │ ├── component │ │ │ └── ApplicationComponent.java │ │ └── module │ │ │ ├── ApplicationModule.java │ │ │ ├── ClientApiModule.java │ │ │ ├── DataLayerModule.java │ │ │ └── FluxModule.java │ │ ├── model │ │ ├── News.java │ │ ├── StartImage.java │ │ └── TodayNews.java │ │ ├── protocol │ │ └── ClientApi.java │ │ ├── service │ │ ├── DailyManager.java │ │ └── base │ │ │ ├── BaseManager.java │ │ │ └── DataLayer.java │ │ ├── ui │ │ ├── Activity │ │ │ ├── HomeActivity.java │ │ │ └── NewsDetailActivity.java │ │ ├── Adapter │ │ │ └── NewsListAdapter.java │ │ └── Fragment │ │ │ ├── NewsDetailFragment.java │ │ │ └── NewsListFragment.java │ │ ├── util │ │ ├── AppContextUtil.java │ │ ├── BundleUtil.java │ │ ├── ExitClickUtil.java │ │ ├── FastBlur.java │ │ ├── FastClickUtil.java │ │ ├── HtmlUtil.java │ │ ├── NetUtil.java │ │ └── SpUtil.java │ │ └── view │ │ └── TextSliderView.java │ └── res │ ├── drawable-xhdpi │ ├── ic_empty.png │ ├── ic_error.png │ └── ic_placeholder.jpg │ ├── drawable │ ├── bg_list_item_selector.xml │ ├── bg_shadow_mask.xml │ ├── color_list_item_text.xml │ └── list_divider.xml │ ├── layout │ ├── activity_home.xml │ ├── activity_news.xml │ ├── fragment_news_detail.xml │ ├── fragment_news_list.xml │ ├── include_empty.xml │ ├── include_error.xml │ ├── include_tool_bar.xml │ ├── list_item_header.xml │ ├── list_item_news.xml │ └── slider_item.xml │ ├── menu │ └── menu_news.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── mipmap-xxxhdpi │ └── ic_launcher.png │ ├── values-v21 │ └── styles.xml │ ├── values-w820dp │ └── dimens.xml │ └── values │ ├── attrs.xml │ ├── colors.xml │ ├── dimens.xml │ ├── dimens_detail_news.xml │ ├── strings.xml │ ├── styles.xml │ └── theme.xml ├── build.gradle ├── config └── dependencies.gradle ├── demo.gif ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── screenshot ├── flux_flow.png └── structure.jpg └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io 2 | 3 | ### Android ### 4 | # Built application files 5 | *.apk 6 | *.ap_ 7 | 8 | # Files for the Dalvik VM 9 | *.dex 10 | 11 | # Java class files 12 | *.class 13 | 14 | # Generated files 15 | bin/ 16 | gen/ 17 | 18 | # Gradle files 19 | .gradle/ 20 | build/ 21 | 22 | # Local configuration file (sdk path, etc) 23 | local.properties 24 | 25 | # Proguard folder generated by Eclipse 26 | proguard/ 27 | 28 | # Log Files 29 | *.log 30 | 31 | 32 | ### Intellij ### 33 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm 34 | 35 | /*.iml 36 | 37 | ## Directory-based project format: 38 | .idea/ 39 | # if you remove the above rule, at least ignore the follwing: 40 | 41 | # User-specific stuff: 42 | # .idea/workspace.xml 43 | # .idea/tasks.xml 44 | # .idea/dictionaries 45 | 46 | # Sensitive or high-churn files: 47 | # .idea/dataSources.ids 48 | # .idea/dataSources.xml 49 | # .idea/sqlDataSources.xml 50 | # .idea/dynamic.xml 51 | # .idea/uiDesigner.xml 52 | 53 | # Gradle: 54 | # .idea/gradle.xml 55 | # .idea/libraries 56 | 57 | # Mongo Explorer plugin: 58 | # .idea/mongoSettings.xml 59 | 60 | ## File-based project format: 61 | *.ipr 62 | *.iws 63 | 64 | ## Plugin-specific files: 65 | 66 | # IntelliJ 67 | out/ 68 | 69 | # mpeltonen/sbt-idea plugin 70 | .idea_modules/ 71 | 72 | # JIRA plugin 73 | atlassian-ide-plugin.xml 74 | 75 | # Crashlytics plugin (for Android Studio and IntelliJ) 76 | com_crashlytics_export_strings.xml 77 | 78 | 79 | ### OSX ### 80 | .DS_Store 81 | .AppleDouble 82 | .LSOverride 83 | 84 | # Icon must end with two \r 85 | Icon 86 | 87 | 88 | # Thumbnails 89 | ._* 90 | 91 | # Files that might appear on external disk 92 | .Spotlight-V100 93 | .Trashes 94 | 95 | # Directories potentially created on remote AFP share 96 | .AppleDB 97 | .AppleDesktop 98 | Network Trash Folder 99 | Temporary Items 100 | .apdisk 101 | 102 | 103 | ### Windows ### 104 | # Windows image file caches 105 | Thumbs.db 106 | ehthumbs.db 107 | 108 | # Folder config file 109 | Desktop.ini 110 | 111 | # Recycle Bin used on file shares 112 | $RECYCLE.BIN/ 113 | 114 | # Windows Installer files 115 | *.cab 116 | *.msi 117 | *.msm 118 | *.msp 119 | 120 | # Windows shortcuts 121 | *.lnk 122 | 123 | 124 | ### Gradle ### 125 | .gradle 126 | 127 | # Ignore Gradle GUI config 128 | gradle-app.setting 129 | *.iml 130 | 131 | /local.properties 132 | /.idea/workspace.xml 133 | /.idea/libraries 134 | /build 135 | /captures -------------------------------------------------------------------------------- /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 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 知乎日报 2 | 本示例基于Flux架构,使用Retrofit RxJava Dagger2 [Apollo](https://github.com/lsxiao/Apollo) (Compile-time RxBus)构建。 3 | 4 | ## Flux架构 5 | 6 | ![](https://github.com/lsxiao/ZhihuDailyRRD/blob/master/screenshot/flux_flow.png?raw=true) 7 | 8 | 9 | ## 工程结构 10 | 11 | ![](https://github.com/lsxiao/ZhihuDailyRRD/blob/master/screenshot/structure.jpg?raw=true) 12 | 13 | ## 示例预览 14 | 15 | ![](https://github.com/lsxiao/ZhihuDailyRRD/blob/master/demo.gif?raw=true) 16 | 17 | ## 关于Flux中View的UI交互处理问题 18 | 考虑如下问题,从Activity_1点击按钮,跳转到Activity_2,此时按钮的点击是不是也要经过action->dispatcher->store->view这样的流动? 19 | 20 | 在我实践过后,我个人建议与业务相关的UI交互尽量走Flux的Action分发(例如点击按钮从服务器获取数据),纯粹的UI逻辑可以直接在View(这里指Flux中的View,例如Activity)中处理,例如页面跳转。 21 | 22 | ## 关于Dispatcher 23 | Dispatcher在Flux中只应存在一个。 24 | 25 | 本示例中的Dispatcher只是简单的负责注册,取消注册,分发action,并没有实现通过调整回调方法的触发次序来管理Store之间的依赖关系,Store可以声明等待其他Store更新完毕再更新自己,也就是没有实现waitFor方法(以后会实现,因为需要恰当的使用场景)。 26 | 27 | 本示例中的Dispatcher中有关Store的注册与取消注册采用了订阅/取消模式,使用了第三方RxBus库[Apollo](https://github.com/lsxiao/Apollo)来实现。 28 | 29 | ##API 30 | 31 | 本示例中的所有Api,都是通过[izzyleung](https://github.com/izzyleung)的分析而来,最终解释权归知乎所有。 32 | 33 | ## 开源许可 34 | 35 | Apache License Version 2.0 36 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | app.iml -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'com.neenbedankt.android-apt' 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.3" 6 | 7 | defaultConfig { 8 | applicationId "lsxiao.com.zhihudailyrrd" 9 | minSdkVersion 19 10 | targetSdkVersion 23 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | packagingOptions { 15 | exclude 'META-INF/LICENSE' 16 | exclude 'META-INF/NOTICE' 17 | } 18 | 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | } 26 | 27 | dependencies { 28 | compile fileTree(dir: 'libs', include: ['*.jar']) 29 | //Test 30 | def test = parent.ext.test 31 | testCompile test.junit 32 | 33 | //Dependencies 34 | def libs = parent.ext.libs 35 | provided libs.javaxAnnotation 36 | provided libs.supportAnnotation 37 | apt libs.daggerCompiler 38 | 39 | //support 40 | compile libs.appcompat 41 | compile libs.recyclerView 42 | compile libs.supportDesign 43 | compile libs.cardView 44 | 45 | //Apollo 46 | compile libs.apollo 47 | apt libs.apolloProcessor 48 | 49 | //image loader 50 | compile libs.picasso 51 | compile libs.nineOld 52 | 53 | //image slider 54 | 55 | compile libs.imageSlider 56 | 57 | //inject 58 | compile libs.dagger 59 | compile libs.butterKnife 60 | 61 | //json 62 | compile libs.gson 63 | compile libs.retrofitWithGson 64 | 65 | //retrofit 66 | compile libs.retrofit 67 | compile libs.retrofitWithRxJava 68 | 69 | //okHttp 70 | compile libs.okHttpLogInterceptor 71 | 72 | //rxJava 73 | compile libs.rxJava 74 | compile libs.rxAndroid 75 | compile libs.rxBinding 76 | compile libs.rxLifecycle 77 | compile libs.rxComponent 78 | 79 | compile libs.circleImage 80 | 81 | compile libs.jodaTime 82 | } 83 | -------------------------------------------------------------------------------- /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/boluo/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/lsxiao/com/zhihudailyrrd/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package lsxiao.com.zhihudailyrrd; 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 | 14 | public void testName() throws Exception { 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 23 | 24 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /app/src/main/java/lsxiao/com/zhihudailyrrd/DailyApp.java: -------------------------------------------------------------------------------- 1 | package lsxiao.com.zhihudailyrrd; 2 | 3 | import android.app.Application; 4 | 5 | import com.lsxiao.apllo.Apollo; 6 | import com.lsxiao.apollo.generate.SubscriberBinderImplement; 7 | 8 | import lsxiao.com.zhihudailyrrd.inject.component.ApplicationComponent; 9 | import lsxiao.com.zhihudailyrrd.inject.component.DaggerApplicationComponent; 10 | import lsxiao.com.zhihudailyrrd.inject.module.ApplicationModule; 11 | import rx.android.schedulers.AndroidSchedulers; 12 | 13 | /** 14 | * @author lsxiao 15 | * @date 2015-11-03 22:24 16 | */ 17 | public class DailyApp extends Application { 18 | @Override 19 | public void onCreate() { 20 | super.onCreate(); 21 | initDagger(); 22 | initApollo(); 23 | } 24 | 25 | private void initDagger() { 26 | ApplicationComponent.Instance.init(DaggerApplicationComponent 27 | .builder() 28 | .applicationModule(new ApplicationModule(this)) 29 | .build()); 30 | } 31 | 32 | private void initApollo() { 33 | Apollo.get().init(SubscriberBinderImplement.instance(), AndroidSchedulers.mainThread()); 34 | } 35 | 36 | @Override 37 | public void onTerminate() { 38 | super.onTerminate(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/lsxiao/com/zhihudailyrrd/base/AbsFullDialogFragment.java: -------------------------------------------------------------------------------- 1 | package lsxiao.com.zhihudailyrrd.base; 2 | 3 | import android.app.Dialog; 4 | import android.graphics.Color; 5 | import android.graphics.drawable.ColorDrawable; 6 | import android.os.Bundle; 7 | import android.support.annotation.NonNull; 8 | import android.view.ViewGroup; 9 | import android.view.Window; 10 | import android.widget.RelativeLayout; 11 | 12 | /** 13 | * @author lsxiao 14 | * @date 2015-11-05 09:15 15 | */ 16 | public abstract class AbsFullDialogFragment extends BaseDialogFragment { 17 | 18 | @NonNull 19 | @Override 20 | public Dialog onCreateDialog(Bundle savedInstanceState) { 21 | // the content 22 | final RelativeLayout root = new RelativeLayout(getActivity()); 23 | root.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); 24 | 25 | // creating the fullscreen dialog 26 | final Dialog dialog = new Dialog(getActivity()); 27 | dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); 28 | dialog.setContentView(root); 29 | dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); 30 | dialog.getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); 31 | 32 | return dialog; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/lsxiao/com/zhihudailyrrd/base/BaseActivity.java: -------------------------------------------------------------------------------- 1 | package lsxiao.com.zhihudailyrrd.base; 2 | 3 | import android.os.Bundle; 4 | 5 | import com.lsxiao.apllo.Apollo; 6 | import com.lsxiao.apllo.entity.SubscriptionBinder; 7 | import com.trello.rxlifecycle.components.support.RxAppCompatActivity; 8 | 9 | import javax.inject.Inject; 10 | 11 | import butterknife.ButterKnife; 12 | import lsxiao.com.zhihudailyrrd.flux.action.creator.ActionCreatorManager; 13 | import lsxiao.com.zhihudailyrrd.flux.dispatcher.Dispatcher; 14 | import lsxiao.com.zhihudailyrrd.inject.component.ApplicationComponent; 15 | import lsxiao.com.zhihudailyrrd.service.base.DataLayer; 16 | 17 | /** 18 | * @author lsxiao 19 | * @date 2015-11-03 22:28 20 | */ 21 | public abstract class BaseActivity extends RxAppCompatActivity { 22 | @Inject 23 | DataLayer mDataLayer; 24 | @Inject 25 | Dispatcher mDispatcher; 26 | @Inject 27 | ActionCreatorManager mActionCreatorManager; 28 | private SubscriptionBinder binder; 29 | 30 | public BaseActivity() { 31 | ApplicationComponent.Instance.get().inject(this); 32 | } 33 | 34 | public DataLayer getDataLayer() { 35 | return mDataLayer; 36 | } 37 | 38 | public ActionCreatorManager getActionCreatorManager() { 39 | return mActionCreatorManager; 40 | } 41 | 42 | public Dispatcher getDispatcher() { 43 | return mDispatcher; 44 | } 45 | 46 | protected abstract int getLayoutId(); 47 | 48 | protected abstract void afterCreate(Bundle savedInstanceState); 49 | 50 | @Override 51 | protected void onCreate(Bundle savedInstanceState) { 52 | super.onCreate(savedInstanceState); 53 | setContentView(getLayoutId()); 54 | ButterKnife.bind(this); 55 | binder = Apollo.get().bind(this); 56 | afterCreate(savedInstanceState); 57 | } 58 | 59 | @Override 60 | protected void onDestroy() { 61 | super.onDestroy(); 62 | ButterKnife.unbind(this); 63 | if (null != binder) { 64 | binder.unbind(); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /app/src/main/java/lsxiao/com/zhihudailyrrd/base/BaseDialogFragment.java: -------------------------------------------------------------------------------- 1 | package lsxiao.com.zhihudailyrrd.base; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.v4.app.DialogFragment; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | 10 | import com.lsxiao.apllo.Apollo; 11 | import com.lsxiao.apllo.entity.SubscriptionBinder; 12 | 13 | import javax.inject.Inject; 14 | 15 | import butterknife.ButterKnife; 16 | import lsxiao.com.zhihudailyrrd.flux.action.creator.ActionCreatorManager; 17 | import lsxiao.com.zhihudailyrrd.flux.dispatcher.Dispatcher; 18 | import lsxiao.com.zhihudailyrrd.inject.component.ApplicationComponent; 19 | import lsxiao.com.zhihudailyrrd.service.base.DataLayer; 20 | 21 | /** 22 | * @author lsxiao 23 | * @date 2015-11-03 22:28 24 | */ 25 | public abstract class BaseDialogFragment extends DialogFragment { 26 | public static final String TAG = BaseDialogFragment.TAG; 27 | protected View mRootView; 28 | 29 | @Inject 30 | DataLayer mDataLayer; 31 | @Inject 32 | Dispatcher mDispatcher; 33 | // @Inject 34 | ActionCreatorManager mActionCreatorManager; 35 | private SubscriptionBinder binder; 36 | 37 | public BaseDialogFragment() { 38 | ApplicationComponent.Instance.get().inject(this); 39 | } 40 | 41 | @Nullable 42 | @Override 43 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 44 | mRootView = inflater.inflate(getLayoutId(), container, false); 45 | return mRootView; 46 | } 47 | 48 | @Override 49 | public void onViewCreated(View view, Bundle savedInstanceState) { 50 | super.onViewCreated(view, savedInstanceState); 51 | ButterKnife.bind(this, view); 52 | binder = Apollo.get().bind(this); 53 | afterCreate(savedInstanceState); 54 | } 55 | 56 | public DataLayer getDataLayer() { 57 | return mDataLayer; 58 | } 59 | 60 | public Dispatcher getDispatcher() { 61 | return mDispatcher; 62 | } 63 | 64 | public ActionCreatorManager getActionCreatorManager() { 65 | return mActionCreatorManager; 66 | } 67 | 68 | @Override 69 | public void onDestroyView() { 70 | super.onDestroyView(); 71 | ButterKnife.unbind(this); 72 | if (null != binder) { 73 | binder.unbind(); 74 | } 75 | } 76 | 77 | 78 | protected abstract int getLayoutId(); 79 | 80 | protected abstract void afterCreate(Bundle savedInstanceState); 81 | } 82 | -------------------------------------------------------------------------------- /app/src/main/java/lsxiao/com/zhihudailyrrd/base/BaseFragment.java: -------------------------------------------------------------------------------- 1 | package lsxiao.com.zhihudailyrrd.base; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | 9 | import com.lsxiao.apllo.Apollo; 10 | import com.lsxiao.apllo.entity.SubscriptionBinder; 11 | import com.trello.rxlifecycle.components.support.RxFragment; 12 | 13 | import javax.inject.Inject; 14 | 15 | import butterknife.ButterKnife; 16 | import lsxiao.com.zhihudailyrrd.flux.action.creator.ActionCreatorManager; 17 | import lsxiao.com.zhihudailyrrd.flux.dispatcher.Dispatcher; 18 | import lsxiao.com.zhihudailyrrd.flux.store.base.BaseStore; 19 | import lsxiao.com.zhihudailyrrd.inject.component.ApplicationComponent; 20 | 21 | /** 22 | * @author lsxiao 23 | * @date 2015-11-03 22:28 24 | */ 25 | public abstract class BaseFragment extends RxFragment { 26 | public static final String TAG = BaseFragment.class.getSimpleName(); 27 | protected View mRootView; 28 | @Inject 29 | Dispatcher mDispatcher; 30 | @Inject 31 | ActionCreatorManager mActionCreatorManager; 32 | 33 | private SubscriptionBinder binder; 34 | 35 | public BaseFragment() { 36 | ApplicationComponent.Instance.get().inject(this); 37 | } 38 | 39 | @Nullable 40 | @Override 41 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 42 | mRootView = inflater.inflate(getLayoutId(), container, false); 43 | return mRootView; 44 | } 45 | 46 | @Override 47 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { 48 | super.onViewCreated(view, savedInstanceState); 49 | ButterKnife.bind(this, view); 50 | binder = Apollo.get().bind(this); 51 | getDispatcher().register(getStore());//订阅Action 52 | afterCreate(savedInstanceState); 53 | } 54 | 55 | @Override 56 | public void onDestroyView() { 57 | super.onDestroyView(); 58 | ButterKnife.unbind(this); 59 | //取消订阅Action 60 | getDispatcher().unregister(getStore()); 61 | if (null != binder) { 62 | binder.unbind(); 63 | } 64 | } 65 | 66 | public Dispatcher getDispatcher() { 67 | return mDispatcher; 68 | } 69 | 70 | public ActionCreatorManager getActionCreatorManager() { 71 | return mActionCreatorManager; 72 | } 73 | 74 | protected abstract int getLayoutId(); 75 | 76 | protected abstract BaseStore getStore(); 77 | 78 | protected abstract void afterCreate(Bundle savedInstanceState); 79 | 80 | } 81 | -------------------------------------------------------------------------------- /app/src/main/java/lsxiao/com/zhihudailyrrd/base/BundleKey.java: -------------------------------------------------------------------------------- 1 | package lsxiao.com.zhihudailyrrd.base; 2 | 3 | /** 4 | * @author lsxiao 5 | * @date 2015-11-03 22:28 6 | */ 7 | public class BundleKey { 8 | public static final String STORY = "story"; 9 | public static final String TODAY_NEWS = "todayNews"; 10 | public static final String NEWS = "news"; 11 | public static final String THROWABLE = "throwable"; 12 | public static final String POSITION = "position"; 13 | 14 | public static final String NEWS_DETAIL_STORE = "newsDetailStore"; 15 | public static final String NEWS_LIST_STORE = "newsListStore"; 16 | public static final String LATEST_DATE = "latestDate"; 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/lsxiao/com/zhihudailyrrd/base/DividerItemDecoration.java: -------------------------------------------------------------------------------- 1 | package lsxiao.com.zhihudailyrrd.base; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Rect; 6 | import android.graphics.drawable.Drawable; 7 | import android.support.v7.widget.LinearLayoutManager; 8 | import android.support.v7.widget.RecyclerView; 9 | import android.view.View; 10 | 11 | import lsxiao.com.zhihudailyrrd.R; 12 | 13 | /** 14 | * 用于RecyclerView分割线 15 | * 16 | * @author lsxiao 17 | * @date 2015-11-05 00:27 18 | */ 19 | public class DividerItemDecoration extends RecyclerView.ItemDecoration { 20 | 21 | public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL; 22 | 23 | public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL; 24 | 25 | private Drawable mDivider; 26 | 27 | private int mOrientation; 28 | 29 | public DividerItemDecoration(Context context, int orientation) { 30 | mDivider = context.getResources().getDrawable(R.drawable.list_divider); 31 | setOrientation(orientation); 32 | } 33 | 34 | public void setOrientation(int orientation) { 35 | if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) { 36 | throw new IllegalArgumentException("invalid orientation"); 37 | } 38 | mOrientation = orientation; 39 | } 40 | 41 | @Override 42 | public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { 43 | if (mOrientation == VERTICAL_LIST) { 44 | drawVertical(c, parent); 45 | } else { 46 | drawHorizontal(c, parent); 47 | } 48 | } 49 | 50 | public void drawVertical(Canvas c, RecyclerView parent) { 51 | final int left = parent.getPaddingLeft(); 52 | final int right = parent.getWidth() - parent.getPaddingRight(); 53 | 54 | final int childCount = parent.getChildCount(); 55 | for (int i = 0; i < childCount; i++) { 56 | final View child = parent.getChildAt(i); 57 | final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child 58 | .getLayoutParams(); 59 | final int top = child.getBottom() + params.bottomMargin; 60 | final int bottom = top + mDivider.getIntrinsicHeight(); 61 | mDivider.setBounds(left, top, right, bottom); 62 | mDivider.draw(c); 63 | } 64 | } 65 | 66 | public void drawHorizontal(Canvas c, RecyclerView parent) { 67 | final int top = parent.getPaddingTop(); 68 | final int bottom = parent.getHeight() - parent.getPaddingBottom(); 69 | 70 | final int childCount = parent.getChildCount(); 71 | for (int i = 0; i < childCount; i++) { 72 | final View child = parent.getChildAt(i); 73 | final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child 74 | .getLayoutParams(); 75 | final int left = child.getRight() + params.rightMargin; 76 | final int right = left + mDivider.getIntrinsicHeight(); 77 | mDivider.setBounds(left, top, right, bottom); 78 | mDivider.draw(c); 79 | } 80 | } 81 | 82 | @Override 83 | public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { 84 | if (mOrientation == VERTICAL_LIST) { 85 | outRect.set(0, 0, 0, mDivider.getIntrinsicHeight()); 86 | } else { 87 | outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /app/src/main/java/lsxiao/com/zhihudailyrrd/base/Events.java: -------------------------------------------------------------------------------- 1 | package lsxiao.com.zhihudailyrrd.base; 2 | 3 | /** 4 | * author lsxiao 5 | * date 2016-08-09 10:24 6 | */ 7 | public class Events { 8 | public static final String NEWS_LIST_FETCH_CHANGE = "news_list_fetch_change"; 9 | public static final String NEWS_DETAIL_FETCH_CHANGE = "news_list_fetch_change"; 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/lsxiao/com/zhihudailyrrd/flux/action/NewsAction.java: -------------------------------------------------------------------------------- 1 | package lsxiao.com.zhihudailyrrd.flux.action; 2 | 3 | import android.os.Bundle; 4 | 5 | import lsxiao.com.zhihudailyrrd.flux.action.base.BaseAction; 6 | 7 | /** 8 | * 新闻Action 9 | * author lsxiao 10 | * date 2016-05-09 18:26 11 | */ 12 | public class NewsAction extends BaseAction { 13 | public final static String ACTION_LIST_NEWS_FETCH_START = "list_news_refresh_start"; 14 | public final static String ACTION_LIST_NEWS_FETCH_FINISH = "list_news_refresh_finish"; 15 | public final static String ACTION_LIST_NEWS_FETCH_ERROR = "list_news_refresh_error"; 16 | 17 | public final static String ACTION_DETAIL_NEWS_FETCH_START = "detail_news_refresh_start"; 18 | public final static String ACTION_DETAIL_NEWS_FETCH_FINISH = "detail_news_refresh_finish"; 19 | public final static String ACTION_DETAIL_NEWS_FETCH_ERROR = "detail_news_refresh_error"; 20 | 21 | public NewsAction(String type, Bundle data) { 22 | super(type, data); 23 | } 24 | 25 | public NewsAction(String type) { 26 | super(type); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/lsxiao/com/zhihudailyrrd/flux/action/base/BaseAction.java: -------------------------------------------------------------------------------- 1 | package lsxiao.com.zhihudailyrrd.flux.action.base; 2 | 3 | import android.os.Bundle; 4 | 5 | import java.io.Serializable; 6 | 7 | /** 8 | * Action 基类,所有action都需要继承此类 9 | * author lsxiao 10 | * date 2016-05-09 17:29 11 | */ 12 | public abstract class BaseAction implements Serializable { 13 | private final String type; 14 | private final Bundle data; 15 | 16 | protected BaseAction(String type, Bundle data) { 17 | this.type = type; 18 | this.data = data; 19 | } 20 | 21 | protected BaseAction(String type) { 22 | this(type, null); 23 | } 24 | 25 | public String getType() { 26 | return type; 27 | } 28 | 29 | public Bundle getData() { 30 | return data; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/lsxiao/com/zhihudailyrrd/flux/action/creator/ActionCreatorManager.java: -------------------------------------------------------------------------------- 1 | package lsxiao.com.zhihudailyrrd.flux.action.creator; 2 | 3 | /** 4 | * 管理所有的ActionCreator 5 | * author lsxiao 6 | * date 2016-05-09 18:29 7 | */ 8 | public class ActionCreatorManager { 9 | private NewsActionCreator mNewsActionCreator; 10 | 11 | public ActionCreatorManager(NewsActionCreator newsActionCreator) { 12 | mNewsActionCreator = newsActionCreator; 13 | } 14 | 15 | public NewsActionCreator getNewsActionCreator() { 16 | return mNewsActionCreator; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/lsxiao/com/zhihudailyrrd/flux/action/creator/NewsActionCreator.java: -------------------------------------------------------------------------------- 1 | package lsxiao.com.zhihudailyrrd.flux.action.creator; 2 | 3 | import android.os.Bundle; 4 | 5 | import java.util.concurrent.TimeUnit; 6 | 7 | import lsxiao.com.zhihudailyrrd.base.BundleKey; 8 | import lsxiao.com.zhihudailyrrd.flux.action.NewsAction; 9 | import lsxiao.com.zhihudailyrrd.flux.action.creator.base.BaseActionCreator; 10 | import lsxiao.com.zhihudailyrrd.flux.dispatcher.Dispatcher; 11 | import lsxiao.com.zhihudailyrrd.model.News; 12 | import lsxiao.com.zhihudailyrrd.model.TodayNews; 13 | import lsxiao.com.zhihudailyrrd.service.base.DataLayer; 14 | import lsxiao.com.zhihudailyrrd.util.BundleUtil; 15 | import rx.Observable; 16 | import rx.android.schedulers.AndroidSchedulers; 17 | import rx.functions.Action1; 18 | import rx.functions.Func1; 19 | import rx.schedulers.Schedulers; 20 | 21 | /** 22 | * 新闻的ActionCreator 23 | * author lsxiao 24 | * date 2016-05-09 17:25 25 | */ 26 | public class NewsActionCreator extends BaseActionCreator { 27 | public NewsActionCreator(Dispatcher dispatcher, DataLayer dataLayer) { 28 | super(dispatcher, dataLayer); 29 | } 30 | 31 | public void fetchListNews() { 32 | //分发开始刷新列表事件 33 | getDispatcher().dispatch(new NewsAction(NewsAction.ACTION_LIST_NEWS_FETCH_START)); 34 | 35 | //缓存数据源 36 | Observable cache = getDataLayer().getDailyService().getLatestTodayNews(); 37 | 38 | //服务端数据源 39 | Observable network = getDataLayer().getDailyService().getTodayNews(); 40 | 41 | //输出前缓存一下 42 | network = network.doOnNext(new Action1() { 43 | @Override 44 | public void call(TodayNews todayNews) { 45 | getDataLayer().getDailyService().cacheTodayNews(todayNews); 46 | } 47 | }); 48 | 49 | //没有网络数据再使用缓存数据 50 | Observable source = Observable 51 | .concat(network, cache) 52 | //依次遍历序列中的数据源,返回第一个符合条件的数据源 53 | .takeFirst(new Func1() { 54 | @Override 55 | public Boolean call(TodayNews todayNews) { 56 | return todayNews != null; 57 | } 58 | }); 59 | 60 | source.subscribeOn(Schedulers.io()) 61 | .observeOn(AndroidSchedulers.mainThread()) 62 | .subscribe(new Action1() { 63 | @Override 64 | public void call(TodayNews todayNews) { 65 | //封装数据 66 | Bundle bundle = BundleUtil.newInstance(); 67 | bundle.putSerializable(BundleKey.TODAY_NEWS, todayNews); 68 | //分发Action 69 | getDispatcher().dispatch(new NewsAction(NewsAction.ACTION_LIST_NEWS_FETCH_FINISH, bundle)); 70 | } 71 | }, new Action1() { 72 | @Override 73 | public void call(Throwable throwable) { 74 | //封装异常 75 | Bundle bundle = BundleUtil.withThrowable(throwable); 76 | //分发Action 77 | getDispatcher().dispatch(new NewsAction(NewsAction.ACTION_LIST_NEWS_FETCH_ERROR, bundle)); 78 | } 79 | }); 80 | 81 | } 82 | 83 | public void fetchDetailNews(TodayNews.Story mStory) { 84 | //分发开始刷新列表事件 85 | getDispatcher().dispatch(new NewsAction(NewsAction.ACTION_DETAIL_NEWS_FETCH_START)); 86 | 87 | //服务端数据源 88 | Observable network = getDataLayer().getDailyService().getNews(mStory.getId()); 89 | 90 | //缓存数据源 91 | Observable cache = getDataLayer().getDailyService().getLocalNews(String.valueOf(mStory.getId())); 92 | 93 | //输出数据前缓存到本地 94 | network = network.doOnNext(new Action1() { 95 | @Override 96 | public void call(News news) { 97 | getDataLayer().getDailyService().cacheNews(news); 98 | } 99 | }); 100 | 101 | //返回第一个不为空的Observable对象   102 | Observable newsObservable = Observable 103 | .concat(cache, network) 104 | .takeFirst(new Func1() { 105 | @Override 106 | public Boolean call(News news) { 107 | //如果本地数据存在的话 108 | return news != null; 109 | } 110 | }); 111 | 112 | //订阅事件序列 113 | newsObservable.subscribeOn(Schedulers.io()) 114 | .delay(1, TimeUnit.SECONDS)//延迟一秒显示,让loading过渡更自然 115 | .observeOn(AndroidSchedulers.mainThread())//在主线程处理订阅的事件序列 116 | .subscribe(new Action1() { 117 | @Override 118 | public void call(News news) { 119 | //封装数据 120 | Bundle bundle = BundleUtil.newInstance(); 121 | bundle.putSerializable(BundleKey.NEWS, news); 122 | //分发Action给Store 123 | getDispatcher().dispatch(new NewsAction(NewsAction.ACTION_DETAIL_NEWS_FETCH_FINISH, bundle)); 124 | } 125 | }, new Action1() { 126 | @Override 127 | public void call(Throwable throwable) { 128 | //封装异常 129 | Bundle bundle = BundleUtil.withThrowable(throwable); 130 | //分发Action给Store 131 | getDispatcher().dispatch(new NewsAction(NewsAction.ACTION_DETAIL_NEWS_FETCH_ERROR, bundle)); 132 | } 133 | }); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /app/src/main/java/lsxiao/com/zhihudailyrrd/flux/action/creator/base/BaseActionCreator.java: -------------------------------------------------------------------------------- 1 | package lsxiao.com.zhihudailyrrd.flux.action.creator.base; 2 | 3 | import lsxiao.com.zhihudailyrrd.flux.dispatcher.Dispatcher; 4 | import lsxiao.com.zhihudailyrrd.service.base.DataLayer; 5 | 6 | /** 7 | * ActionCreator基类,所有ActionCreator都需要继承此类 8 | * author lsxiao 9 | * date 2016-05-09 17:21 10 | */ 11 | public abstract class BaseActionCreator { 12 | Dispatcher mDispatcher; 13 | DataLayer mDataLayer; 14 | 15 | public BaseActionCreator(Dispatcher dispatcher, DataLayer dataLayer) { 16 | mDispatcher = dispatcher; 17 | mDataLayer = dataLayer; 18 | } 19 | 20 | public Dispatcher getDispatcher() { 21 | return mDispatcher; 22 | } 23 | 24 | public DataLayer getDataLayer() { 25 | return mDataLayer; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/lsxiao/com/zhihudailyrrd/flux/dispatcher/Dispatcher.java: -------------------------------------------------------------------------------- 1 | package lsxiao.com.zhihudailyrrd.flux.dispatcher; 2 | 3 | import android.util.Log; 4 | 5 | import com.lsxiao.apllo.Apollo; 6 | 7 | import java.util.Arrays; 8 | import java.util.HashMap; 9 | import java.util.List; 10 | import java.util.Map; 11 | 12 | import lsxiao.com.zhihudailyrrd.flux.action.base.BaseAction; 13 | import lsxiao.com.zhihudailyrrd.flux.store.base.BaseStore; 14 | import rx.Observable; 15 | import rx.Subscription; 16 | import rx.functions.Action1; 17 | 18 | /** 19 | * author lsxiao 20 | * date 2016-05-09 17:28 21 | *

22 | * 负责Action的分发,以及Store的订阅和取消订阅事件 23 | */ 24 | public class Dispatcher { 25 | private Map mStoreSubscriptionHashMap; 26 | private static Dispatcher sInstance; 27 | private Observable mActionObservable; 28 | private boolean mIsDispatching = false; 29 | 30 | private Dispatcher() { 31 | mStoreSubscriptionHashMap = new HashMap<>(); 32 | } 33 | 34 | /** 35 | * 返回Dispatcher单例对象 36 | * 37 | * @return dispatcher Dispatcher 38 | */ 39 | public static Dispatcher instance() { 40 | if (sInstance == null) { 41 | sInstance = new Dispatcher(); 42 | } 43 | return sInstance; 44 | } 45 | 46 | /** 47 | * 订阅action 48 | * 49 | * @param store BaseStore 50 | */ 51 | public void register(final BaseStore store) { 52 | if (null == store) { 53 | throw new IllegalArgumentException("the store can't be null"); 54 | } 55 | 56 | final int uniqueId = System.identityHashCode(store); 57 | if (mStoreSubscriptionHashMap.containsKey(uniqueId)) { 58 | Subscription subscription = mStoreSubscriptionHashMap.get(uniqueId); 59 | if (subscription.isUnsubscribed()) { 60 | mStoreSubscriptionHashMap.remove(uniqueId); 61 | } else { 62 | return; 63 | } 64 | } 65 | 66 | //将subscription缓存下来,以便之后取消订阅 67 | mStoreSubscriptionHashMap.put(uniqueId, Apollo.get().toObservable(BaseAction.class.getCanonicalName(), BaseAction.class) 68 | .subscribe(new Action1() { 69 | @Override 70 | public void call(BaseAction action) { 71 | store.onAction(action); 72 | } 73 | })); 74 | } 75 | 76 | 77 | /** 78 | * 订阅action 79 | * 80 | * @param stores BaseStore[] 81 | */ 82 | public void register(BaseStore... stores) { 83 | if (null == stores || stores.length == 0) { 84 | throw new IllegalArgumentException("the store array is null or empty"); 85 | } 86 | register(Arrays.asList(stores)); 87 | } 88 | 89 | /** 90 | * 订阅action 91 | * 92 | * @param stores List 93 | */ 94 | public void register(List stores) { 95 | if (null == stores || stores.isEmpty()) { 96 | throw new IllegalArgumentException("the store list is null or empty"); 97 | } 98 | Observable.from(stores).forEach(new Action1() { 99 | @Override 100 | public void call(BaseStore baseStore) { 101 | register(baseStore); 102 | } 103 | }); 104 | } 105 | 106 | /** 107 | * 取消订阅action 108 | * 109 | * @param store BaseStore 110 | */ 111 | public void unregister(BaseStore store) { 112 | if (null == store) { 113 | throw new IllegalArgumentException("the store can't be null"); 114 | } 115 | final int uniqueId = System.identityHashCode(store); 116 | //获取到订阅者 117 | Subscription subscription = mStoreSubscriptionHashMap.get(uniqueId); 118 | 119 | //如果没有取消订阅 120 | if (subscription != null && !subscription.isUnsubscribed()) { 121 | //取消订阅 122 | subscription.unsubscribe(); 123 | } 124 | mStoreSubscriptionHashMap.remove(uniqueId); 125 | } 126 | 127 | /** 128 | * 取消订阅action 129 | * 130 | * @param stores BaseStore[] 131 | */ 132 | public void unregister(BaseStore... stores) { 133 | if (null == stores || stores.length == 0) { 134 | throw new IllegalArgumentException("the store array is null or empty"); 135 | } 136 | unregister(Arrays.asList(stores)); 137 | } 138 | 139 | /** 140 | * 取消订阅action 141 | * 142 | * @param stores List 143 | */ 144 | public void unregister(List stores) { 145 | if (null == stores || stores.isEmpty()) { 146 | throw new IllegalArgumentException("the store list is null or empty"); 147 | } 148 | for (BaseStore store : stores) { 149 | unregister(store); 150 | } 151 | } 152 | 153 | /** 154 | * 分发action 155 | * 156 | * @param action BaseAction child instance 157 | */ 158 | public void dispatch(BaseAction action) { 159 | if (null == action) { 160 | throw new IllegalArgumentException("the action can't be null"); 161 | } 162 | Apollo.get().send(BaseAction.class.getCanonicalName(), action); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /app/src/main/java/lsxiao/com/zhihudailyrrd/flux/store/NewsDetailStore.java: -------------------------------------------------------------------------------- 1 | package lsxiao.com.zhihudailyrrd.flux.store; 2 | 3 | import android.os.Bundle; 4 | 5 | import java.io.Serializable; 6 | 7 | import lsxiao.com.zhihudailyrrd.base.BundleKey; 8 | import lsxiao.com.zhihudailyrrd.base.Events; 9 | import lsxiao.com.zhihudailyrrd.flux.action.NewsAction; 10 | import lsxiao.com.zhihudailyrrd.flux.action.base.BaseAction; 11 | import lsxiao.com.zhihudailyrrd.flux.store.base.BaseStore; 12 | import lsxiao.com.zhihudailyrrd.model.News; 13 | import lsxiao.com.zhihudailyrrd.util.BundleUtil; 14 | 15 | /** 16 | * 对应DetailNewsFragment 17 | * author lsxiao 18 | * date 2016-05-09 17:26 19 | */ 20 | public class NewsDetailStore extends BaseStore { 21 | private News news; 22 | private Throwable mThrowable; 23 | private FetchStatus mFetchStatus = FetchStatus.INIT; 24 | 25 | private enum FetchStatus implements Serializable { 26 | INIT, LOADING, FINISH, ERROR 27 | } 28 | 29 | @Override 30 | public void onAction(BaseAction action) { 31 | switch (action.getType()) { 32 | case NewsAction.ACTION_DETAIL_NEWS_FETCH_START: { 33 | mTag = Events.NEWS_DETAIL_FETCH_CHANGE; 34 | mFetchStatus = FetchStatus.LOADING; 35 | mChangeEvent = new FetchChangeEvent(); 36 | emitStoreChange(); 37 | break; 38 | } 39 | case NewsAction.ACTION_DETAIL_NEWS_FETCH_FINISH: { 40 | Bundle bundle = action.getData(); 41 | if (null != bundle && !bundle.isEmpty()) { 42 | news = (News) bundle.getSerializable(BundleKey.NEWS); 43 | } 44 | mTag = Events.NEWS_DETAIL_FETCH_CHANGE; 45 | mFetchStatus = FetchStatus.FINISH; 46 | mChangeEvent = new FetchChangeEvent(); 47 | emitStoreChange(); 48 | break; 49 | } 50 | case NewsAction.ACTION_DETAIL_NEWS_FETCH_ERROR: { 51 | mFetchStatus = FetchStatus.ERROR; 52 | mTag = Events.NEWS_DETAIL_FETCH_CHANGE; 53 | mChangeEvent = new FetchChangeEvent(); 54 | mThrowable = BundleUtil.getThrowable(action.getData()); 55 | emitStoreChange(); 56 | break; 57 | } 58 | } 59 | } 60 | 61 | public boolean isShowLoadView() { 62 | return isLoading(); 63 | } 64 | 65 | public int getEmptyViewVis() { 66 | return isEmpty() && !isError() && isFinish() ? VIS : GONE; 67 | } 68 | 69 | public int getErrorViewVis() { 70 | return isError() && isEmpty() ? VIS : GONE; 71 | } 72 | 73 | 74 | public News getData() { 75 | return news; 76 | } 77 | 78 | public boolean isLoading() { 79 | return mFetchStatus == FetchStatus.LOADING; 80 | } 81 | 82 | public boolean isFinish() { 83 | return mFetchStatus == FetchStatus.FINISH; 84 | } 85 | 86 | public boolean isError() { 87 | return mFetchStatus == FetchStatus.ERROR; 88 | } 89 | 90 | public boolean isEmpty() { 91 | return news == null; 92 | } 93 | 94 | public Throwable getThrowable() { 95 | return mThrowable; 96 | } 97 | 98 | @Override 99 | protected BaseStore.ChangeEvent getChangeEvent() { 100 | return mChangeEvent; 101 | } 102 | 103 | public class FetchChangeEvent implements BaseStore.ChangeEvent { 104 | 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /app/src/main/java/lsxiao/com/zhihudailyrrd/flux/store/NewsListStore.java: -------------------------------------------------------------------------------- 1 | package lsxiao.com.zhihudailyrrd.flux.store; 2 | 3 | import android.os.Bundle; 4 | 5 | import java.io.Serializable; 6 | 7 | import lsxiao.com.zhihudailyrrd.base.BundleKey; 8 | import lsxiao.com.zhihudailyrrd.base.Events; 9 | import lsxiao.com.zhihudailyrrd.flux.action.NewsAction; 10 | import lsxiao.com.zhihudailyrrd.flux.action.base.BaseAction; 11 | import lsxiao.com.zhihudailyrrd.flux.store.base.BaseStore; 12 | import lsxiao.com.zhihudailyrrd.model.TodayNews; 13 | import lsxiao.com.zhihudailyrrd.util.BundleUtil; 14 | 15 | /** 16 | * 对应ListNewsFragment 17 | * author lsxiao 18 | * date 2016-05-09 21:18 19 | */ 20 | public class NewsListStore extends BaseStore { 21 | private TodayNews todayNews; 22 | private Throwable mThrowable; 23 | private FetchStatus mFetchStatus = FetchStatus.INIT; 24 | 25 | private enum FetchStatus implements Serializable { 26 | INIT, LOADING, FINISH, ERROR 27 | } 28 | 29 | @Override 30 | public void onAction(BaseAction action) { 31 | switch (action.getType()) { 32 | case NewsAction.ACTION_LIST_NEWS_FETCH_START: { 33 | mFetchStatus = FetchStatus.LOADING; 34 | mChangeEvent = new FetchChangeEvent(); 35 | mTag = Events.NEWS_LIST_FETCH_CHANGE; 36 | emitStoreChange(); 37 | break; 38 | } 39 | case NewsAction.ACTION_LIST_NEWS_FETCH_FINISH: { 40 | Bundle bundle = action.getData(); 41 | if (null != bundle && !bundle.isEmpty()) { 42 | todayNews = (TodayNews) bundle.getSerializable(BundleKey.TODAY_NEWS); 43 | } 44 | mFetchStatus = FetchStatus.FINISH; 45 | mTag = Events.NEWS_LIST_FETCH_CHANGE; 46 | mChangeEvent = new FetchChangeEvent(); 47 | emitStoreChange(); 48 | break; 49 | } 50 | case NewsAction.ACTION_LIST_NEWS_FETCH_ERROR: { 51 | mFetchStatus = FetchStatus.ERROR; 52 | mTag = Events.NEWS_LIST_FETCH_CHANGE; 53 | mChangeEvent = new FetchChangeEvent(); 54 | mThrowable = BundleUtil.getThrowable(action.getData()); 55 | emitStoreChange(); 56 | break; 57 | } 58 | default: 59 | } 60 | } 61 | 62 | public boolean isShowLoadView() { 63 | return isLoading(); 64 | } 65 | 66 | public int getEmptyViewVis() { 67 | return isEmpty() && !isError() && isFinish() ? VIS : GONE; 68 | } 69 | 70 | public int getErrorViewVis() { 71 | return isError() && isEmpty() ? VIS : GONE; 72 | } 73 | 74 | 75 | public TodayNews getTodayNews() { 76 | return todayNews; 77 | } 78 | 79 | public boolean isLoading() { 80 | return mFetchStatus == FetchStatus.LOADING; 81 | } 82 | 83 | public boolean isFinish() { 84 | return mFetchStatus == FetchStatus.FINISH; 85 | } 86 | 87 | public boolean isError() { 88 | return mFetchStatus == FetchStatus.ERROR; 89 | } 90 | 91 | public boolean isEmpty() { 92 | return todayNews == null; 93 | } 94 | 95 | public Throwable getThrowable() { 96 | return mThrowable; 97 | } 98 | 99 | @Override 100 | protected ChangeEvent getChangeEvent() { 101 | return mChangeEvent; 102 | } 103 | 104 | 105 | public class FetchChangeEvent implements ChangeEvent { 106 | 107 | } 108 | 109 | public class ItemClickChangeEvent implements ChangeEvent { 110 | public int position; 111 | 112 | public ItemClickChangeEvent(int position) { 113 | this.position = position; 114 | } 115 | } 116 | 117 | public class SliderClickChangeEvent implements ChangeEvent { 118 | public TodayNews.Story story; 119 | 120 | public SliderClickChangeEvent(TodayNews.Story story) { 121 | this.story = story; 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /app/src/main/java/lsxiao/com/zhihudailyrrd/flux/store/base/BaseStore.java: -------------------------------------------------------------------------------- 1 | package lsxiao.com.zhihudailyrrd.flux.store.base; 2 | 3 | import android.view.View; 4 | 5 | import com.lsxiao.apllo.Apollo; 6 | 7 | import java.io.Serializable; 8 | 9 | import lsxiao.com.zhihudailyrrd.flux.action.base.BaseAction; 10 | 11 | /** 12 | * Store基类,所有Store都需要继承此类,Store主要对View的状态进行管理,以及发送Store改变事件给View 13 | * author lsxiao 14 | * date 2016-05-09 17:25 15 | */ 16 | public abstract class BaseStore implements Serializable { 17 | public static final int VIS = View.VISIBLE; 18 | public static final int GONE = View.GONE; 19 | public static final int INVIS = View.INVISIBLE; 20 | 21 | public abstract void onAction(BaseAction action); 22 | 23 | public ChangeEvent mChangeEvent; 24 | public String mTag; 25 | 26 | /** 27 | * 发送Store改变事件,View接收到后进行相应的render 28 | */ 29 | protected void emitStoreChange() { 30 | Apollo.get().send(mTag, getChangeEvent()); 31 | } 32 | 33 | protected abstract ChangeEvent getChangeEvent(); 34 | 35 | public interface ChangeEvent extends Serializable { 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/lsxiao/com/zhihudailyrrd/inject/component/ApplicationComponent.java: -------------------------------------------------------------------------------- 1 | package lsxiao.com.zhihudailyrrd.inject.component; 2 | 3 | import android.app.Application; 4 | import android.support.annotation.NonNull; 5 | 6 | import javax.inject.Singleton; 7 | 8 | import dagger.Component; 9 | import lsxiao.com.zhihudailyrrd.base.BaseActivity; 10 | import lsxiao.com.zhihudailyrrd.base.BaseDialogFragment; 11 | import lsxiao.com.zhihudailyrrd.base.BaseFragment; 12 | import lsxiao.com.zhihudailyrrd.inject.module.ApplicationModule; 13 | import lsxiao.com.zhihudailyrrd.service.base.BaseManager; 14 | 15 | /** 16 | * @author lsxiao 17 | * @date 2015-11-04 00:47 18 | */ 19 | @Singleton 20 | @Component(modules = ApplicationModule.class) 21 | public interface ApplicationComponent { 22 | 23 | void inject(BaseActivity activity); 24 | 25 | void inject(BaseFragment fragment); 26 | 27 | void inject(BaseDialogFragment dialogFragment); 28 | 29 | void inject(BaseManager manager); 30 | 31 | Application getApplication(); 32 | 33 | class Instance { 34 | private static ApplicationComponent sComponent; 35 | 36 | public static void init(@NonNull ApplicationComponent component) { 37 | sComponent = component; 38 | } 39 | 40 | public static ApplicationComponent get() { 41 | return sComponent; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/lsxiao/com/zhihudailyrrd/inject/module/ApplicationModule.java: -------------------------------------------------------------------------------- 1 | package lsxiao.com.zhihudailyrrd.inject.module; 2 | 3 | import android.app.Application; 4 | 5 | import javax.inject.Singleton; 6 | 7 | import dagger.Module; 8 | import dagger.Provides; 9 | 10 | /** 11 | * author lsxiao 12 | * date 2016-05-09 20:06 13 | */ 14 | @Module(includes = {DataLayerModule.class, ClientAPIModule.class, FluxModule.class}) 15 | public class ApplicationModule { 16 | Application mApplication; 17 | 18 | public ApplicationModule(Application application) { 19 | mApplication = application; 20 | } 21 | 22 | /** 23 | * 提供Application单例对象 24 | * 25 | * @return Application 26 | */ 27 | @Singleton 28 | @Provides 29 | public Application provideApplication() { 30 | return mApplication; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/lsxiao/com/zhihudailyrrd/inject/module/ClientApiModule.java: -------------------------------------------------------------------------------- 1 | package lsxiao.com.zhihudailyrrd.inject.module; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.GsonBuilder; 5 | 6 | import javax.inject.Singleton; 7 | 8 | import dagger.Module; 9 | import dagger.Provides; 10 | import lsxiao.com.zhihudailyrrd.protocol.ClientAPI; 11 | import okhttp3.OkHttpClient; 12 | import okhttp3.logging.HttpLoggingInterceptor; 13 | import retrofit2.Converter; 14 | import retrofit2.Retrofit; 15 | import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory; 16 | import retrofit2.converter.gson.GsonConverterFactory; 17 | 18 | /** 19 | * @author xiaolishang 20 | * @date 2015-11-03 23:28 21 | */ 22 | @Module 23 | public class ClientAPIModule { 24 | private static final String API_VERSION = "4"; 25 | private static final String BASE_URL = "http://news-at.zhihu.com/api/4/"; 26 | 27 | /** 28 | * 创建一个ClientAPI的实现类单例对象 29 | * 30 | * @param client OkHttpClient 31 | * @param converterFactory Converter.Factory 32 | * @return ClientAPI 33 | */ 34 | @Provides 35 | @Singleton 36 | public ClientAPI provideClientApi(OkHttpClient client, Converter.Factory converterFactory) { 37 | Retrofit retrofit = new Retrofit.Builder() 38 | .baseUrl(BASE_URL) 39 | .addConverterFactory(converterFactory) 40 | .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) 41 | .client(client) 42 | .build(); 43 | return retrofit.create(ClientAPI.class); 44 | } 45 | 46 | /** 47 | * 日志拦截器单例对象,用于OkHttp层对日志进行处理 48 | * 49 | * @return HttpLoggingInterceptor 50 | */ 51 | @Provides 52 | @Singleton 53 | public HttpLoggingInterceptor provideLogger() { 54 | HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(); 55 | interceptor.setLevel(HttpLoggingInterceptor.Level.NONE); 56 | return interceptor; 57 | } 58 | 59 | 60 | /** 61 | * Gson转换器单例对象 62 | * 63 | * @param gson Gson 64 | * @return Converter.Factory 65 | */ 66 | @Provides 67 | @Singleton 68 | public Converter.Factory provideConverter(Gson gson) { 69 | return GsonConverterFactory.create(gson); 70 | } 71 | 72 | 73 | /** 74 | * Gson 单例对象 75 | * 76 | * @return Gson 77 | */ 78 | @Provides 79 | @Singleton 80 | public Gson provideGson() { 81 | return new GsonBuilder().serializeNulls().create(); 82 | } 83 | 84 | /** 85 | * OkHttp客户端单例对象 86 | * 87 | * @param loggingInterceptor HttpLoggingInterceptor 88 | * @return OkHttpClient 89 | */ 90 | @Provides 91 | @Singleton 92 | public OkHttpClient provideClient(HttpLoggingInterceptor loggingInterceptor) { 93 | OkHttpClient client = new OkHttpClient.Builder() 94 | .addInterceptor(loggingInterceptor) 95 | .build(); 96 | return client; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /app/src/main/java/lsxiao/com/zhihudailyrrd/inject/module/DataLayerModule.java: -------------------------------------------------------------------------------- 1 | package lsxiao.com.zhihudailyrrd.inject.module; 2 | 3 | import javax.inject.Singleton; 4 | 5 | import dagger.Module; 6 | import dagger.Provides; 7 | import lsxiao.com.zhihudailyrrd.service.DailyManager; 8 | import lsxiao.com.zhihudailyrrd.service.base.DataLayer; 9 | 10 | /** 11 | * @author lsxiao 12 | * @date 2015-11-04 00:44 13 | */ 14 | @Module 15 | public class DataLayerModule { 16 | 17 | @Singleton 18 | @Provides 19 | public DailyManager provideDailyManager() { 20 | return new DailyManager(); 21 | } 22 | 23 | 24 | @Singleton 25 | @Provides 26 | public DataLayer provideDataLayer(DailyManager dailyManager) { 27 | return new DataLayer(dailyManager); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/lsxiao/com/zhihudailyrrd/inject/module/FluxModule.java: -------------------------------------------------------------------------------- 1 | package lsxiao.com.zhihudailyrrd.inject.module; 2 | 3 | import javax.inject.Singleton; 4 | 5 | import dagger.Module; 6 | import dagger.Provides; 7 | import lsxiao.com.zhihudailyrrd.flux.action.creator.ActionCreatorManager; 8 | import lsxiao.com.zhihudailyrrd.flux.action.creator.NewsActionCreator; 9 | import lsxiao.com.zhihudailyrrd.flux.dispatcher.Dispatcher; 10 | import lsxiao.com.zhihudailyrrd.service.base.DataLayer; 11 | 12 | /** 13 | * Flux 依赖 14 | * 15 | * @author lsxiao 16 | * @date 2015-11-04 00:44 17 | */ 18 | @Module 19 | public class FluxModule { 20 | 21 | 22 | /** 23 | * 提供分发器单例对象 24 | * 25 | * @return Dispatcher 26 | */ 27 | @Singleton 28 | @Provides 29 | public Dispatcher provideDispatcher() { 30 | return Dispatcher.instance(); 31 | } 32 | 33 | 34 | /** 35 | * 提供NewsActionCreator单例对象 36 | * 37 | * @param dispatcher Dispatcher 38 | * @param dataLayer DataLayer 39 | * @return NewsActionCreator 40 | */ 41 | @Singleton 42 | @Provides 43 | public NewsActionCreator provideNewsActionCreator(Dispatcher dispatcher, DataLayer dataLayer) { 44 | return new NewsActionCreator(dispatcher, dataLayer); 45 | } 46 | 47 | /** 48 | * 提供ActionCreatorManager单例对象 49 | * 50 | * @param newsActionCreator NewsActionCreator 51 | * @return ActionCreatorManager 52 | */ 53 | @Singleton 54 | @Provides 55 | public ActionCreatorManager provideActionCreatorManager(NewsActionCreator newsActionCreator) { 56 | return new ActionCreatorManager(newsActionCreator); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /app/src/main/java/lsxiao/com/zhihudailyrrd/model/News.java: -------------------------------------------------------------------------------- 1 | package lsxiao.com.zhihudailyrrd.model; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | import java.io.Serializable; 6 | import java.util.List; 7 | 8 | /** 9 | * @author lsxiao 10 | * @date 2015-11-03 22:40 11 | */ 12 | public class News implements Serializable { 13 | //新闻的 id 14 | @SerializedName("id") 15 | private Long mId; 16 | 17 | // HTML 格式的新闻 18 | @SerializedName("body") 19 | private String mBody; 20 | 21 | //图片的内容提供方。为了避免被起诉非法使用图片,在显示图片时最好附上其版权信息。 22 | @SerializedName("image_source") 23 | private String mImageSource; 24 | 25 | //新闻标题 26 | @SerializedName("title") 27 | private String mTitle; 28 | 29 | //获得的图片同 最新消息 获得的图片分辨率不同。这里获得的是在文章浏览界面中使用的大图。 30 | @SerializedName("image") 31 | private String mImage; 32 | 33 | //供在线查看内容与分享至 SNS 用的 URL 34 | @SerializedName("share_url") 35 | private String mShareUrl; 36 | 37 | // 供手机端的 WebView(UIWebView) 使用 38 | @SerializedName("js") 39 | private List mJsList; 40 | 41 | //供手机端的 WebView(UIWebView) 使用 42 | @SerializedName("css") 43 | private List mCssList; 44 | 45 | //供 Google Analytics 使用 46 | @SerializedName("ga_prefix") 47 | private String mGaPrefix; 48 | 49 | //这篇文章的推荐者 50 | @SerializedName("recommenders") 51 | private List mRecommenderList; 52 | 53 | // 新闻的类型 54 | @SerializedName("type") 55 | private String mType; 56 | 57 | /** 58 | * 在较为特殊的情况下,知乎日报可能将某个主题日报的站外文章推送至知乎日报首页。 59 | * 此时返回的 JSON 数据缺少 body,image-source,image,js 属性。 60 | * 多出 theme_name,editor_name,theme_id 三个属性。type 由 0 变为 1。 61 | */ 62 | 63 | //主题id 64 | @SerializedName("theme_id") 65 | private String mThemeId; 66 | 67 | //主题名 68 | @SerializedName("theme_name") 69 | private String mThemeName; 70 | 71 | //编辑者名 72 | @SerializedName("editor_name") 73 | private String mEditorName; 74 | 75 | //栏目 76 | @SerializedName("section") 77 | private Section mSection; 78 | 79 | /** 80 | * 栏目的信息 81 | */ 82 | public static class Section implements Serializable { 83 | //该栏目的 id 84 | @SerializedName("id") 85 | private Long mSectionId; 86 | 87 | //该栏目的名称 88 | @SerializedName("name") 89 | private String mName; 90 | 91 | //栏目的缩略图 92 | @SerializedName("thumbnail") 93 | private String mThumbnail; 94 | 95 | public Long getSectionId() { 96 | return mSectionId; 97 | } 98 | 99 | public String getName() { 100 | return mName; 101 | } 102 | 103 | public String getThumbnail() { 104 | return mThumbnail; 105 | } 106 | 107 | @Override 108 | public String toString() { 109 | return "Section{" + 110 | "mSectionId=" + mSectionId + 111 | ", mName='" + mName + '\'' + 112 | ", mThumbnail='" + mThumbnail + '\'' + 113 | '}'; 114 | } 115 | } 116 | 117 | public static class Recommender implements Serializable { 118 | //这篇文章的推荐者头像 119 | @SerializedName("avatar") 120 | private String mAvatarUrl; 121 | 122 | public String getAvatarUrl() { 123 | return mAvatarUrl; 124 | } 125 | } 126 | 127 | public Long getId() { 128 | return mId; 129 | } 130 | 131 | public String getBody() { 132 | return mBody; 133 | } 134 | 135 | public String getImageSource() { 136 | return mImageSource; 137 | } 138 | 139 | public String getTitle() { 140 | return mTitle; 141 | } 142 | 143 | public String getImage() { 144 | return mImage; 145 | } 146 | 147 | public String getShareUrl() { 148 | return mShareUrl; 149 | } 150 | 151 | public List getJsList() { 152 | return mJsList; 153 | } 154 | 155 | public List getCssList() { 156 | return mCssList; 157 | } 158 | 159 | 160 | public List getRecommenderList() { 161 | return mRecommenderList; 162 | } 163 | 164 | public String getType() { 165 | return mType; 166 | } 167 | 168 | public String getThemeId() { 169 | return mThemeId; 170 | } 171 | 172 | public String getThemeName() { 173 | return mThemeName; 174 | } 175 | 176 | public String getEditorName() { 177 | return mEditorName; 178 | } 179 | 180 | public Section getSection() { 181 | return mSection; 182 | } 183 | 184 | @Override 185 | public String toString() { 186 | return "News{" + 187 | "mId=" + mId + 188 | ", mBody='" + mBody + '\'' + 189 | ", mImageSource='" + mImageSource + '\'' + 190 | ", mTitle='" + mTitle + '\'' + 191 | ", mImage='" + mImage + '\'' + 192 | ", mShareUrl='" + mShareUrl + '\'' + 193 | ", mJsList=" + mJsList + 194 | ", mCssList=" + mCssList + 195 | ", mRecommenderList=" + mRecommenderList + 196 | ", mType='" + mType + '\'' + 197 | ", mThemeId='" + mThemeId + '\'' + 198 | ", mThemeName='" + mThemeName + '\'' + 199 | ", mEditorName='" + mEditorName + '\'' + 200 | ", mSection=" + mSection + 201 | '}'; 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /app/src/main/java/lsxiao/com/zhihudailyrrd/model/StartImage.java: -------------------------------------------------------------------------------- 1 | package lsxiao.com.zhihudailyrrd.model; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | import java.io.Serializable; 6 | 7 | /** 8 | * @author lsxiao 9 | * @date 2015-11-03 23:06 10 | */ 11 | public class StartImage implements Serializable { 12 | //图像的 URL 13 | @SerializedName("img") 14 | private String url; 15 | 16 | //供显示的图片版权信息 17 | @SerializedName("text") 18 | private String mText; 19 | 20 | public String getUrl() { 21 | return url; 22 | } 23 | 24 | public String getText() { 25 | return mText; 26 | } 27 | 28 | @Override 29 | public String toString() { 30 | return "StartImage{" + 31 | "mText='" + mText + '\'' + 32 | ", url='" + url + '\'' + 33 | '}'; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/lsxiao/com/zhihudailyrrd/model/TodayNews.java: -------------------------------------------------------------------------------- 1 | package lsxiao.com.zhihudailyrrd.model; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | import java.io.Serializable; 6 | import java.util.List; 7 | 8 | /** 9 | * 今日热文 10 | * 11 | * @author lsxiao 12 | * @date 2015-11-03 22:32 13 | */ 14 | public class TodayNews implements Serializable { 15 | //日期,唯一,重复的直接覆盖 16 | @SerializedName("date") 17 | private String mDate; 18 | 19 | //当日新闻 20 | @SerializedName("stories") 21 | private List mStories; 22 | 23 | //顶部ViewPager滚动显示的新闻 24 | @SerializedName("top_stories") 25 | private List mTopStories; 26 | 27 | public String getDate() { 28 | return mDate; 29 | } 30 | 31 | public List getStories() { 32 | return mStories; 33 | } 34 | 35 | public List getTopStories() { 36 | return mTopStories; 37 | } 38 | 39 | @Override 40 | public String toString() { 41 | return "TodayNews{" + 42 | "mDate='" + mDate + '\'' + 43 | ", mStories=" + mStories + 44 | ", mTopStories=" + mTopStories + 45 | '}'; 46 | } 47 | 48 | public static class Story implements Serializable { 49 | //id 50 | @SerializedName("id") 51 | private long mId; 52 | 53 | //标题 54 | @SerializedName("title") 55 | private String mTitle; 56 | 57 | //图像地址(官方 API 使用数组形式。目前暂未有使用多张图片的情形出现,曾见无 images 属性的情况,请在使用中注意 ) 58 | @SerializedName("images") 59 | private List mImageUrls; 60 | 61 | //图像地址 只有topStories才有 62 | @SerializedName("image") 63 | private String mImageUrl; 64 | 65 | //类型,作用未知 66 | @SerializedName("type") 67 | private int mType; 68 | 69 | //供 Google Analytics 使用 70 | @SerializedName("ga_prefix") 71 | private String mGaPrefix; 72 | 73 | //消息是否包含多张图片(仅出现在包含多图的新闻中) 74 | @SerializedName("multipic") 75 | private boolean mMultiPic; 76 | 77 | public long getId() { 78 | return mId; 79 | } 80 | 81 | public String getTitle() { 82 | return mTitle; 83 | } 84 | 85 | public List getImageUrls() { 86 | return mImageUrls; 87 | } 88 | 89 | public String getImageUrl() { 90 | return mImageUrl; 91 | } 92 | 93 | public int getType() { 94 | return mType; 95 | } 96 | 97 | public String getGaPrefix() { 98 | return mGaPrefix; 99 | } 100 | 101 | public boolean isMultiPic() { 102 | return mMultiPic; 103 | } 104 | 105 | @Override 106 | public String toString() { 107 | return "Story{" + 108 | "mId=" + mId + 109 | ", mTitle='" + mTitle + '\'' + 110 | ", mImageUrls=" + mImageUrls + 111 | ", mImageUrl='" + mImageUrl + '\'' + 112 | ", mType=" + mType + 113 | ", mGaPrefix='" + mGaPrefix + '\'' + 114 | ", mMultiPic=" + mMultiPic + 115 | '}'; 116 | } 117 | } 118 | 119 | } 120 | -------------------------------------------------------------------------------- /app/src/main/java/lsxiao/com/zhihudailyrrd/protocol/ClientApi.java: -------------------------------------------------------------------------------- 1 | package lsxiao.com.zhihudailyrrd.protocol; 2 | 3 | import lsxiao.com.zhihudailyrrd.model.TodayNews; 4 | import lsxiao.com.zhihudailyrrd.model.News; 5 | import lsxiao.com.zhihudailyrrd.model.StartImage; 6 | import retrofit2.http.GET; 7 | import retrofit2.http.Path; 8 | import rx.Observable; 9 | 10 | /** 11 | * @author lsxiao 12 | * @date 2015-11-03 22:28 13 | */ 14 | public interface ClientAPI { 15 | 16 | /** 17 | * FIELD 18 | */ 19 | 20 | String FIELD_NEWS_ID = "newsId"; 21 | 22 | 23 | /** 24 | * URL 25 | */ 26 | 27 | //获取启动页面图片 28 | String URL_GET_START_IMAGE = "start-image/1080*1776"; 29 | 30 | //获取最新日报新闻列表 31 | String URL_GET_LATEST_NEWS = "news/latest"; 32 | 33 | //获取新闻 34 | String URL_GET_NEWS = "news/{newsId}"; 35 | 36 | 37 | /** 38 | * 获取今日日报新闻列表 39 | * 40 | * @return TodayNews 41 | */ 42 | @GET(URL_GET_LATEST_NEWS) 43 | Observable getTodayNews(); 44 | 45 | /** 46 | * 获取启动图片 47 | * 48 | * @return StartImage 49 | */ 50 | @GET(URL_GET_START_IMAGE) 51 | Observable getStartImage(); 52 | 53 | /** 54 | * 获取新闻 55 | * 56 | * @param newsId long 57 | * @return News 58 | */ 59 | @GET(URL_GET_NEWS) 60 | Observable getNews(@Path(FIELD_NEWS_ID) long newsId); 61 | } 62 | -------------------------------------------------------------------------------- /app/src/main/java/lsxiao/com/zhihudailyrrd/service/DailyManager.java: -------------------------------------------------------------------------------- 1 | package lsxiao.com.zhihudailyrrd.service; 2 | 3 | 4 | import lsxiao.com.zhihudailyrrd.base.BundleKey; 5 | import lsxiao.com.zhihudailyrrd.model.News; 6 | import lsxiao.com.zhihudailyrrd.model.StartImage; 7 | import lsxiao.com.zhihudailyrrd.model.TodayNews; 8 | import lsxiao.com.zhihudailyrrd.service.base.BaseManager; 9 | import lsxiao.com.zhihudailyrrd.service.base.DataLayer; 10 | import lsxiao.com.zhihudailyrrd.util.SpUtil; 11 | import rx.Observable; 12 | import rx.Subscriber; 13 | 14 | /** 15 | * @author lsxiao 16 | * date 2015-11-03 22:28 17 | */ 18 | public class DailyManager extends BaseManager implements DataLayer.DailyService { 19 | @Override 20 | public Observable getTodayNews() { 21 | return getApi().getTodayNews(); 22 | } 23 | 24 | @Override 25 | public Observable getStartImage() { 26 | return getApi().getStartImage(); 27 | } 28 | 29 | @Override 30 | public Observable getNews(long newsId) { 31 | return getApi().getNews(newsId); 32 | } 33 | 34 | @Override 35 | public void cacheTodayNews(final TodayNews todayNews) { 36 | SpUtil.saveOrUpdate(BundleKey.LATEST_DATE, todayNews.getDate()); 37 | SpUtil.saveOrUpdate(todayNews.getDate(), getGson().toJson(todayNews)); 38 | } 39 | 40 | @Override 41 | public void cacheNews(final News news) { 42 | SpUtil.saveOrUpdate(String.valueOf(news.getId()), getGson().toJson(news)); 43 | } 44 | 45 | @Override 46 | public Observable getLatestTodayNews() { 47 | return Observable.create(new Observable.OnSubscribe() { 48 | @Override 49 | public void call(Subscriber subscriber) { 50 | try { 51 | subscriber.onStart(); 52 | String latestDate = SpUtil.find(BundleKey.LATEST_DATE); 53 | String json = SpUtil.find(latestDate); 54 | TodayNews todayNews = getGson().fromJson(json, TodayNews.class); 55 | subscriber.onNext(todayNews); 56 | subscriber.onCompleted(); 57 | } catch (Exception e) { 58 | subscriber.onError(e); 59 | } 60 | } 61 | }); 62 | } 63 | 64 | @Override 65 | public Observable getLocalNews(final String id) { 66 | return Observable.create(new Observable.OnSubscribe() { 67 | @Override 68 | public void call(Subscriber subscriber) { 69 | try { 70 | subscriber.onStart(); 71 | String json = SpUtil.find(id); 72 | News news = getGson().fromJson(json, News.class); 73 | subscriber.onNext(news); 74 | subscriber.onCompleted(); 75 | } catch (Exception e) { 76 | subscriber.onError(e); 77 | } 78 | } 79 | }); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /app/src/main/java/lsxiao/com/zhihudailyrrd/service/base/BaseManager.java: -------------------------------------------------------------------------------- 1 | package lsxiao.com.zhihudailyrrd.service.base; 2 | 3 | 4 | import com.google.gson.Gson; 5 | 6 | import javax.inject.Inject; 7 | 8 | import lsxiao.com.zhihudailyrrd.inject.component.ApplicationComponent; 9 | import lsxiao.com.zhihudailyrrd.protocol.ClientAPI; 10 | 11 | /** 12 | * @author lsxiao 13 | * @date 2015-11-03 22:28 14 | */ 15 | public abstract class BaseManager { 16 | @Inject 17 | ClientAPI mApi; 18 | @Inject 19 | Gson mGson; 20 | 21 | public BaseManager() { 22 | ApplicationComponent.Instance.get().inject(this); 23 | } 24 | 25 | public ClientAPI getApi() { 26 | return mApi; 27 | } 28 | 29 | public Gson getGson() { 30 | return mGson; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/lsxiao/com/zhihudailyrrd/service/base/DataLayer.java: -------------------------------------------------------------------------------- 1 | package lsxiao.com.zhihudailyrrd.service.base; 2 | 3 | 4 | import lsxiao.com.zhihudailyrrd.model.TodayNews; 5 | import lsxiao.com.zhihudailyrrd.model.News; 6 | import lsxiao.com.zhihudailyrrd.model.StartImage; 7 | import rx.Observable; 8 | 9 | /** 10 | * @author lsxiao 11 | * @date 2015-11-03 22:28 12 | */ 13 | public class DataLayer { 14 | DailyService mDailyService; 15 | 16 | public DataLayer(DailyService dailyService) { 17 | mDailyService = dailyService; 18 | } 19 | 20 | public DailyService getDailyService() { 21 | return mDailyService; 22 | } 23 | 24 | public interface DailyService { 25 | 26 | /** 27 | * 获取最新日报新闻列表 28 | * 29 | * @return TodayNews 30 | */ 31 | Observable getTodayNews(); 32 | 33 | /** 34 | * 获取启动图片 35 | * 36 | * @return StartImage 37 | */ 38 | Observable getStartImage(); 39 | 40 | /** 41 | * 获取新闻 42 | * 43 | * @param newsId long 44 | * @return News 45 | */ 46 | Observable getNews(long newsId); 47 | 48 | /** 49 | * 获取本地新闻 50 | * 51 | * @param id string 52 | * @return News 53 | */ 54 | Observable getLocalNews(final String id); 55 | 56 | 57 | /** 58 | * 获取本地今日热文 59 | * 60 | * @return TodayNews 61 | */ 62 | Observable getLatestTodayNews(); 63 | 64 | 65 | /** 66 | * 缓存新闻 67 | * 68 | * @param news News 69 | * @return Void 70 | */ 71 | void cacheNews(final News news); 72 | 73 | 74 | /** 75 | * 缓存今日热文列表 76 | * 77 | * @param todayNews TodayNews 78 | * @return Void 79 | */ 80 | void cacheTodayNews(final TodayNews todayNews); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /app/src/main/java/lsxiao/com/zhihudailyrrd/ui/Activity/HomeActivity.java: -------------------------------------------------------------------------------- 1 | package lsxiao.com.zhihudailyrrd.ui.Activity; 2 | 3 | import android.os.Bundle; 4 | import android.support.v4.app.Fragment; 5 | import android.support.v4.app.FragmentTransaction; 6 | import android.widget.Toast; 7 | 8 | import lsxiao.com.zhihudailyrrd.R; 9 | import lsxiao.com.zhihudailyrrd.base.BaseActivity; 10 | import lsxiao.com.zhihudailyrrd.ui.Fragment.NewsListFragment; 11 | import lsxiao.com.zhihudailyrrd.util.ExitClickUtil; 12 | 13 | /** 14 | * @author lsxiao 15 | * @date 2015-11-03 22:28 16 | */ 17 | public class HomeActivity extends BaseActivity { 18 | 19 | @Override 20 | protected int getLayoutId() { 21 | return R.layout.activity_home; 22 | } 23 | 24 | @Override 25 | protected void afterCreate(Bundle savedInstanceState) { 26 | Fragment fragment = getSupportFragmentManager().findFragmentByTag(NewsListFragment.TAG); 27 | if (fragment == null) { 28 | fragment = NewsListFragment.newInstance(); 29 | FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); 30 | ft.replace(R.id.fl_container, fragment, NewsListFragment.TAG); 31 | ft.commit(); 32 | } 33 | } 34 | 35 | @Override 36 | public void onBackPressed() { 37 | if (!ExitClickUtil.isClickAgain()) { 38 | Toast.makeText(this, getString(R.string.click_again_to_exit_app), Toast.LENGTH_SHORT).show(); 39 | return; 40 | } 41 | super.onBackPressed(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/lsxiao/com/zhihudailyrrd/ui/Activity/NewsDetailActivity.java: -------------------------------------------------------------------------------- 1 | package lsxiao.com.zhihudailyrrd.ui.Activity; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.support.v4.app.Fragment; 7 | import android.support.v4.app.FragmentManager; 8 | import android.support.v4.app.FragmentTransaction; 9 | import android.view.MenuItem; 10 | 11 | import lsxiao.com.zhihudailyrrd.R; 12 | import lsxiao.com.zhihudailyrrd.base.BaseActivity; 13 | import lsxiao.com.zhihudailyrrd.base.BundleKey; 14 | import lsxiao.com.zhihudailyrrd.model.TodayNews; 15 | import lsxiao.com.zhihudailyrrd.ui.Fragment.NewsDetailFragment; 16 | 17 | public class NewsDetailActivity extends BaseActivity { 18 | @Override 19 | protected int getLayoutId() { 20 | return R.layout.activity_news; 21 | } 22 | 23 | public static void start(Context context, TodayNews.Story story) { 24 | Intent intent = new Intent(context, NewsDetailActivity.class); 25 | intent.putExtra(BundleKey.STORY, story); 26 | context.startActivity(intent); 27 | } 28 | 29 | @Override 30 | protected void afterCreate(Bundle savedInstanceState) { 31 | TodayNews.Story story = (TodayNews.Story) getIntent().getSerializableExtra(BundleKey.STORY); 32 | showNewsFragment(story); 33 | } 34 | 35 | private void showNewsFragment(TodayNews.Story story) { 36 | Fragment fragment = getSupportFragmentManager().findFragmentByTag(NewsDetailFragment.TAG); 37 | if (fragment == null) { 38 | fragment = NewsDetailFragment.newInstance(story); 39 | } 40 | FragmentManager fm = getSupportFragmentManager(); 41 | FragmentTransaction ft = fm.beginTransaction(); 42 | ft.replace(R.id.rl_news_container, fragment, NewsDetailFragment.TAG); 43 | ft.commit(); 44 | } 45 | 46 | @Override 47 | public boolean onOptionsItemSelected(MenuItem item) { 48 | switch (item.getItemId()) { 49 | case android.R.id.home: 50 | finish(); 51 | return true; 52 | } 53 | return super.onOptionsItemSelected(item); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/java/lsxiao/com/zhihudailyrrd/ui/Adapter/NewsListAdapter.java: -------------------------------------------------------------------------------- 1 | package lsxiao.com.zhihudailyrrd.ui.Adapter; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.TextView; 10 | 11 | import com.daimajia.slider.library.Indicators.PagerIndicator; 12 | import com.daimajia.slider.library.SliderLayout; 13 | import com.daimajia.slider.library.SliderTypes.BaseSliderView; 14 | import com.squareup.picasso.Picasso; 15 | 16 | import java.util.List; 17 | 18 | import de.hdodenhof.circleimageview.CircleImageView; 19 | import lsxiao.com.zhihudailyrrd.R; 20 | import lsxiao.com.zhihudailyrrd.base.BundleKey; 21 | import lsxiao.com.zhihudailyrrd.model.TodayNews; 22 | import lsxiao.com.zhihudailyrrd.view.TextSliderView; 23 | 24 | /** 25 | * @author lsxiao 26 | * @date 2015-11-04 23:07 27 | */ 28 | public class NewsListAdapter extends RecyclerView.Adapter { 29 | private List mStories; 30 | private Context mContext; 31 | private View.OnClickListener mListener; 32 | private BaseSliderView.OnSliderClickListener mSliderClickListener; 33 | private List mHeaderStories; 34 | private static final int TYPE_HEADER = 0; 35 | private static final int TYPE_ITEM = 1; 36 | 37 | public NewsListAdapter(Context context, List stories, List headerStories, View.OnClickListener listener, BaseSliderView.OnSliderClickListener sliderClickListener) { 38 | mContext = context; 39 | mStories = stories; 40 | mListener = listener; 41 | mHeaderStories = headerStories; 42 | mSliderClickListener = sliderClickListener; 43 | } 44 | 45 | @Override 46 | public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 47 | View itemView; 48 | if (viewType == TYPE_ITEM) { 49 | itemView = LayoutInflater.from(mContext).inflate(R.layout.list_item_news, parent, false); 50 | return new ItemVH(itemView); 51 | } else if (viewType == TYPE_HEADER) { 52 | itemView = LayoutInflater.from(mContext).inflate(R.layout.list_item_header, parent, false); 53 | return new HeaderVH(itemView); 54 | } 55 | return null; 56 | } 57 | 58 | @Override 59 | public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { 60 | if (holder instanceof ItemVH) { 61 | ItemVH itemVH = (ItemVH) holder; 62 | final TodayNews.Story story = mStories.get(position - (mHeaderStories == null || mHeaderStories.isEmpty() ? 0 : 1)); 63 | //设置新闻标题 64 | itemVH.mTvTitle.setText(story.getTitle()); 65 | //加载图片 66 | Picasso.with(mContext) 67 | .load(story.getImageUrls().get(0)) 68 | .placeholder(R.drawable.ic_placeholder) 69 | .into(itemVH.mIvNewsThumbnail); 70 | itemVH.itemView.setOnClickListener(mListener); 71 | } else if (holder instanceof HeaderVH) { 72 | HeaderVH headerVH = (HeaderVH) holder; 73 | headerVH.mSlHeader.removeAllSliders(); 74 | for (int i = 0; i < mHeaderStories.size(); i++) { 75 | final TodayNews.Story story = mHeaderStories.get(i); 76 | 77 | //附加信息 78 | Bundle bundle = new Bundle(); 79 | bundle.putSerializable(BundleKey.STORY, story); 80 | 81 | TextSliderView textSliderView = new TextSliderView(mContext); 82 | textSliderView.setOnSliderClickListener(mSliderClickListener); 83 | textSliderView 84 | .description(story.getTitle()) 85 | .setScaleType(BaseSliderView.ScaleType.CenterCrop) 86 | .image(story.getImageUrl()) 87 | .bundle(bundle); 88 | headerVH.mSlHeader.addSlider(textSliderView); 89 | } 90 | } 91 | } 92 | 93 | @Override 94 | public int getItemCount() { 95 | return mStories.size() + (mHeaderStories == null || mHeaderStories.isEmpty() ? 0 : 1); 96 | } 97 | 98 | public List getStories() { 99 | return mStories; 100 | } 101 | 102 | public TodayNews.Story getItemData(int position) { 103 | position = getItemCount() == mHeaderStories.size() ? position : position - 1; 104 | return getStories().get(position); 105 | } 106 | 107 | public TodayNews.Story getHeaderData(int position) { 108 | return getHeaderStories().get(position); 109 | } 110 | 111 | public List getHeaderStories() { 112 | return mHeaderStories; 113 | } 114 | 115 | public void setStories(List stories, List topStories) { 116 | mStories = stories; 117 | mHeaderStories = topStories; 118 | } 119 | 120 | @Override 121 | public int getItemViewType(int position) { 122 | if (position == 0) { 123 | return TYPE_HEADER; 124 | } else { 125 | return TYPE_ITEM; 126 | } 127 | } 128 | 129 | public static class ItemVH extends RecyclerView.ViewHolder { 130 | public CircleImageView mIvNewsThumbnail; 131 | public TextView mTvTitle; 132 | 133 | public ItemVH(View itemView) { 134 | super(itemView); 135 | mIvNewsThumbnail = (CircleImageView) itemView.findViewById(R.id.iv_story_thumbnail); 136 | mTvTitle = (TextView) itemView.findViewById(R.id.tv_title); 137 | } 138 | } 139 | 140 | 141 | public static class HeaderVH extends RecyclerView.ViewHolder { 142 | public SliderLayout mSlHeader; 143 | public PagerIndicator mPagerIndicator; 144 | 145 | public HeaderVH(View itemView) { 146 | super(itemView); 147 | mSlHeader = (SliderLayout) itemView.findViewById(R.id.sl_header); 148 | mPagerIndicator = (PagerIndicator) itemView.findViewById(R.id.pi_header); 149 | mSlHeader.setCustomIndicator(mPagerIndicator); 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /app/src/main/java/lsxiao/com/zhihudailyrrd/ui/Fragment/NewsDetailFragment.java: -------------------------------------------------------------------------------- 1 | package lsxiao.com.zhihudailyrrd.ui.Fragment; 2 | 3 | import android.os.Build; 4 | import android.os.Bundle; 5 | import android.support.design.widget.CollapsingToolbarLayout; 6 | import android.support.v4.app.Fragment; 7 | import android.support.v4.widget.ContentLoadingProgressBar; 8 | import android.support.v4.widget.NestedScrollView; 9 | import android.support.v7.app.ActionBar; 10 | import android.support.v7.app.AppCompatActivity; 11 | import android.support.v7.widget.Toolbar; 12 | import android.view.View; 13 | import android.webkit.WebSettings; 14 | import android.webkit.WebView; 15 | import android.widget.ImageView; 16 | import android.widget.TextView; 17 | 18 | import com.lsxiao.apllo.annotations.Receive; 19 | import com.squareup.picasso.Picasso; 20 | 21 | import butterknife.Bind; 22 | import lsxiao.com.zhihudailyrrd.R; 23 | import lsxiao.com.zhihudailyrrd.base.BaseFragment; 24 | import lsxiao.com.zhihudailyrrd.base.BundleKey; 25 | import lsxiao.com.zhihudailyrrd.base.Events; 26 | import lsxiao.com.zhihudailyrrd.flux.store.NewsDetailStore; 27 | import lsxiao.com.zhihudailyrrd.flux.store.base.BaseStore; 28 | import lsxiao.com.zhihudailyrrd.model.News; 29 | import lsxiao.com.zhihudailyrrd.model.TodayNews; 30 | import lsxiao.com.zhihudailyrrd.util.HtmlUtil; 31 | 32 | /** 33 | * @author lsxiao 34 | * date 2015-11-05 10:27 35 | */ 36 | public class NewsDetailFragment extends BaseFragment implements View.OnClickListener { 37 | @Bind(R.id.wv_news) 38 | WebView mWvNews; 39 | @Bind(R.id.cpb_loading) 40 | ContentLoadingProgressBar mCpbLoading; 41 | @Bind(R.id.iv_header) 42 | ImageView mImageView; 43 | @Bind(R.id.tv_source) 44 | TextView mTvSource; 45 | @Bind(R.id.collapsingToolbarLayout) 46 | CollapsingToolbarLayout mCollapsingToolbarLayout; 47 | @Bind(R.id.toolbar) 48 | Toolbar mToolbar; 49 | @Bind(R.id.nested_view) 50 | NestedScrollView mNestedScrollView; 51 | @Bind(R.id.tv_load_empty) 52 | TextView mTvLoadEmpty; 53 | @Bind(R.id.tv_load_error) 54 | TextView mTvLoadError; 55 | private TodayNews.Story mStory; 56 | private News mNews; 57 | private NewsDetailStore mNewsDetailStore; 58 | 59 | @Override 60 | protected int getLayoutId() { 61 | return R.layout.fragment_news_detail; 62 | } 63 | 64 | public static Fragment newInstance(TodayNews.Story story) { 65 | Bundle bundle = new Bundle(); 66 | bundle.putSerializable(BundleKey.STORY, story); 67 | Fragment Fragment = new NewsDetailFragment(); 68 | Fragment.setArguments(bundle); 69 | return Fragment; 70 | } 71 | 72 | @Override 73 | protected void afterCreate(Bundle savedInstanceState) { 74 | mStory = (TodayNews.Story) getArguments().getSerializable(BundleKey.STORY); 75 | init(); 76 | dispatchFetchDetailNews(); 77 | } 78 | 79 | /** 80 | * 初始化 81 | */ 82 | private void init() { 83 | AppCompatActivity activity = (AppCompatActivity) getActivity(); 84 | activity.setSupportActionBar(mToolbar); 85 | ActionBar actionBar = activity.getSupportActionBar(); 86 | if (actionBar != null) { 87 | actionBar.setDisplayHomeAsUpEnabled(true); 88 | } 89 | 90 | mTvLoadError.setOnClickListener(this); 91 | 92 | setHasOptionsMenu(true); 93 | 94 | mNestedScrollView.setOverScrollMode(View.OVER_SCROLL_NEVER); 95 | mWvNews.setOverScrollMode(View.OVER_SCROLL_NEVER); 96 | mWvNews.getSettings().setLoadsImagesAutomatically(true); 97 | //设置 缓存模式 98 | mWvNews.getSettings().setCacheMode(WebSettings.LOAD_DEFAULT); 99 | // 开启 DOM storage API 功能 100 | mWvNews.getSettings().setDomStorageEnabled(true); 101 | 102 | //为可折叠toolbar设置标题 103 | mCollapsingToolbarLayout.setTitle(getString(R.string.app_name)); 104 | 105 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 106 | mNestedScrollView.setElevation(0); 107 | mWvNews.setElevation(0); 108 | } 109 | } 110 | 111 | /** 112 | * 接收Store状态改变事件 113 | */ 114 | @Receive(tag = Events.NEWS_DETAIL_FETCH_CHANGE) 115 | public void onDetailFetchChanged(NewsDetailStore.FetchChangeEvent fetchChangeEvent) { 116 | render(); 117 | } 118 | 119 | /** 120 | * 渲染UI 121 | */ 122 | @SuppressWarnings("ResourceType") 123 | private void render() { 124 | mTvLoadEmpty.setVisibility(mNewsDetailStore.getEmptyViewVis()); 125 | mTvLoadError.setVisibility(mNewsDetailStore.getErrorViewVis()); 126 | mCpbLoading.setVisibility(mNewsDetailStore.isShowLoadView() ? View.VISIBLE : View.GONE); 127 | if (!mNewsDetailStore.isEmpty() && mNewsDetailStore.isFinish()) { 128 | News news = mNewsDetailStore.getData(); 129 | Picasso.with(getActivity()).load(news.getImage()).into(mImageView); 130 | mTvSource.setText(news.getImageSource()); 131 | String htmlData = HtmlUtil.createHtmlData(news); 132 | mWvNews.loadData(htmlData, HtmlUtil.MIME_TYPE, HtmlUtil.ENCODING); 133 | } 134 | } 135 | 136 | 137 | /** 138 | * 分发拉取详细新闻action 139 | */ 140 | private void dispatchFetchDetailNews() { 141 | getActionCreatorManager().getNewsActionCreator().fetchDetailNews(mStory); 142 | } 143 | 144 | /** 145 | * Store的订阅和注销由基类控制,这里只需要返回一个Store实例 146 | * 这个方法在afterCreate()之前就会被调用 147 | * 148 | * @return BaseStore 149 | */ 150 | @Override 151 | protected BaseStore getStore() { 152 | if (mNewsDetailStore == null) { 153 | mNewsDetailStore = new NewsDetailStore(); 154 | } 155 | return mNewsDetailStore; 156 | } 157 | 158 | @Override 159 | public void onClick(View v) { 160 | final int id = v.getId(); 161 | switch (id) { 162 | case R.id.tv_load_error: { 163 | dispatchFetchDetailNews(); 164 | break; 165 | } 166 | default: 167 | } 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /app/src/main/java/lsxiao/com/zhihudailyrrd/ui/Fragment/NewsListFragment.java: -------------------------------------------------------------------------------- 1 | package lsxiao.com.zhihudailyrrd.ui.Fragment; 2 | 3 | import android.os.Bundle; 4 | import android.support.v4.app.Fragment; 5 | import android.support.v4.widget.SwipeRefreshLayout; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.support.v7.widget.RecyclerView; 8 | import android.support.v7.widget.StaggeredGridLayoutManager; 9 | import android.support.v7.widget.Toolbar; 10 | import android.view.View; 11 | import android.view.ViewTreeObserver; 12 | import android.widget.TextView; 13 | 14 | import com.daimajia.slider.library.SliderTypes.BaseSliderView; 15 | import com.lsxiao.apllo.annotations.Receive; 16 | 17 | import java.util.ArrayList; 18 | 19 | import butterknife.Bind; 20 | import lsxiao.com.zhihudailyrrd.R; 21 | import lsxiao.com.zhihudailyrrd.base.BaseFragment; 22 | import lsxiao.com.zhihudailyrrd.base.BundleKey; 23 | import lsxiao.com.zhihudailyrrd.base.DividerItemDecoration; 24 | import lsxiao.com.zhihudailyrrd.base.Events; 25 | import lsxiao.com.zhihudailyrrd.flux.store.NewsListStore; 26 | import lsxiao.com.zhihudailyrrd.flux.store.base.BaseStore; 27 | import lsxiao.com.zhihudailyrrd.model.TodayNews; 28 | import lsxiao.com.zhihudailyrrd.ui.Activity.NewsDetailActivity; 29 | import lsxiao.com.zhihudailyrrd.ui.Adapter.NewsListAdapter; 30 | 31 | /** 32 | * @author lsxiao 33 | * date 2015-11-03 23:43 34 | */ 35 | public class NewsListFragment extends BaseFragment implements SwipeRefreshLayout.OnRefreshListener, View.OnClickListener, BaseSliderView.OnSliderClickListener { 36 | public static final int SPAN_COUNT = 1;//列数 37 | 38 | NewsListAdapter mNewsListAdapter; 39 | @Bind(R.id.toolbar) 40 | Toolbar mToolbar; 41 | @Bind(R.id.rcv_news_list) 42 | RecyclerView mRcvNewsList; 43 | @Bind(R.id.srl_news_list) 44 | SwipeRefreshLayout mSrlNewsList; 45 | @Bind(R.id.tv_load_empty) 46 | TextView mTvLoadEmpty; 47 | @Bind(R.id.tv_load_error) 48 | TextView mTvLoadError; 49 | 50 | NewsListStore mNewsListStore; 51 | 52 | @Override 53 | protected int getLayoutId() { 54 | return R.layout.fragment_news_list; 55 | } 56 | 57 | @Override 58 | protected void afterCreate(Bundle savedInstanceState) { 59 | init(); 60 | mRootView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { 61 | @Override 62 | public void onGlobalLayout() { 63 | mRootView.getViewTreeObserver().removeGlobalOnLayoutListener(this); 64 | dispatchFetchListNews(); 65 | } 66 | }); 67 | } 68 | 69 | @Override 70 | protected BaseStore getStore() { 71 | if (mNewsListStore == null) { 72 | mNewsListStore = new NewsListStore(); 73 | } 74 | return mNewsListStore; 75 | } 76 | 77 | public static Fragment newInstance() { 78 | return new NewsListFragment(); 79 | } 80 | 81 | 82 | private void init() { 83 | mToolbar.setTitle(getString(R.string.today_news)); 84 | AppCompatActivity activity = (AppCompatActivity) getActivity(); 85 | activity.setSupportActionBar(mToolbar); 86 | 87 | mTvLoadError.setOnClickListener(this); 88 | 89 | //为下拉刷新组件绑定监听器 90 | mSrlNewsList.setOnRefreshListener(this); 91 | 92 | //为下拉刷新组件设置CircleProgress主色调 93 | mSrlNewsList.setColorSchemeColors(getResources().getColor(R.color.color_primary)); 94 | 95 | //实例化布局管理器 96 | RecyclerView.LayoutManager manager = new StaggeredGridLayoutManager(SPAN_COUNT, StaggeredGridLayoutManager.VERTICAL); 97 | mRcvNewsList.setLayoutManager(manager); 98 | 99 | //初始化并绑定适配器 100 | mNewsListAdapter = new NewsListAdapter(getActivity(), new ArrayList(), new ArrayList(), 101 | this, this); 102 | mRcvNewsList.setAdapter(mNewsListAdapter); 103 | 104 | //设置ItemView的divider 105 | mRcvNewsList.addItemDecoration(new DividerItemDecoration(getActivity(), 106 | DividerItemDecoration.VERTICAL_LIST)); 107 | } 108 | 109 | 110 | //拉取的数据改变事件 111 | @Receive(tag = Events.NEWS_LIST_FETCH_CHANGE) 112 | public void onListNewsFetchChanged(NewsListStore.ChangeEvent fetchChangeEvent) { 113 | render(); 114 | } 115 | 116 | 117 | /** 118 | * 渲染UI 119 | */ 120 | @SuppressWarnings("ResourceType") 121 | private void render() { 122 | mSrlNewsList.setRefreshing(mNewsListStore.isShowLoadView()); 123 | mTvLoadEmpty.setVisibility(mNewsListStore.getEmptyViewVis()); 124 | mTvLoadError.setVisibility(mNewsListStore.getErrorViewVis()); 125 | if (!mNewsListStore.isEmpty() && mNewsListStore.isFinish()) { 126 | TodayNews todayNews = mNewsListStore.getTodayNews(); 127 | mNewsListAdapter.setStories(todayNews.getStories(), todayNews.getTopStories()); 128 | mNewsListAdapter.notifyDataSetChanged(); 129 | } 130 | } 131 | 132 | @Override 133 | public void onRefresh() { 134 | dispatchFetchListNews(); 135 | } 136 | 137 | /** 138 | * 分发拉取列表新闻action 139 | */ 140 | private void dispatchFetchListNews() { 141 | getActionCreatorManager().getNewsActionCreator().fetchListNews(); 142 | } 143 | 144 | @Override 145 | public void onClick(View v) { 146 | final int id = v.getId(); 147 | switch (id) { 148 | case R.id.tv_load_error: { 149 | dispatchFetchListNews(); 150 | break; 151 | } 152 | default: { 153 | final int position = mRcvNewsList.getChildAdapterPosition(v); 154 | if (RecyclerView.NO_POSITION != position) { 155 | nav2NewsDetailActivity(position); 156 | } 157 | } 158 | } 159 | } 160 | 161 | private void nav2NewsDetailActivity(TodayNews.Story story) { 162 | NewsDetailActivity.start(getActivity(), story); 163 | } 164 | 165 | private void nav2NewsDetailActivity(int position) { 166 | TodayNews.Story story = mNewsListAdapter.getItemData(position); 167 | nav2NewsDetailActivity(story); 168 | } 169 | 170 | @Override 171 | public void onSliderClick(BaseSliderView slider) { 172 | TodayNews.Story story = (TodayNews.Story) slider.getBundle().getSerializable(BundleKey.STORY); 173 | if (story != null) { 174 | nav2NewsDetailActivity(story); 175 | } 176 | } 177 | 178 | @Override 179 | public void onDestroyView() { 180 | super.onDestroyView(); 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /app/src/main/java/lsxiao/com/zhihudailyrrd/util/AppContextUtil.java: -------------------------------------------------------------------------------- 1 | package lsxiao.com.zhihudailyrrd.util; 2 | 3 | import android.content.Context; 4 | 5 | import lsxiao.com.zhihudailyrrd.inject.component.ApplicationComponent; 6 | 7 | /** 8 | * @author lsxiao 9 | * @date 2015-11-08 23:28 10 | */ 11 | public class AppContextUtil { 12 | 13 | public static Context instance() { 14 | return ApplicationComponent.Instance.get().getApplication(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/lsxiao/com/zhihudailyrrd/util/BundleUtil.java: -------------------------------------------------------------------------------- 1 | package lsxiao.com.zhihudailyrrd.util; 2 | 3 | import android.os.Bundle; 4 | 5 | import lsxiao.com.zhihudailyrrd.base.BundleKey; 6 | 7 | /** 8 | * author lsxiao 9 | * date 2016-05-09 21:30 10 | */ 11 | public class BundleUtil { 12 | private BundleUtil() { 13 | } 14 | 15 | public static Bundle newInstance() { 16 | return new Bundle(); 17 | } 18 | 19 | public static Bundle withThrowable(Throwable throwable) { 20 | Bundle bundle = newInstance(); 21 | bundle.putSerializable(BundleKey.THROWABLE, throwable); 22 | return bundle; 23 | } 24 | 25 | public static Throwable getThrowable(Bundle bundle) { 26 | if (bundle == null || bundle.isEmpty()) { 27 | throw new IllegalArgumentException("the bundle is null or empty"); 28 | } 29 | return (Throwable) bundle.getSerializable(BundleKey.THROWABLE); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/lsxiao/com/zhihudailyrrd/util/ExitClickUtil.java: -------------------------------------------------------------------------------- 1 | package lsxiao.com.zhihudailyrrd.util; 2 | 3 | /** 4 | * @author lsxiao 5 | * @date 2015-11-03 22:28 6 | */ 7 | public class ExitClickUtil { 8 | private static long lastClickTime; 9 | 10 | public static boolean isClickAgain() { 11 | long time = System.currentTimeMillis(); 12 | long timeD = time - lastClickTime; 13 | if (0 < timeD && timeD < 1500) { 14 | return true; 15 | } 16 | lastClickTime = time; 17 | return false; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/lsxiao/com/zhihudailyrrd/util/FastBlur.java: -------------------------------------------------------------------------------- 1 | package lsxiao.com.zhihudailyrrd.util; 2 | 3 | import android.graphics.Bitmap; 4 | 5 | 6 | /** 7 | * Created by QIUJUER 8 | * on 2014/4/19. 9 | */ 10 | public class FastBlur { 11 | private FastBlur() { 12 | } 13 | 14 | public static Bitmap doBlur(Bitmap sentBitmap, int radius, boolean canReuseInBitmap) { 15 | Bitmap bitmap; 16 | if (canReuseInBitmap) { 17 | bitmap = sentBitmap; 18 | } else { 19 | bitmap = sentBitmap.copy(sentBitmap.getConfig(), true); 20 | } 21 | 22 | if (radius < 1) { 23 | return (null); 24 | } 25 | 26 | int w = bitmap.getWidth(); 27 | int h = bitmap.getHeight(); 28 | 29 | int[] pix = new int[w * h]; 30 | bitmap.getPixels(pix, 0, w, 0, 0, w, h); 31 | 32 | int wm = w - 1; 33 | int hm = h - 1; 34 | int wh = w * h; 35 | int div = radius + radius + 1; 36 | 37 | int r[] = new int[wh]; 38 | int g[] = new int[wh]; 39 | int b[] = new int[wh]; 40 | int rsum, gsum, bsum, x, y, i, p, yp, yi, yw; 41 | int vmin[] = new int[Math.max(w, h)]; 42 | 43 | int divsum = (div + 1) >> 1; 44 | divsum *= divsum; 45 | int dv[] = new int[256 * divsum]; 46 | for (i = 0; i < 256 * divsum; i++) { 47 | dv[i] = (i / divsum); 48 | } 49 | 50 | yw = yi = 0; 51 | 52 | int[][] stack = new int[div][3]; 53 | int stackpointer; 54 | int stackstart; 55 | int[] sir; 56 | int rbs; 57 | int r1 = radius + 1; 58 | int routsum, goutsum, boutsum; 59 | int rinsum, ginsum, binsum; 60 | 61 | for (y = 0; y < h; y++) { 62 | rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0; 63 | for (i = -radius; i <= radius; i++) { 64 | p = pix[yi + Math.min(wm, Math.max(i, 0))]; 65 | sir = stack[i + radius]; 66 | sir[0] = (p & 0xff0000) >> 16; 67 | sir[1] = (p & 0x00ff00) >> 8; 68 | sir[2] = (p & 0x0000ff); 69 | rbs = r1 - Math.abs(i); 70 | rsum += sir[0] * rbs; 71 | gsum += sir[1] * rbs; 72 | bsum += sir[2] * rbs; 73 | if (i > 0) { 74 | rinsum += sir[0]; 75 | ginsum += sir[1]; 76 | binsum += sir[2]; 77 | } else { 78 | routsum += sir[0]; 79 | goutsum += sir[1]; 80 | boutsum += sir[2]; 81 | } 82 | } 83 | stackpointer = radius; 84 | 85 | for (x = 0; x < w; x++) { 86 | 87 | r[yi] = dv[rsum]; 88 | g[yi] = dv[gsum]; 89 | b[yi] = dv[bsum]; 90 | 91 | rsum -= routsum; 92 | gsum -= goutsum; 93 | bsum -= boutsum; 94 | 95 | stackstart = stackpointer - radius + div; 96 | sir = stack[stackstart % div]; 97 | 98 | routsum -= sir[0]; 99 | goutsum -= sir[1]; 100 | boutsum -= sir[2]; 101 | 102 | if (y == 0) { 103 | vmin[x] = Math.min(x + radius + 1, wm); 104 | } 105 | p = pix[yw + vmin[x]]; 106 | 107 | sir[0] = (p & 0xff0000) >> 16; 108 | sir[1] = (p & 0x00ff00) >> 8; 109 | sir[2] = (p & 0x0000ff); 110 | 111 | rinsum += sir[0]; 112 | ginsum += sir[1]; 113 | binsum += sir[2]; 114 | 115 | rsum += rinsum; 116 | gsum += ginsum; 117 | bsum += binsum; 118 | 119 | stackpointer = (stackpointer + 1) % div; 120 | sir = stack[(stackpointer) % div]; 121 | 122 | routsum += sir[0]; 123 | goutsum += sir[1]; 124 | boutsum += sir[2]; 125 | 126 | rinsum -= sir[0]; 127 | ginsum -= sir[1]; 128 | binsum -= sir[2]; 129 | 130 | yi++; 131 | } 132 | yw += w; 133 | } 134 | for (x = 0; x < w; x++) { 135 | rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0; 136 | yp = -radius * w; 137 | for (i = -radius; i <= radius; i++) { 138 | yi = Math.max(0, yp) + x; 139 | 140 | sir = stack[i + radius]; 141 | 142 | sir[0] = r[yi]; 143 | sir[1] = g[yi]; 144 | sir[2] = b[yi]; 145 | 146 | rbs = r1 - Math.abs(i); 147 | 148 | rsum += r[yi] * rbs; 149 | gsum += g[yi] * rbs; 150 | bsum += b[yi] * rbs; 151 | 152 | if (i > 0) { 153 | rinsum += sir[0]; 154 | ginsum += sir[1]; 155 | binsum += sir[2]; 156 | } else { 157 | routsum += sir[0]; 158 | goutsum += sir[1]; 159 | boutsum += sir[2]; 160 | } 161 | 162 | if (i < hm) { 163 | yp += w; 164 | } 165 | } 166 | yi = x; 167 | stackpointer = radius; 168 | for (y = 0; y < h; y++) { 169 | // Preserve alpha channel: ( 0xff000000 & pix[yi] ) 170 | pix[yi] = (0xff000000 & pix[yi]) | (dv[rsum] << 16) | (dv[gsum] << 8) | dv[bsum]; 171 | 172 | rsum -= routsum; 173 | gsum -= goutsum; 174 | bsum -= boutsum; 175 | 176 | stackstart = stackpointer - radius + div; 177 | sir = stack[stackstart % div]; 178 | 179 | routsum -= sir[0]; 180 | goutsum -= sir[1]; 181 | boutsum -= sir[2]; 182 | 183 | if (x == 0) { 184 | vmin[y] = Math.min(y + r1, hm) * w; 185 | } 186 | p = x + vmin[y]; 187 | 188 | sir[0] = r[p]; 189 | sir[1] = g[p]; 190 | sir[2] = b[p]; 191 | 192 | rinsum += sir[0]; 193 | ginsum += sir[1]; 194 | binsum += sir[2]; 195 | 196 | rsum += rinsum; 197 | gsum += ginsum; 198 | bsum += binsum; 199 | 200 | stackpointer = (stackpointer + 1) % div; 201 | sir = stack[stackpointer]; 202 | 203 | routsum += sir[0]; 204 | goutsum += sir[1]; 205 | boutsum += sir[2]; 206 | 207 | rinsum -= sir[0]; 208 | ginsum -= sir[1]; 209 | binsum -= sir[2]; 210 | 211 | yi += w; 212 | } 213 | } 214 | 215 | bitmap.setPixels(pix, 0, w, 0, 0, w, h); 216 | return (bitmap); 217 | } 218 | } -------------------------------------------------------------------------------- /app/src/main/java/lsxiao/com/zhihudailyrrd/util/FastClickUtil.java: -------------------------------------------------------------------------------- 1 | package lsxiao.com.zhihudailyrrd.util; 2 | 3 | /** 4 | * @author lsxiao 5 | * @date 2015-11-03 22:28 6 | */ 7 | public class FastClickUtil { 8 | private static long lastClickTime; 9 | 10 | public static boolean isFastDoubleClick() { 11 | long time = System.currentTimeMillis(); 12 | long timeD = time - lastClickTime; 13 | if (0 < timeD && timeD < 400) { 14 | return true; 15 | } 16 | lastClickTime = time; 17 | return false; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/lsxiao/com/zhihudailyrrd/util/HtmlUtil.java: -------------------------------------------------------------------------------- 1 | package lsxiao.com.zhihudailyrrd.util; 2 | 3 | import java.util.List; 4 | 5 | import lsxiao.com.zhihudailyrrd.model.News; 6 | 7 | /** 8 | * @author lsxiao 9 | * @date 2015-11-05 10:45 10 | */ 11 | public class HtmlUtil { 12 | //css样式,隐藏header 13 | private static final String HIDE_HEADER_STYLE = ""; 14 | 15 | //css style tag,需要格式化 16 | private static final String NEEDED_FORMAT_CSS_TAG = ""; 17 | 18 | // js script tag,需要格式化 19 | private static final String NEEDED_FORMAT_JS_TAG = ""; 20 | 21 | public static final String MIME_TYPE = "text/html; charset=utf-8"; 22 | public static final String ENCODING = "utf-8"; 23 | 24 | private HtmlUtil() { 25 | } 26 | 27 | /** 28 | * 根据css链接生成Link标签 29 | * 30 | * @param url String 31 | * @return String 32 | */ 33 | public static String createCssTag(String url) { 34 | return String.format(NEEDED_FORMAT_CSS_TAG, url); 35 | } 36 | 37 | /** 38 | * 根据多个css链接生成Link标签 39 | * 40 | * @param urls List 41 | * @return String 42 | */ 43 | public static String createCssTag(List urls) { 44 | final StringBuilder sb = new StringBuilder(); 45 | for (String url : urls) { 46 | sb.append(createCssTag(url)); 47 | } 48 | return sb.toString(); 49 | } 50 | 51 | /** 52 | * 根据js链接生成Script标签 53 | * 54 | * @param url String 55 | * @return String 56 | */ 57 | public static String createJsTag(String url) { 58 | return String.format(NEEDED_FORMAT_JS_TAG, url); 59 | } 60 | 61 | /** 62 | * 根据多个js链接生成Script标签 63 | * 64 | * @param urls List 65 | * @return String 66 | */ 67 | public static String createJsTag(List urls) { 68 | final StringBuilder sb = new StringBuilder(); 69 | for (String url : urls) { 70 | sb.append(createJsTag(url)); 71 | } 72 | return sb.toString(); 73 | } 74 | 75 | /** 76 | * 根据样式标签,html字符串,js标签 77 | * 生成完整的HTML文档 78 | * 79 | * @param html string 80 | * @param css string 81 | * @param js string 82 | * @return string 83 | */ 84 | private static String createHtmlData(String html, String css, String js) { 85 | return css.concat(HIDE_HEADER_STYLE).concat(html).concat(js); 86 | } 87 | 88 | /** 89 | * 根据News 90 | * 生成完整的HTML文档 91 | * 92 | * @param news news 93 | * @return String 94 | */ 95 | public static String createHtmlData(News news) { 96 | final String css = HtmlUtil.createCssTag(news.getCssList()); 97 | final String js = HtmlUtil.createJsTag(news.getJsList()); 98 | return createHtmlData(news.getBody(), css, js); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /app/src/main/java/lsxiao/com/zhihudailyrrd/util/NetUtil.java: -------------------------------------------------------------------------------- 1 | package lsxiao.com.zhihudailyrrd.util; 2 | 3 | import android.content.Context; 4 | import android.net.ConnectivityManager; 5 | import android.net.NetworkInfo; 6 | 7 | /** 8 | * @author lsxiao 9 | * @date 2015-11-09 00:50 10 | */ 11 | public class NetUtil { 12 | 13 | private NetUtil() { 14 | } 15 | 16 | public static boolean isNetworkConnected() { 17 | if (AppContextUtil.instance() != null) { 18 | ConnectivityManager mConnectivityManager = (ConnectivityManager) AppContextUtil.instance() 19 | .getSystemService(Context.CONNECTIVITY_SERVICE); 20 | NetworkInfo mNetworkInfo = mConnectivityManager.getActiveNetworkInfo(); 21 | if (mNetworkInfo != null) { 22 | return mNetworkInfo.isAvailable(); 23 | } 24 | } 25 | return false; 26 | } 27 | 28 | public static boolean isWifiConnected() { 29 | if (AppContextUtil.instance() != null) { 30 | ConnectivityManager mConnectivityManager = (ConnectivityManager) AppContextUtil.instance() 31 | .getSystemService(Context.CONNECTIVITY_SERVICE); 32 | NetworkInfo mWiFiNetworkInfo = mConnectivityManager 33 | .getNetworkInfo(ConnectivityManager.TYPE_WIFI); 34 | if (mWiFiNetworkInfo != null) { 35 | return mWiFiNetworkInfo.isAvailable(); 36 | } 37 | } 38 | return false; 39 | } 40 | 41 | public static boolean isMobileConnected() { 42 | if (AppContextUtil.instance() != null) { 43 | ConnectivityManager mConnectivityManager = (ConnectivityManager) AppContextUtil.instance() 44 | .getSystemService(Context.CONNECTIVITY_SERVICE); 45 | NetworkInfo mMobileNetworkInfo = mConnectivityManager 46 | .getNetworkInfo(ConnectivityManager.TYPE_MOBILE); 47 | if (mMobileNetworkInfo != null) { 48 | return mMobileNetworkInfo.isAvailable(); 49 | } 50 | } 51 | return false; 52 | } 53 | 54 | public static int getConnectedType() { 55 | if (AppContextUtil.instance() != null) { 56 | ConnectivityManager mConnectivityManager = (ConnectivityManager) AppContextUtil.instance() 57 | .getSystemService(Context.CONNECTIVITY_SERVICE); 58 | NetworkInfo mNetworkInfo = mConnectivityManager.getActiveNetworkInfo(); 59 | if (mNetworkInfo != null && mNetworkInfo.isAvailable()) { 60 | return mNetworkInfo.getType(); 61 | } 62 | } 63 | return -1; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /app/src/main/java/lsxiao/com/zhihudailyrrd/util/SpUtil.java: -------------------------------------------------------------------------------- 1 | package lsxiao.com.zhihudailyrrd.util; 2 | 3 | import android.preference.PreferenceManager; 4 | 5 | import lsxiao.com.zhihudailyrrd.inject.component.ApplicationComponent; 6 | 7 | /** 8 | * @author lsxiao 9 | * @date 2015-11-08 23:21 10 | */ 11 | public class SpUtil { 12 | public static void saveOrUpdate(String key, String json) { 13 | PreferenceManager.getDefaultSharedPreferences(ApplicationComponent 14 | .Instance 15 | .get() 16 | .getApplication()) 17 | .edit().putString(key, json).apply(); 18 | } 19 | 20 | public static String find(String key) { 21 | return PreferenceManager.getDefaultSharedPreferences(ApplicationComponent 22 | .Instance 23 | .get() 24 | .getApplication()).getString(key, null); 25 | } 26 | 27 | public static void delete(String key) { 28 | PreferenceManager.getDefaultSharedPreferences(ApplicationComponent 29 | .Instance 30 | .get() 31 | .getApplication()).edit().remove(key).apply(); 32 | } 33 | 34 | public static void clearAll() { 35 | PreferenceManager.getDefaultSharedPreferences(ApplicationComponent 36 | .Instance 37 | .get() 38 | .getApplication()).edit().clear().apply(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/lsxiao/com/zhihudailyrrd/view/TextSliderView.java: -------------------------------------------------------------------------------- 1 | package lsxiao.com.zhihudailyrrd.view; 2 | 3 | import android.content.Context; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.widget.ImageView; 7 | import android.widget.TextView; 8 | 9 | import com.daimajia.slider.library.SliderTypes.BaseSliderView; 10 | 11 | import lsxiao.com.zhihudailyrrd.R; 12 | 13 | /** 14 | * @author lsxiao 15 | * @date 2015-11-07 16:45 16 | */ 17 | public class TextSliderView extends BaseSliderView { 18 | public TextSliderView(Context context) { 19 | super(context); 20 | } 21 | 22 | @Override 23 | public View getView() { 24 | View v = LayoutInflater.from(getContext()).inflate(R.layout.slider_item, null); 25 | ImageView target = (ImageView) v.findViewById(R.id.iv_slider); 26 | TextView title = (TextView) v.findViewById(R.id.tv_title); 27 | title.setText(getDescription()); 28 | bindEventAndShow(v, target); 29 | return v; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sloaix/ZhihuDailyFluxRRD/f6d9eaa01c5199849f2d7ad099dd4e78d775bf68/app/src/main/res/drawable-xhdpi/ic_empty.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sloaix/ZhihuDailyFluxRRD/f6d9eaa01c5199849f2d7ad099dd4e78d775bf68/app/src/main/res/drawable-xhdpi/ic_error.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_placeholder.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sloaix/ZhihuDailyFluxRRD/f6d9eaa01c5199849f2d7ad099dd4e78d775bf68/app/src/main/res/drawable-xhdpi/ic_placeholder.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_list_item_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_shadow_mask.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/color_list_item_text.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/list_divider.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_home.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_news.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_news_detail.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 14 | 15 | 24 | 25 | 32 | 33 | 38 | 39 | 44 | 45 | 53 | 54 | 55 | 58 | 59 | 60 | 61 | 62 | 68 | 69 | 75 | 76 | 77 | 78 | 82 | 83 | 85 | 86 | 88 | 89 | 90 | 91 | 99 | 100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_news_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | 17 | 18 | 19 | 21 | 22 | 24 | 25 | 26 | 31 | 32 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /app/src/main/res/layout/include_empty.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/layout/include_error.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/layout/include_tool_bar.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/layout/list_item_header.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 15 | 34 | -------------------------------------------------------------------------------- /app/src/main/res/layout/list_item_news.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 21 | 22 | 23 | 41 | 42 | -------------------------------------------------------------------------------- /app/src/main/res/layout/slider_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 13 | 14 | 18 | 19 | 23 | 24 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_news.xml: -------------------------------------------------------------------------------- 1 |

5 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sloaix/ZhihuDailyFluxRRD/f6d9eaa01c5199849f2d7ad099dd4e78d775bf68/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sloaix/ZhihuDailyFluxRRD/f6d9eaa01c5199849f2d7ad099dd4e78d775bf68/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sloaix/ZhihuDailyFluxRRD/f6d9eaa01c5199849f2d7ad099dd4e78d775bf68/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sloaix/ZhihuDailyFluxRRD/f6d9eaa01c5199849f2d7ad099dd4e78d775bf68/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sloaix/ZhihuDailyFluxRRD/f6d9eaa01c5199849f2d7ad099dd4e78d775bf68/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 | > 2 | 3 | 9 | 10 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #448AFF 6 | #eeeeee 7 | @color/color_primary 8 | #fff 9 | #fff 10 | #454545 11 | #a1ffffff 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 16dp 3 | 16dp 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens_detail_news.xml: -------------------------------------------------------------------------------- 1 | 2 | 13sp 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 知乎日报 3 | 每日热文 4 | 设置 5 | 再按一次退出知乎日报 6 | 版权 7 | 没有新闻 8 | 加载失败,请点击重试 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 15 | 16 | 10 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | apply from: 'config/dependencies.gradle' 2 | buildscript { 3 | repositories { 4 | jcenter() 5 | } 6 | dependencies { 7 | classpath 'com.android.tools.build:gradle:2.1.2' 8 | classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' 9 | classpath 'me.tatarka:gradle-retrolambda:3.2.0' 10 | classpath "com.fernandocejas.frodo:frodo-plugin:0.8.3" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | jcenter() 17 | maven { url "https://www.jitpack.io" } 18 | } 19 | } 20 | 21 | task clean(type: Delete) { 22 | delete rootProject.buildDir 23 | } 24 | -------------------------------------------------------------------------------- /config/dependencies.gradle: -------------------------------------------------------------------------------- 1 | allprojects { 2 | repositories { 3 | jcenter() 4 | maven { url "https://oss.sonatype.org/content/repositories/snapshots/" } 5 | } 6 | } 7 | 8 | ext { 9 | //Android 10 | androidBuildTools = "23.0.3" 11 | androidMinSdk = 21 12 | androidTargetSdk = 23 13 | androidCompileSdk = 23 14 | androidSupport = "23.2.1" 15 | 16 | //Libraries 17 | dagger = '2.2' 18 | picasso = "2.5.2" 19 | butterKnife = '7.0.1' 20 | rxJava = '1.1.3' 21 | rxAndroid = '1.1.0' 22 | rxLifecycle = '0.5.0' 23 | rxBinding = '0.4.0' 24 | javaxAnnotation = '10.0-b28' 25 | retrofit = "2.0.2" 26 | okHttp = "3.2.0" 27 | jotaTime = "2.8.2" 28 | imageSlider = "1.1.5@aar" 29 | nineOld = "2.4.0" 30 | gson = "2.6.2" 31 | apollo = "0.1.3" 32 | 33 | circleImage = "2.0.0" 34 | 35 | //Test 36 | jUnit = '4.12' 37 | 38 | libs = [ 39 | picasso : "com.squareup.picasso:picasso:${picasso}", 40 | daggerCompiler : "com.google.dagger:dagger-compiler:${dagger}", 41 | dagger : "com.google.dagger:dagger:${dagger}", 42 | butterKnife : "com.jakewharton:butterknife:${butterKnife}", 43 | supportV4 : "com.android.support:support-v4:${androidSupport}", 44 | appcompat : "com.android.support:appcompat-v7:${androidSupport}", 45 | recyclerView : "com.android.support:recyclerview-v7:${androidSupport}", 46 | cardView : "com.android.support:cardview-v7:${androidSupport}", 47 | supportDesign : "com.android.support:design:${androidSupport}", 48 | supportAnnotation : "com.android.support:support-annotations:${androidSupport}", 49 | rxJava : "io.reactivex:rxjava:${rxJava}", 50 | rxAndroid : "io.reactivex:rxandroid:${rxAndroid}", 51 | rxLifecycle : "com.trello:rxlifecycle:${rxLifecycle}", 52 | rxComponent : "com.trello:rxlifecycle-components:${rxLifecycle}", 53 | rxBinding : "com.jakewharton.rxbinding:rxbinding:${rxBinding}", 54 | javaxAnnotation : "org.glassfish:javax.annotation:${javaxAnnotation}", 55 | retrofit : "com.squareup.retrofit2:retrofit:${retrofit}", 56 | retrofitWithGson : "com.squareup.retrofit2:converter-gson:${retrofit}", 57 | retrofitWithRxJava : "com.squareup.retrofit2:adapter-rxjava:${retrofit}", 58 | okHttpLogInterceptor: "com.squareup.okhttp3:logging-interceptor:${okHttp}", 59 | jodaTime : "joda-time:joda-time:${jotaTime}", 60 | imageSlider : "com.daimajia.slider:library:${imageSlider}", 61 | nineOld : "com.nineoldandroids:library:${nineOld}", 62 | gson : "com.google.code.gson:gson:${gson}", 63 | circleImage : "de.hdodenhof:circleimageview:${circleImage}", 64 | apollo : "com.github.lsxiao.Apollo:apollo:${apollo}", 65 | apolloProcessor : "com.github.lsxiao.Apollo:processor:${apollo}" 66 | ] 67 | 68 | 69 | test = [ 70 | junit: "junit:junit:${jUnit}" 71 | ] 72 | } -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sloaix/ZhihuDailyFluxRRD/f6d9eaa01c5199849f2d7ad099dd4e78d775bf68/demo.gif -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sloaix/ZhihuDailyFluxRRD/f6d9eaa01c5199849f2d7ad099dd4e78d775bf68/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Apr 29 12:57:51 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 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /screenshot/flux_flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sloaix/ZhihuDailyFluxRRD/f6d9eaa01c5199849f2d7ad099dd4e78d775bf68/screenshot/flux_flow.png -------------------------------------------------------------------------------- /screenshot/structure.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sloaix/ZhihuDailyFluxRRD/f6d9eaa01c5199849f2d7ad099dd4e78d775bf68/screenshot/structure.jpg -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | --------------------------------------------------------------------------------