() {
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 super TodayNews> 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 super News> 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 |
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 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/values/theme.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
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 |
--------------------------------------------------------------------------------