() {
54 | @Override
55 | public void call(Throwable e) {
56 | doRxError(e);
57 | }
58 | });
59 | mCompositeSubscription.add(sub);
60 | } else {
61 | mHasBindUser = false;
62 | mViewInterface.showDefaultInfo();
63 |
64 | }
65 | }
66 |
67 | public boolean hasBindUser() {
68 | return mHasBindUser;
69 | }
70 |
71 | }
72 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ouwenjie/zhizhihu/ui/view/swipebacklayout/Utils.java:
--------------------------------------------------------------------------------
1 | package com.ouwenjie.zhizhihu.ui.view.swipebacklayout;
2 |
3 | import android.app.Activity;
4 |
5 | import java.lang.reflect.Method;
6 |
7 | /**
8 | * Created by 文杰 on 2015/11/5.
9 | */
10 | public class Utils {
11 | private Utils() {
12 | }
13 |
14 | /**
15 | * Convert a translucent themed Activity
16 | * {@link android.R.attr#windowIsTranslucent} to a fullscreen opaque
17 | * Activity.
18 | *
19 | * Call this whenever the background of a translucent Activity has changed
20 | * to become opaque. Doing so will allow the {@link android.view.Surface} of
21 | * the Activity behind to be released.
22 | *
23 | * This call has no effect on non-translucent activities or on activities
24 | * with the {@link android.R.attr#windowIsFloating} attribute.
25 | */
26 | public static void convertActivityFromTranslucent(Activity activity) {
27 | try {
28 | Method method = Activity.class.getDeclaredMethod("convertFromTranslucent");
29 | method.setAccessible(true);
30 | method.invoke(activity);
31 | } catch (Throwable t) {
32 | }
33 | }
34 |
35 | /**
36 | * Convert a translucent themed Activity
37 | * {@link android.R.attr#windowIsTranslucent} back from opaque to
38 | * translucent following a call to
39 | * {@link #convertActivityFromTranslucent(android.app.Activity)} .
40 | *
41 | * Calling this allows the Activity behind this one to be seen again. Once
42 | * all such Activities have been redrawn
43 | *
44 | * This call has no effect on non-translucent activities or on activities
45 | * with the {@link android.R.attr#windowIsFloating} attribute.
46 | */
47 | public static void convertActivityToTranslucent(Activity activity) {
48 | try {
49 | Class>[] classes = Activity.class.getDeclaredClasses();
50 | Class> translucentConversionListenerClazz = null;
51 | for (Class clazz : classes) {
52 | if (clazz.getSimpleName().contains("TranslucentConversionListener")) {
53 | translucentConversionListenerClazz = clazz;
54 | }
55 | }
56 | Method method = Activity.class.getDeclaredMethod("convertToTranslucent",
57 | translucentConversionListenerClazz);
58 | method.setAccessible(true);
59 | method.invoke(activity, new Object[] {
60 | null
61 | });
62 | } catch (Throwable t) {
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ouwenjie/zhizhihu/presenter/MVPPresenter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Saúl Molinero.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.ouwenjie.zhizhihu.presenter;
17 |
18 | import android.content.Context;
19 |
20 | import com.orhanobut.logger.Logger;
21 | import com.ouwenjie.zhizhihu.R;
22 | import com.ouwenjie.zhizhihu.common.ModelErrorException;
23 | import com.ouwenjie.zhizhihu.ui.viewInterface.MVPView;
24 | import com.ouwenjie.zhizhihu.utils.NetworkUtil;
25 |
26 | import rx.subscriptions.CompositeSubscription;
27 |
28 | /**
29 | * Presenter 抽象类,定义了基本的类型和流程
30 | */
31 | public abstract class MVPPresenter {
32 |
33 | protected T mViewInterface;
34 | protected Context mContext;
35 |
36 | protected CompositeSubscription mCompositeSubscription;
37 |
38 | public MVPPresenter(T viewInterface) {
39 | this.mViewInterface = viewInterface;
40 | mContext = viewInterface.getContext();
41 | mCompositeSubscription = new CompositeSubscription();
42 | }
43 |
44 | /**
45 | * Called when the presenter is initialized
46 | */
47 | public abstract void create();
48 |
49 | /**
50 | * Called when the presenter is stop,
51 | * i.e when an activity * or a fragment finishes
52 | */
53 | public abstract void destroy();
54 |
55 | public T getAttachedView() {
56 | return mViewInterface;
57 | }
58 |
59 | protected void doRxError(Throwable e) {
60 | Logger.t("doRxError=> ").e(e.getMessage());
61 | if (e instanceof ModelErrorException) {
62 | // 网络请求成功,但出现错误,则显示后台的 message
63 | mViewInterface.toast(e.getMessage());
64 | } else {
65 | // 网络错误
66 | if (!NetworkUtil.isNetworkAvailable(mContext)) {
67 | mViewInterface.toast(mViewInterface.getContext().getString(R.string.label_network_not_available));
68 | } else {
69 | // 其它错误
70 | mViewInterface.toast(mViewInterface.getContext().getString(R.string.label_operation_failed));
71 | }
72 | }
73 | }
74 |
75 | }
76 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/layout_top_user_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
21 |
22 |
27 |
28 |
36 |
37 |
45 |
46 |
47 |
48 |
55 |
56 |
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/app/src/main/res/layout-v21/layout_top_user_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
22 |
23 |
28 |
29 |
37 |
38 |
46 |
47 |
48 |
49 |
56 |
57 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ouwenjie/zhizhihu/ui/activity/LoadingActivity.java:
--------------------------------------------------------------------------------
1 | package com.ouwenjie.zhizhihu.ui.activity;
2 |
3 | import android.content.Intent;
4 | import android.os.Bundle;
5 | import android.os.Handler;
6 | import android.os.Message;
7 | import android.view.Window;
8 | import android.view.WindowManager;
9 | import android.view.animation.Animation;
10 | import android.view.animation.AnimationUtils;
11 | import android.widget.ImageView;
12 |
13 | import com.ouwenjie.zhizhihu.R;
14 | import com.ouwenjie.zhizhihu.ui.activity.base.BaseActivity;
15 |
16 | import java.lang.ref.WeakReference;
17 |
18 | import butterknife.Bind;
19 | import butterknife.ButterKnife;
20 |
21 | public class LoadingActivity extends BaseActivity {
22 |
23 | public static final int MSG_PASS = 1; // 进入主页
24 | public final long MIN_WAITTING_MSEC = 1000; // 最少等待的毫秒数
25 | private Handler mHandler = new WeakHandler(this);
26 |
27 | @Bind(R.id.loading_img)
28 | ImageView mLoadingImg;
29 | @Bind(R.id.logo_img)
30 | ImageView mLogoImg;
31 |
32 | @Override
33 | protected void onCreate(Bundle savedInstanceState) {
34 | getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
35 | WindowManager.LayoutParams.FLAG_FULLSCREEN);// 实现窗口全屏
36 | requestWindowFeature(Window.FEATURE_NO_TITLE);// 设置无标题样式
37 | super.onCreate(savedInstanceState);
38 | setContentView(R.layout.activity_loading);
39 | ButterKnife.bind(this);
40 |
41 | }
42 |
43 | @Override
44 | protected void onStart() {
45 | super.onStart();
46 | // 3 秒后进入主界面
47 | mHandler.removeMessages(MSG_PASS);
48 | mHandler.sendEmptyMessageDelayed(MSG_PASS, MIN_WAITTING_MSEC * 3);
49 | Animation alpha = AnimationUtils.loadAnimation(this, R.anim.alpha_0_1);
50 | mLogoImg.startAnimation(alpha);
51 | }
52 |
53 | /**
54 | * 进入主页
55 | */
56 | private void pass() {
57 | Intent intent;
58 | intent = new Intent(getApplicationContext(), HomeActivity.class);
59 | startActivity(intent);
60 | finish();
61 | }
62 |
63 |
64 | static class WeakHandler extends Handler {
65 | WeakReference mActivity;
66 |
67 | public WeakHandler(LoadingActivity activity) {
68 | mActivity = new WeakReference(activity);
69 | }
70 |
71 | @Override
72 | public void handleMessage(Message msg) {
73 | super.handleMessage(msg);
74 | LoadingActivity activity = mActivity.get();
75 | if (null != activity) {
76 | switch (msg.what) {
77 | case MSG_PASS: // 跳转
78 | activity.pass();
79 | break;
80 | default:
81 | break;
82 | }
83 | }
84 | }
85 | }
86 |
87 | }
88 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ouwenjie/zhizhihu/ui/view/MultiSwipeRefreshLayout.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Drakeet
3 | *
4 | * This file is part of Meizhi
5 | *
6 | * Meizhi is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * Meizhi is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with Meizhi. If not, see .
18 | */
19 |
20 | package com.ouwenjie.zhizhihu.ui.view;
21 |
22 | import android.content.Context;
23 | import android.content.res.TypedArray;
24 | import android.graphics.drawable.Drawable;
25 | import android.support.v4.widget.SwipeRefreshLayout;
26 | import android.util.AttributeSet;
27 |
28 | import com.ouwenjie.zhizhihu.R;
29 |
30 |
31 | /**
32 | * Pick from Google io 2014
33 | * Created by drakeet on 1/3/15.
34 | */
35 | public class MultiSwipeRefreshLayout extends SwipeRefreshLayout {
36 |
37 | private CanChildScrollUpCallback mCanChildScrollUpCallback;
38 | private Drawable mForegroundDrawable;
39 |
40 | public MultiSwipeRefreshLayout(Context context) {
41 | this(context, null);
42 | }
43 |
44 |
45 | public MultiSwipeRefreshLayout(Context context, AttributeSet attrs) {
46 | super(context, attrs);
47 | final TypedArray a =
48 | context.obtainStyledAttributes(attrs, R.styleable.MultiSwipeRefreshLayout, 0, 0);
49 |
50 | mForegroundDrawable = a.getDrawable(R.styleable.MultiSwipeRefreshLayout_foreground);
51 | if (mForegroundDrawable != null) {
52 | mForegroundDrawable.setCallback(this);
53 | setWillNotDraw(false);
54 | }
55 | a.recycle();
56 | }
57 |
58 |
59 | @Override
60 | protected void onSizeChanged(int w, int h, int oldw, int oldh) {
61 | super.onSizeChanged(w, h, oldw, oldh);
62 | if (mForegroundDrawable != null) {
63 | mForegroundDrawable.setBounds(0, 0, w, h);
64 | }
65 | }
66 |
67 |
68 | public void setCanChildScrollUpCallback(CanChildScrollUpCallback canChildScrollUpCallback) {
69 | mCanChildScrollUpCallback = canChildScrollUpCallback;
70 | }
71 |
72 |
73 | public interface CanChildScrollUpCallback {
74 | boolean canSwipeRefreshChildScrollUp();
75 | }
76 |
77 |
78 | @Override
79 | public boolean canChildScrollUp() {
80 | if (mCanChildScrollUpCallback != null) {
81 | return mCanChildScrollUpCallback.canSwipeRefreshChildScrollUp();
82 | }
83 | return super.canChildScrollUp();
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ouwenjie/zhizhihu/model/api/ApiService.java:
--------------------------------------------------------------------------------
1 | package com.ouwenjie.zhizhihu.model.api;
2 |
3 | import com.ouwenjie.zhizhihu.model.entity.AnswerModel;
4 | import com.ouwenjie.zhizhihu.model.entity.CheckNewModel;
5 | import com.ouwenjie.zhizhihu.model.entity.PostsModel;
6 | import com.ouwenjie.zhizhihu.model.entity.SearchUserModel;
7 | import com.ouwenjie.zhizhihu.model.entity.TopUserModel;
8 | import com.ouwenjie.zhizhihu.model.entity.UserDetail;
9 |
10 | import retrofit2.http.GET;
11 | import retrofit2.http.Headers;
12 | import retrofit2.http.Path;
13 | import rx.Observable;
14 |
15 | /**
16 | * @link http://www.kanzhihu.com/api-document
17 | *
18 | * Created by 文杰 on 2015/10/18.
19 | */
20 | public interface ApiService {
21 |
22 | /**
23 | * 获取最新的 Post 列表
24 | *
25 | * @return
26 | */
27 | @Headers("Cache-Control: public, max-age=3600")
28 | @GET("getposts")
29 | Observable getPosts();
30 |
31 | /**
32 | * 获取一个时间戳之前的 Post
33 | *
34 | * @param publishTime 时间戳
35 | * @return
36 | */
37 | @Headers("Cache-Control: public, max-age=3600")
38 | @GET("getposts/{publishTime}")
39 | Observable getPosts(@Path("publishTime") String publishTime);
40 |
41 | /**
42 | * 检查「看知乎」首页在指定时间之后有没有更新
43 | *
44 | * @param publishTime 时间戳
45 | * @return
46 | */
47 | @Headers("Cache-Control: public, max-age=3600")
48 | @GET("checknew/{publishTime}")
49 | Observable checkNew(@Path("publishTime") String publishTime);
50 |
51 | /**
52 | * 获取单篇 Post 的答案列表
53 | *
54 | * @param date
55 | * @param category
56 | * @return
57 | */
58 | @Headers("Cache-Control: public, max-age=3600")
59 | @GET("getpostanswers/{date}/{category}")
60 | Observable getPostAnswers(@Path("date") String date, @Path("category") String category);
61 |
62 | /**
63 | * 获取单个用户的详细数据
64 | *
65 | * @param userHash
66 | * @return
67 | */
68 | @Headers("Cache-Control: public, max-age=3600")
69 | @GET("userdetail2/{hash}")
70 | Observable getUserDetail(@Path("hash") String userHash);
71 |
72 | /**
73 | * 获取 top 3 用户
74 | *
75 | * @param type
76 | * @return
77 | */
78 | @Headers("Cache-Control: public, max-age=3600")
79 | @GET("topuser/{type}/3")
80 | Observable getTop1User(@Path("type") String type);
81 |
82 |
83 | /**
84 | * 获取 top 用户
85 | *
86 | * @param type
87 | * @param page
88 | * @param item
89 | * @return
90 | */
91 | @Headers("Cache-Control: public, max-age=3600")
92 | @GET("topuser/{type}/{page}/{item}")
93 | Observable getTopUser(
94 | @Path("type") String type,
95 | @Path("page") int page,
96 | @Path("item") int item
97 | );
98 |
99 | /**
100 | * 搜索用户
101 | *
102 | * @param key
103 | * @return
104 | */
105 | @Headers("Cache-Control: public, max-age=3600")
106 | @GET("searchuser/{key}")
107 | Observable searchUser(@Path("key") String key);
108 |
109 | }
110 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ouwenjie/zhizhihu/ui/fragment/SwipeRefreshFragment.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Drakeet
3 | *
4 | * This file is part of Meizhi
5 | *
6 | * Meizhi is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * Meizhi is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with Meizhi. If not, see .
18 | */
19 |
20 | package com.ouwenjie.zhizhihu.ui.fragment;
21 |
22 | import android.os.Bundle;
23 | import android.support.v4.widget.SwipeRefreshLayout;
24 | import android.view.View;
25 |
26 | import com.ouwenjie.zhizhihu.R;
27 | import com.ouwenjie.zhizhihu.ui.view.MultiSwipeRefreshLayout;
28 |
29 |
30 | /**
31 | * Created by drakeet on 8/11/15.
32 | */
33 | public class SwipeRefreshFragment extends BaseFragment {
34 |
35 | public MultiSwipeRefreshLayout mSwipeRefreshLayout;
36 |
37 | @Override
38 | public void onViewCreated(View view, Bundle savedInstanceState) {
39 | super.onViewCreated(view, savedInstanceState);
40 | trySetupSwipeRefresh(view);
41 | }
42 |
43 |
44 | void trySetupSwipeRefresh(View root) {
45 | mSwipeRefreshLayout = (MultiSwipeRefreshLayout) root.findViewById(R.id.swipe_refresh_layout);
46 | if (mSwipeRefreshLayout != null) {
47 | mSwipeRefreshLayout.setColorSchemeResources(
48 | R.color.refresh_progress_2,
49 | R.color.refresh_progress_1,
50 | R.color.refresh_progress_3);
51 | mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
52 | @Override
53 | public void onRefresh() {
54 | onRefreshing();
55 | }
56 | });
57 | }
58 | }
59 |
60 | public void onRefreshing() {
61 | setRefreshing(false);
62 | }
63 |
64 | public void setRefreshing(boolean refreshing) {
65 | if (mSwipeRefreshLayout == null) {
66 | return;
67 | }
68 | if (!refreshing) {
69 | // 防止刷新消失太快,让子弹飞一会儿
70 | mSwipeRefreshLayout.postDelayed(new Runnable() {
71 | @Override
72 | public void run() {
73 | mSwipeRefreshLayout.setRefreshing(false);
74 | }
75 | }, 1000);
76 | } else {
77 | mSwipeRefreshLayout.setRefreshing(true);
78 | }
79 | }
80 |
81 |
82 | public void setProgressViewOffset(boolean scale, int start, int end) {
83 | mSwipeRefreshLayout.setProgressViewOffset(scale, start, end);
84 | }
85 |
86 |
87 | public void setSwipeableChildren(
88 | MultiSwipeRefreshLayout.CanChildScrollUpCallback canChildScrollUpCallback) {
89 | mSwipeRefreshLayout.setCanChildScrollUpCallback(canChildScrollUpCallback);
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ouwenjie/zhizhihu/ui/activity/HomeActivity.java:
--------------------------------------------------------------------------------
1 | package com.ouwenjie.zhizhihu.ui.activity;
2 |
3 | import android.os.Bundle;
4 | import android.support.annotation.IdRes;
5 | import android.widget.FrameLayout;
6 |
7 | import com.ouwenjie.zhizhihu.R;
8 | import com.ouwenjie.zhizhihu.ui.activity.base.BaseActivity;
9 | import com.ouwenjie.zhizhihu.ui.fragment.PostFragment;
10 | import com.ouwenjie.zhizhihu.ui.fragment.TopUserFragment;
11 | import com.ouwenjie.zhizhihu.ui.fragment.UserCenterFragment;
12 | import com.roughike.bottombar.BottomBar;
13 | import com.roughike.bottombar.OnMenuTabClickListener;
14 |
15 | import butterknife.Bind;
16 | import butterknife.ButterKnife;
17 |
18 | public class HomeActivity extends BaseActivity {
19 |
20 | @Bind(R.id.container)
21 | FrameLayout mContainer;
22 |
23 | private BottomBar mBottomBar;
24 |
25 | private PostFragment mPostFragment;
26 | private TopUserFragment mTopUserFragment;
27 | private UserCenterFragment mUserCenterFragment;
28 |
29 | @Override
30 | protected void onCreate(Bundle savedInstanceState) {
31 | super.onCreate(savedInstanceState);
32 | setContentView(R.layout.activity_home);
33 | ButterKnife.bind(this);
34 |
35 | mPostFragment = PostFragment.newInstance();
36 | mTopUserFragment = TopUserFragment.newInstance();
37 | mUserCenterFragment = UserCenterFragment.newInstance(null);
38 |
39 | getSupportFragmentManager().beginTransaction()
40 | .replace(R.id.container, mPostFragment).commit();
41 |
42 | mBottomBar = BottomBar.attach(this, savedInstanceState);
43 | mBottomBar.setItemsFromMenu(R.menu.menu_home, new OnMenuTabClickListener() {
44 | @Override
45 | public void onMenuTabSelected(@IdRes int menuItemId) {
46 | // The user selected item number one.
47 | if (menuItemId == R.id.posts) {
48 | getSupportFragmentManager().beginTransaction()
49 | .replace(R.id.container, mPostFragment).commit();
50 | } else if (menuItemId == R.id.bigv) {
51 | getSupportFragmentManager().beginTransaction()
52 | .replace(R.id.container, mTopUserFragment).commit();
53 | } else if (menuItemId == R.id.me) {
54 | getSupportFragmentManager().beginTransaction()
55 | .replace(R.id.container, mUserCenterFragment).commit();
56 | }
57 | }
58 |
59 | @Override
60 | public void onMenuTabReSelected(@IdRes int menuItemId) {
61 | // The user reselected item number one, scroll your content to top.
62 | if (menuItemId == R.id.posts) {
63 | // The user selected item number one.
64 | } else if (menuItemId == R.id.bigv) {
65 |
66 | } else if (menuItemId == R.id.me) {
67 |
68 | }
69 | }
70 | });
71 |
72 | }
73 |
74 | @Override
75 | protected void onSaveInstanceState(Bundle outState) {
76 | super.onSaveInstanceState(outState);
77 |
78 | // Necessary to restore the BottomBar's state, otherwise we would
79 | // lose the current tab on orientation change.
80 | mBottomBar.onSaveInstanceState(outState);
81 | }
82 |
83 | }
84 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ouwenjie/zhizhihu/presenter/PostListPresenter.java:
--------------------------------------------------------------------------------
1 | package com.ouwenjie.zhizhihu.presenter;
2 |
3 | import com.ouwenjie.zhizhihu.model.entity.Post;
4 | import com.ouwenjie.zhizhihu.model.mImp.ApiImp;
5 | import com.ouwenjie.zhizhihu.model.mInterface.ApiInterface;
6 | import com.ouwenjie.zhizhihu.ui.viewInterface.PostListViewInterface;
7 |
8 | import java.util.ArrayList;
9 | import java.util.List;
10 |
11 | import rx.Subscriber;
12 | import rx.Subscription;
13 |
14 | /**
15 | * Created by 文杰 on 2015/10/27.
16 | */
17 | public class PostListPresenter extends MVPPresenter {
18 |
19 | private ApiInterface mApiInterface;
20 |
21 | private List mPosts = new ArrayList<>();
22 | private String mLastPublishTime;
23 |
24 | private boolean mIsLoadingNextPage = false;
25 |
26 | public PostListPresenter(PostListViewInterface postListViewInterface) {
27 | super(postListViewInterface);
28 | mApiInterface = new ApiImp();
29 | }
30 |
31 | @Override
32 | public void create() {
33 |
34 | }
35 |
36 | @Override
37 | public void destroy() {
38 | mCompositeSubscription.clear();
39 | }
40 |
41 | /**
42 | * 初始化,加载第一页
43 | */
44 | public void initData() {
45 | mViewInterface.setSwipeRefreshing(true);
46 | Subscription sub = mApiInterface.getPosts()
47 | .subscribe(new Subscriber>() {
48 | @Override
49 | public void onCompleted() {
50 |
51 | }
52 |
53 | @Override
54 | public void onError(Throwable e) {
55 | doRxError(e);
56 | }
57 |
58 | @Override
59 | public void onNext(List list) {
60 | mPosts = list;
61 | mViewInterface.initList(mPosts);
62 | mLastPublishTime = mPosts.get(mPosts.size() - 1).getPublishtime();
63 | mViewInterface.setSwipeRefreshing(false);
64 | }
65 | });
66 | mCompositeSubscription.add(sub);
67 | }
68 |
69 | /**
70 | * 加载下一页
71 | */
72 | public void loadNextPage() {
73 | mViewInterface.setSwipeRefreshing(true);
74 | mIsLoadingNextPage = true;
75 | Subscription sub = mApiInterface.getPosts(mLastPublishTime)
76 | .subscribe(new Subscriber>() {
77 | @Override
78 | public void onCompleted() {
79 |
80 | }
81 |
82 | @Override
83 | public void onError(Throwable e) {
84 | doRxError(e);
85 | }
86 |
87 | @Override
88 | public void onNext(List list) {
89 | mPosts.addAll(list);
90 | mViewInterface.refreshList();
91 | mLastPublishTime = mPosts.get(mPosts.size() - 1).getPublishtime();
92 | mViewInterface.setSwipeRefreshing(false);
93 | mIsLoadingNextPage = false;
94 | }
95 | });
96 | mCompositeSubscription.add(sub);
97 | }
98 |
99 | public boolean hasData() {
100 | return !mPosts.isEmpty();
101 | }
102 |
103 | public boolean isLoadingNextPage() {
104 | return mIsLoadingNextPage;
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ouwenjie/zhizhihu/ui/activity/AppCompatPreferenceActivity.java:
--------------------------------------------------------------------------------
1 | package com.ouwenjie.zhizhihu.ui.activity;
2 |
3 | import android.content.res.Configuration;
4 | import android.os.Bundle;
5 | import android.preference.PreferenceActivity;
6 | import android.support.annotation.LayoutRes;
7 | import android.support.annotation.Nullable;
8 | import android.support.v7.app.ActionBar;
9 | import android.support.v7.app.AppCompatDelegate;
10 | import android.support.v7.widget.Toolbar;
11 | import android.view.MenuInflater;
12 | import android.view.View;
13 | import android.view.ViewGroup;
14 |
15 | /**
16 | * A {@link android.preference.PreferenceActivity} which implements and proxies the necessary calls
17 | * to be used with AppCompat.
18 | */
19 | public abstract class AppCompatPreferenceActivity extends PreferenceActivity {
20 |
21 | private AppCompatDelegate mDelegate;
22 |
23 | @Override
24 | protected void onCreate(Bundle savedInstanceState) {
25 | getDelegate().installViewFactory();
26 | getDelegate().onCreate(savedInstanceState);
27 | super.onCreate(savedInstanceState);
28 | }
29 |
30 | @Override
31 | protected void onPostCreate(Bundle savedInstanceState) {
32 | super.onPostCreate(savedInstanceState);
33 | getDelegate().onPostCreate(savedInstanceState);
34 | }
35 |
36 | public ActionBar getSupportActionBar() {
37 | return getDelegate().getSupportActionBar();
38 | }
39 |
40 | public void setSupportActionBar(@Nullable Toolbar toolbar) {
41 | getDelegate().setSupportActionBar(toolbar);
42 | }
43 |
44 | @Override
45 | public MenuInflater getMenuInflater() {
46 | return getDelegate().getMenuInflater();
47 | }
48 |
49 | @Override
50 | public void setContentView(@LayoutRes int layoutResID) {
51 | getDelegate().setContentView(layoutResID);
52 | }
53 |
54 | @Override
55 | public void setContentView(View view) {
56 | getDelegate().setContentView(view);
57 | }
58 |
59 | @Override
60 | public void setContentView(View view, ViewGroup.LayoutParams params) {
61 | getDelegate().setContentView(view, params);
62 | }
63 |
64 | @Override
65 | public void addContentView(View view, ViewGroup.LayoutParams params) {
66 | getDelegate().addContentView(view, params);
67 | }
68 |
69 | @Override
70 | protected void onPostResume() {
71 | super.onPostResume();
72 | getDelegate().onPostResume();
73 | }
74 |
75 | @Override
76 | protected void onTitleChanged(CharSequence title, int color) {
77 | super.onTitleChanged(title, color);
78 | getDelegate().setTitle(title);
79 | }
80 |
81 | @Override
82 | public void onConfigurationChanged(Configuration newConfig) {
83 | super.onConfigurationChanged(newConfig);
84 | getDelegate().onConfigurationChanged(newConfig);
85 | }
86 |
87 | @Override
88 | protected void onStop() {
89 | super.onStop();
90 | getDelegate().onStop();
91 | }
92 |
93 | @Override
94 | protected void onDestroy() {
95 | super.onDestroy();
96 | getDelegate().onDestroy();
97 | }
98 |
99 | public void invalidateOptionsMenu() {
100 | getDelegate().invalidateOptionsMenu();
101 | }
102 |
103 | private AppCompatDelegate getDelegate() {
104 | if (mDelegate == null) {
105 | mDelegate = AppCompatDelegate.create(this, null);
106 | }
107 | return mDelegate;
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | apply plugin: 'realm-android'
3 |
4 | android {
5 | compileSdkVersion 24
6 | buildToolsVersion "24.0.0"
7 |
8 | defaultConfig {
9 | applicationId "com.ouwenjie.zhizhihu"
10 | minSdkVersion 19
11 | targetSdkVersion 22
12 | versionCode 1
13 | versionName "1.0"
14 | }
15 |
16 | buildTypes {
17 | release {
18 | minifyEnabled false
19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
20 | }
21 | debug {
22 | minifyEnabled false
23 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
24 | }
25 | }
26 |
27 | // 打包配置
28 | packagingOptions { // 打包配置
29 |
30 | exclude 'META-INF/dependencies.txt'
31 | exclude 'META-INF/DEPENDENCIES.txt'
32 | exclude 'META-INF/DEPENDENCIES'
33 | exclude 'META-INF/license.txt'
34 | exclude 'META-INF/LICENSE.txt'
35 | exclude 'META-INF/LICENSE'
36 | exclude 'META-INF/notice.txt'
37 | exclude 'META-INF/NOTICE.txt'
38 | exclude 'META-INF/NOTICE'
39 | exclude 'META-INF/LGPL2.1'
40 |
41 | // 为什么加入这个呢?防止冲突,比如我同时用了dagger-compiler就会报错,说下面这个`Processor`重复了
42 | exclude 'META-INF/services/javax.annotation.processing.Processor'
43 | }
44 |
45 | // 这个是解决lint报错的代码
46 | lintOptions {
47 | abortOnError false
48 | // 防止在发布的时候出现因MissingTranslation导致Build Failed!
49 | disable 'MissingTranslation'
50 | disable 'ExtraTranslation'
51 | disable 'InvalidPackage'
52 | }
53 |
54 | // 不同的指令集分开打包
55 | splits {
56 | abi {
57 | enable true
58 | reset()
59 | include 'armeabi'
60 | universalApk false
61 | }
62 | }
63 |
64 | // // 使用Java1.8
65 | // compileOptions {
66 | // sourceCompatibility JavaVersion.VERSION_1_8
67 | // targetCompatibility JavaVersion.VERSION_1_8
68 | // }
69 | }
70 |
71 | repositories {
72 | maven { url "https://jitpack.io" }
73 | }
74 |
75 | dependencies {
76 | compile fileTree(include: ['*.jar'], dir: 'libs')
77 | compile 'com.android.support:support-v4:24.0.0'
78 | compile 'com.android.support:appcompat-v7:24.0.0'
79 | compile 'com.android.support:design:24.0.0'
80 | compile 'com.android.support:palette-v7:24.0.0'
81 | compile 'com.android.support:cardview-v7:24.0.0'
82 | compile 'com.android.support:recyclerview-v7:24.0.0'
83 |
84 | compile 'com.jakewharton:butterknife:7.0.1'
85 | compile 'com.squareup:otto:1.3.8'
86 | compile 'com.squareup.retrofit2:retrofit:2.1.0' // 会引用 okHttp
87 | compile 'com.squareup.retrofit2:converter-gson:2.1.0' // 会引用 Gson
88 | compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0' // 会引用 RxJava
89 | compile 'io.reactivex:rxandroid:1.2.1'
90 | compile 'io.reactivex:rxjava:1.1.6'
91 | compile 'com.github.bumptech.glide:glide:3.7.0'
92 |
93 | compile 'com.facebook.stetho:stetho:1.3.1'
94 | compile 'com.facebook.stetho:stetho-okhttp3:1.3.1'
95 | compile 'com.roughike:bottom-bar:1.3.9'
96 | compile 'com.miguelcatalan:materialsearchview:1.4.0'
97 | compile 'com.github.orhanobut:logger:1.12'
98 | compile 'com.nineoldandroids:library:2.4.0'
99 | compile 'de.hdodenhof:circleimageview:2.0.0'
100 | compile 'com.jcodecraeer:xrecyclerview:1.2.7'
101 | compile 'com.github.PhilJay:MPAndroidChart:v2.2.3'
102 | }
103 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
20 |
24 |
25 |
26 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
42 |
46 |
51 |
55 |
59 |
63 |
66 |
70 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ouwenjie/zhizhihu/utils/DataCleanUtil.java:
--------------------------------------------------------------------------------
1 | package com.ouwenjie.zhizhihu.utils;
2 |
3 | import android.content.Context;
4 | import android.os.Environment;
5 |
6 | import java.io.File;
7 | import java.math.BigDecimal;
8 |
9 | /**
10 | * Created by Jack on 2015/11/17.
11 | */
12 | public class DataCleanUtil {
13 |
14 | public static String getTotalCacheSize(Context context) throws Exception {
15 | long cacheSize = getFolderSize(context.getCacheDir());
16 | if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
17 | cacheSize += getFolderSize(context.getExternalCacheDir());
18 | }
19 | return getFormatSize(cacheSize);
20 | }
21 |
22 |
23 | public static void clearAllCache(Context context) {
24 | deleteDir(context.getCacheDir());
25 | if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
26 | deleteDir(context.getExternalCacheDir());
27 | }
28 | }
29 |
30 | private static boolean deleteDir(File dir) {
31 | if (dir != null && dir.isDirectory()) {
32 | String[] children = dir.list();
33 | for (int i = 0; i < children.length; i++) {
34 | boolean success = deleteDir(new File(dir, children[i]));
35 | if (!success) {
36 | return false;
37 | }
38 | }
39 | }
40 | return dir.delete();
41 | }
42 |
43 | // 获取文件
44 | //Context.getExternalFilesDir() --> SDCard/Android/data/你的应用的包名/files/ 目录,一般放一些长时间保存的数据
45 | //Context.getExternalCacheDir() --> SDCard/Android/data/你的应用包名/cache/目录,一般存放临时缓存数据
46 | public static long getFolderSize(File file) throws Exception {
47 | long size = 0;
48 | try {
49 | File[] fileList = file.listFiles();
50 | for (int i = 0; i < fileList.length; i++) {
51 | // 如果下面还有文件
52 | if (fileList[i].isDirectory()) {
53 | size = size + getFolderSize(fileList[i]);
54 | } else {
55 | size = size + fileList[i].length();
56 | }
57 | }
58 | } catch (Exception e) {
59 | e.printStackTrace();
60 | }
61 | return size;
62 | }
63 |
64 | /**
65 | * 格式化单位
66 | *
67 | * @param size
68 | * @return
69 | */
70 | public static String getFormatSize(double size) {
71 | double kiloByte = size / 1024;
72 | if (kiloByte < 1) {
73 | // return size + "Byte";
74 | return "0K";
75 | }
76 |
77 | double megaByte = kiloByte / 1024;
78 | if (megaByte < 1) {
79 | BigDecimal result1 = new BigDecimal(Double.toString(kiloByte));
80 | return result1.setScale(2, BigDecimal.ROUND_HALF_UP)
81 | .toPlainString() + "KB";
82 | }
83 |
84 | double gigaByte = megaByte / 1024;
85 | if (gigaByte < 1) {
86 | BigDecimal result2 = new BigDecimal(Double.toString(megaByte));
87 | return result2.setScale(2, BigDecimal.ROUND_HALF_UP)
88 | .toPlainString() + "MB";
89 | }
90 |
91 | double teraBytes = gigaByte / 1024;
92 | if (teraBytes < 1) {
93 | BigDecimal result3 = new BigDecimal(Double.toString(gigaByte));
94 | return result3.setScale(2, BigDecimal.ROUND_HALF_UP)
95 | .toPlainString() + "GB";
96 | }
97 | BigDecimal result4 = new BigDecimal(teraBytes);
98 | return result4.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString()
99 | + "TB";
100 | }
101 |
102 | }
103 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ouwenjie/zhizhihu/ui/adapter/PostListAdapter.java:
--------------------------------------------------------------------------------
1 | package com.ouwenjie.zhizhihu.ui.adapter;
2 |
3 | import android.app.Activity;
4 | import android.content.Context;
5 | import android.support.v4.app.Fragment;
6 | import android.support.v7.widget.CardView;
7 | import android.support.v7.widget.RecyclerView;
8 | import android.view.LayoutInflater;
9 | import android.view.View;
10 | import android.view.ViewGroup;
11 | import android.widget.ImageView;
12 | import android.widget.TextView;
13 |
14 | import com.bumptech.glide.Glide;
15 | import com.ouwenjie.zhizhihu.R;
16 | import com.ouwenjie.zhizhihu.model.entity.Post;
17 |
18 | import java.util.List;
19 |
20 | import butterknife.Bind;
21 | import butterknife.ButterKnife;
22 |
23 | /**
24 | * Created by 文杰 on 2015/10/18.
25 | */
26 | public class PostListAdapter extends RecyclerView.Adapter {
27 |
28 | private List mPostList;
29 | private Context mContext;
30 | private OnItemClickListener mOnItemClickListener;
31 |
32 | public PostListAdapter(Activity activity, List postList) {
33 | this.mContext = activity;
34 | this.mPostList = postList;
35 | }
36 |
37 | public PostListAdapter(Fragment fragment, List postList) {
38 | this.mContext = fragment.getContext();
39 | this.mPostList = postList;
40 | }
41 |
42 | @Override
43 | public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
44 | final View itemView = LayoutInflater.from(parent.getContext())
45 | .inflate(R.layout.layout_post_list_item, parent, false);
46 | itemView.setOnClickListener(new View.OnClickListener() {
47 | @Override
48 | public void onClick(final View v) {
49 | if (mOnItemClickListener != null) {
50 | mOnItemClickListener.onItemClick(v, (Integer) v.getTag());
51 | }
52 | }
53 | });
54 | return new ViewHolder(itemView);
55 | }
56 |
57 | @Override
58 | public void onBindViewHolder(ViewHolder holder, int position) {
59 | Post post = mPostList.get(position);
60 | String date = post.getDate();
61 | String name = post.getName();
62 | String picUrl = post.getPic();
63 | String publishtime = post.getPublishtime();
64 | int count = post.getCount();
65 | String excerpt = post.getExcerpt();
66 |
67 | Glide.with(mContext)
68 | .load(picUrl)
69 | .into(holder.postPicImg);
70 |
71 | switch (name) {
72 | case "recent":
73 | name = "近日热门";
74 | break;
75 | case "yesterday":
76 | name = "昨日最新";
77 | break;
78 | case "archive":
79 | name = "历史精华";
80 | break;
81 | default:
82 | break;
83 | }
84 |
85 | String txt = date + " " + name;
86 | holder.dateAndNameTxt.setText(txt);
87 | holder.excerptTxt.setText(excerpt);
88 | holder.itemView.setTag(position);
89 | }
90 |
91 | @Override
92 | public int getItemCount() {
93 | return mPostList.size();
94 | }
95 |
96 | public class ViewHolder extends RecyclerView.ViewHolder {
97 |
98 | @Bind(R.id.post_pic_img)
99 | ImageView postPicImg;
100 | @Bind(R.id.date_and_name_txt)
101 | TextView dateAndNameTxt;
102 | @Bind(R.id.excerpt_txt)
103 | TextView excerptTxt;
104 | @Bind(R.id.layout_item_root)
105 | CardView layoutItemRoot;
106 |
107 | public ViewHolder(View itemView) {
108 | super(itemView);
109 | ButterKnife.bind(this, itemView);
110 | }
111 | }
112 |
113 | public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
114 | this.mOnItemClickListener = onItemClickListener;
115 | }
116 |
117 | public interface OnItemClickListener {
118 | void onItemClick(View view, int position);
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ouwenjie/zhizhihu/model/entity/Answer.java:
--------------------------------------------------------------------------------
1 | package com.ouwenjie.zhizhihu.model.entity;
2 |
3 | import io.realm.RealmObject;
4 |
5 | /**
6 | * Created by 文杰 on 2015/10/20.
7 | */
8 | public class Answer extends RealmObject {
9 |
10 | String title;
11 | String time;
12 | String summary;
13 | String questionid;
14 | String answerid;
15 | String authorname;
16 | String authorhash;
17 | String avatar;
18 | String vote;
19 |
20 | public Answer() {
21 | }
22 |
23 | public String getTitle() {
24 | return title;
25 | }
26 |
27 | public void setTitle(String title) {
28 | this.title = title;
29 | }
30 |
31 | public String getTime() {
32 | return time;
33 | }
34 |
35 | public void setTime(String time) {
36 | this.time = time;
37 | }
38 |
39 | public String getSummary() {
40 | return summary;
41 | }
42 |
43 | public void setSummary(String summary) {
44 | this.summary = summary;
45 | }
46 |
47 | public String getQuestionid() {
48 | return questionid;
49 | }
50 |
51 | public void setQuestionid(String questionid) {
52 | this.questionid = questionid;
53 | }
54 |
55 | public String getAnswerid() {
56 | return answerid;
57 | }
58 |
59 | public void setAnswerid(String answerid) {
60 | this.answerid = answerid;
61 | }
62 |
63 | public String getAuthorname() {
64 | return authorname;
65 | }
66 |
67 | public void setAuthorname(String authorname) {
68 | this.authorname = authorname;
69 | }
70 |
71 | public String getAuthorhash() {
72 | return authorhash;
73 | }
74 |
75 | public void setAuthorhash(String authorhash) {
76 | this.authorhash = authorhash;
77 | }
78 |
79 | public String getAvatar() {
80 | return avatar;
81 | }
82 |
83 | public void setAvatar(String avatar) {
84 | this.avatar = avatar;
85 | }
86 |
87 | public String getVote() {
88 | return vote;
89 | }
90 |
91 | public void setVote(String vote) {
92 | this.vote = vote;
93 | }
94 |
95 | @Override
96 | public boolean equals(Object o) {
97 | if (this == o) return true;
98 | if (o == null || getClass() != o.getClass()) return false;
99 |
100 | Answer answer = (Answer) o;
101 |
102 | if (title != null ? !title.equals(answer.title) : answer.title != null) return false;
103 | if (time != null ? !time.equals(answer.time) : answer.time != null) return false;
104 | if (summary != null ? !summary.equals(answer.summary) : answer.summary != null)
105 | return false;
106 | if (questionid != null ? !questionid.equals(answer.questionid) : answer.questionid != null)
107 | return false;
108 | if (answerid != null ? !answerid.equals(answer.answerid) : answer.answerid != null)
109 | return false;
110 | if (authorname != null ? !authorname.equals(answer.authorname) : answer.authorname != null)
111 | return false;
112 | if (authorhash != null ? !authorhash.equals(answer.authorhash) : answer.authorhash != null)
113 | return false;
114 | if (avatar != null ? !avatar.equals(answer.avatar) : answer.avatar != null) return false;
115 | return vote != null ? vote.equals(answer.vote) : answer.vote == null;
116 |
117 | }
118 |
119 | @Override
120 | public int hashCode() {
121 | int result = title != null ? title.hashCode() : 0;
122 | result = 31 * result + (time != null ? time.hashCode() : 0);
123 | result = 31 * result + (summary != null ? summary.hashCode() : 0);
124 | result = 31 * result + (questionid != null ? questionid.hashCode() : 0);
125 | result = 31 * result + (answerid != null ? answerid.hashCode() : 0);
126 | result = 31 * result + (authorname != null ? authorname.hashCode() : 0);
127 | result = 31 * result + (authorhash != null ? authorhash.hashCode() : 0);
128 | result = 31 * result + (avatar != null ? avatar.hashCode() : 0);
129 | result = 31 * result + (vote != null ? vote.hashCode() : 0);
130 | return result;
131 | }
132 |
133 | }
134 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_about_us.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
18 |
19 |
23 |
24 |
30 |
31 |
36 |
37 |
43 |
44 |
45 |
49 |
50 |
51 |
52 |
59 |
60 |
64 |
65 |
74 |
75 |
81 |
82 |
89 |
90 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/layout_answer_list_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
21 |
22 |
31 |
32 |
37 |
38 |
44 |
45 |
49 |
50 |
61 |
62 |
67 |
68 |
77 |
78 |
90 |
91 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
--------------------------------------------------------------------------------
/app/src/main/res/layout-v21/layout_answer_list_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
16 |
17 |
22 |
23 |
32 |
33 |
38 |
39 |
45 |
46 |
50 |
51 |
62 |
63 |
68 |
69 |
78 |
79 |
91 |
92 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_feedback.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
19 |
20 |
21 |
22 |
30 |
31 |
37 |
38 |
44 |
45 |
52 |
53 |
59 |
60 |
61 |
62 |
63 |
64 |
69 |
70 |
81 |
82 |
83 |
84 |
85 |
86 |
91 |
92 |
98 |
99 |
105 |
106 |
114 |
115 |
116 |
117 |
121 |
122 |
129 |
130 |
131 |
132 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ouwenjie/zhizhihu/ui/activity/MyFavoriteActivity.java:
--------------------------------------------------------------------------------
1 | package com.ouwenjie.zhizhihu.ui.activity;
2 |
3 | import android.content.Intent;
4 | import android.os.Bundle;
5 | import android.support.v7.widget.LinearLayoutManager;
6 | import android.support.v7.widget.RecyclerView;
7 | import android.support.v7.widget.Toolbar;
8 | import android.view.View;
9 | import android.widget.ProgressBar;
10 |
11 | import com.ouwenjie.zhizhihu.R;
12 | import com.ouwenjie.zhizhihu.model.api.ZhiHu;
13 | import com.ouwenjie.zhizhihu.model.entity.Answer;
14 | import com.ouwenjie.zhizhihu.ui.activity.base.SwipeBackActivity;
15 | import com.ouwenjie.zhizhihu.ui.adapter.PostAnswerAdapter;
16 |
17 | import java.util.List;
18 |
19 | import butterknife.Bind;
20 | import butterknife.ButterKnife;
21 | import io.realm.Realm;
22 |
23 | public class MyFavoriteActivity extends SwipeBackActivity {
24 |
25 | @Bind(R.id.toolbar)
26 | Toolbar mToolbar;
27 | @Bind(R.id.progress)
28 | ProgressBar mProgressBar;
29 | @Bind(R.id.my_favorite_answer_list_view)
30 | RecyclerView mMyFavoriteAnswerListView;
31 |
32 | private Realm mRealm;
33 | private PostAnswerAdapter mPostAnswerAdapter;
34 |
35 | @Override
36 | protected void onCreate(Bundle savedInstanceState) {
37 | super.onCreate(savedInstanceState);
38 | setContentView(R.layout.activity_my_favorite);
39 | ButterKnife.bind(this);
40 | setSupportActionBar(mToolbar);
41 | getSupportActionBar().setHomeButtonEnabled(true);
42 | getSupportActionBar().setDisplayHomeAsUpEnabled(true);
43 | setTitle("我的收藏");
44 |
45 | mRealm = Realm.getDefaultInstance();
46 |
47 | LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
48 | linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
49 | mMyFavoriteAnswerListView.setLayoutManager(linearLayoutManager);
50 | mMyFavoriteAnswerListView.hasFixedSize();
51 |
52 | }
53 |
54 | @Override
55 | protected void onResume() {
56 | super.onResume();
57 |
58 | List answers = mRealm.allObjects(Answer.class);
59 | initList(answers);
60 | }
61 |
62 |
63 | public void initList(final List answers) {
64 | mPostAnswerAdapter = new PostAnswerAdapter(this, answers);
65 | mPostAnswerAdapter.setOnItemClickListener(getOnItemClickListener(answers));
66 | mMyFavoriteAnswerListView.setAdapter(mPostAnswerAdapter);
67 | mProgressBar.setVisibility(View.GONE);
68 | }
69 |
70 | private PostAnswerAdapter.OnItemClickListener getOnItemClickListener(final List answers) {
71 | return new PostAnswerAdapter.OnItemClickListener() {
72 | @Override
73 | public void onItemClick(View view, int position) {
74 | Answer answer = answers.get(position);
75 | String qId = answer.getQuestionid();
76 | String aId = answer.getAnswerid();
77 | String url = String.format(ZhiHu.QUESTION_BASE_URL + "%s/answer/%s", qId, aId);
78 | Intent intent = new Intent(getContext(), WebBrowserActivity.class);
79 | intent.putExtra(WebBrowserActivity.URL, url);
80 | startActivity(intent);
81 |
82 | // boolean hasZhiHuClient = isAvilible(PostAnswersActivity.this, ZhiHu.PACKAGE_NAME);
83 | // if (hasZhiHuClient) {
84 | // Log.e("onItemClick=>", "hasZhiHuClient");
85 | // String url = String.format(ZhiHu.QUESTION_BASE_URL + "%s/answer/%s", qId, aId);
86 | // Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
87 | // intent.setPackage(ZhiHu.PACKAGE_NAME);
88 | // startActivity(intent);
89 | // } else {
90 | // toast("请您安装知乎客户端");
91 | // }
92 | }
93 |
94 | @Override
95 | public void onFavoriteClick(View view, int position) {
96 | Answer answer = answers.get(position);
97 | Answer favoriteAnswer = Realm.getDefaultInstance()
98 | .where(Answer.class)
99 | .equalTo("answerid", answer.getAnswerid())
100 | .findFirst();
101 | if (favoriteAnswer != null) {
102 | // remove
103 | mRealm.beginTransaction();
104 | favoriteAnswer.removeFromRealm();
105 | mRealm.commitTransaction();
106 | mPostAnswerAdapter.notifyDataSetChanged();
107 | toast("取消收藏");
108 | } else {
109 | // save
110 | mRealm.beginTransaction();
111 | mRealm.copyToRealm(answer);
112 | mRealm.commitTransaction();
113 | mPostAnswerAdapter.notifyDataSetChanged();
114 | toast("已收藏");
115 | }
116 | }
117 | };
118 | }
119 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/ouwenjie/zhizhihu/model/mImp/ApiImp.java:
--------------------------------------------------------------------------------
1 | package com.ouwenjie.zhizhihu.model.mImp;
2 |
3 | import android.text.TextUtils;
4 |
5 | import com.ouwenjie.zhizhihu.common.ModelErrorException;
6 | import com.ouwenjie.zhizhihu.model.api.ApiServiceFactory;
7 | import com.ouwenjie.zhizhihu.model.entity.Answer;
8 | import com.ouwenjie.zhizhihu.model.entity.AnswerModel;
9 | import com.ouwenjie.zhizhihu.model.entity.Post;
10 | import com.ouwenjie.zhizhihu.model.entity.PostsModel;
11 | import com.ouwenjie.zhizhihu.model.entity.SearchUser;
12 | import com.ouwenjie.zhizhihu.model.entity.SearchUserModel;
13 | import com.ouwenjie.zhizhihu.model.entity.TopUser;
14 | import com.ouwenjie.zhizhihu.model.entity.TopUserModel;
15 | import com.ouwenjie.zhizhihu.model.entity.UserDetail;
16 | import com.ouwenjie.zhizhihu.model.mInterface.ApiInterface;
17 |
18 | import java.util.List;
19 |
20 | import rx.Observable;
21 | import rx.android.schedulers.AndroidSchedulers;
22 | import rx.functions.Action1;
23 | import rx.functions.Func1;
24 | import rx.schedulers.Schedulers;
25 |
26 | /**
27 | * Created by Jack on 2016/3/23.
28 | */
29 | public class ApiImp implements ApiInterface {
30 |
31 | @Override
32 | public Observable> getPosts() {
33 | return getPosts("");
34 | }
35 |
36 | /**
37 | * 获取 Post
38 | *
39 | * @param publishTime 某个时间戳之前的 Post
40 | */
41 | @Override
42 | public Observable> getPosts(String publishTime) {
43 | return ApiServiceFactory.getApiService().getPosts(publishTime)
44 | .map(new Func1>() {
45 | @Override
46 | public List call(PostsModel postsModel) {
47 | return postsModel.getPosts();
48 | }
49 | })
50 | .subscribeOn(Schedulers.io())
51 | .observeOn(AndroidSchedulers.mainThread())
52 | .subscribeOn(Schedulers.io());
53 | }
54 |
55 | @Override
56 | public Observable> getAnswers(final String date, final String name) {
57 | return ApiServiceFactory.getApiService().getPostAnswers(date, name)
58 | .map(new Func1>() {
59 | @Override
60 | public List call(AnswerModel answerModel) {
61 | return answerModel.getAnswers();
62 | }
63 | })
64 | .subscribeOn(Schedulers.io())
65 | .observeOn(AndroidSchedulers.mainThread())
66 | .subscribeOn(Schedulers.io());
67 | }
68 |
69 | @Override
70 | public Observable> getTopUser(String type, int page, int item) {
71 | return ApiServiceFactory.getApiService().getTopUser(type, page, item)
72 | .subscribeOn(Schedulers.io())
73 | .observeOn(AndroidSchedulers.mainThread())
74 | .subscribeOn(Schedulers.io())
75 | .map(new Func1>() {
76 | @Override
77 | public List call(TopUserModel model) {
78 | String error = model.getError();
79 | if (!TextUtils.isEmpty(error)) {
80 | throw new ModelErrorException(error);
81 | }
82 | return model.getTopuser();
83 | }
84 | });
85 | }
86 |
87 | @Override
88 | public Observable getUserDetail(String userHash) {
89 | return ApiServiceFactory.getApiService().getUserDetail(userHash)
90 | .subscribeOn(Schedulers.io())
91 | .observeOn(AndroidSchedulers.mainThread())
92 | .subscribeOn(Schedulers.io())
93 | .doOnNext(new Action1() {
94 | @Override
95 | public void call(UserDetail userDetail) {
96 | String error = userDetail.getError();
97 | if (!TextUtils.isEmpty(error)) {
98 | throw new ModelErrorException(error);
99 | }
100 | }
101 | });
102 | }
103 |
104 | @Override
105 | public Observable> searchUser(String key) {
106 | return ApiServiceFactory.getApiService().searchUser(key)
107 | .subscribeOn(Schedulers.io())
108 | .observeOn(AndroidSchedulers.mainThread())
109 | .subscribeOn(Schedulers.io())
110 | .map(new Func1>() {
111 | @Override
112 | public List call(SearchUserModel searchUserModel) {
113 | if (!TextUtils.isEmpty(searchUserModel.getError())) {
114 | throw new ModelErrorException(searchUserModel.getError());
115 | }
116 | return searchUserModel.getUsers();
117 | }
118 | });
119 | }
120 |
121 |
122 | }
123 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ouwenjie/zhizhihu/ui/fragment/PostListFragment.java:
--------------------------------------------------------------------------------
1 | package com.ouwenjie.zhizhihu.ui.fragment;
2 |
3 | import android.os.Bundle;
4 | import android.os.Handler;
5 | import android.support.annotation.Nullable;
6 | import android.support.v7.widget.LinearLayoutManager;
7 | import android.support.v7.widget.RecyclerView;
8 | import android.util.Log;
9 | import android.view.LayoutInflater;
10 | import android.view.View;
11 | import android.view.ViewGroup;
12 |
13 | import com.ouwenjie.zhizhihu.R;
14 | import com.ouwenjie.zhizhihu.model.entity.Post;
15 | import com.ouwenjie.zhizhihu.presenter.PostListPresenter;
16 | import com.ouwenjie.zhizhihu.ui.activity.PostAnswersActivity;
17 | import com.ouwenjie.zhizhihu.ui.adapter.PostListAdapter;
18 | import com.ouwenjie.zhizhihu.ui.viewInterface.PostListViewInterface;
19 |
20 | import java.util.List;
21 |
22 | import butterknife.Bind;
23 | import butterknife.ButterKnife;
24 |
25 | /**
26 | * 精选 --> 推荐
27 | * Created by 文杰 on 2015/10/19.
28 | */
29 | public class PostListFragment extends SwipeRefreshFragment implements PostListViewInterface {
30 |
31 | @Bind(R.id.posts_list)
32 | RecyclerView mPostListView;
33 |
34 | private PostListAdapter mPostListAdapter;
35 | private boolean mIsFirstTimeTouchBottom = true;
36 |
37 | private PostListPresenter mPresenter;
38 |
39 | public PostListFragment() {
40 | }
41 |
42 | public static PostListFragment newInstance(@Nullable Bundle args) {
43 | PostListFragment fragment = new PostListFragment();
44 | fragment.setArguments(args);
45 | return fragment;
46 | }
47 |
48 | @Override
49 | public void onCreate(Bundle savedInstanceState) {
50 | super.onCreate(savedInstanceState);
51 | if (getArguments() != null) {
52 | }
53 | }
54 |
55 | @Nullable
56 | @Override
57 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
58 | View rootView = inflater.inflate(R.layout.fragment_posts, container, false);
59 | ButterKnife.bind(this, rootView);
60 | return rootView;
61 | }
62 |
63 | @Override
64 | public void onActivityCreated(@Nullable Bundle savedInstanceState) {
65 | super.onActivityCreated(savedInstanceState);
66 | mPresenter = new PostListPresenter(this);
67 | mPresenter.create();
68 |
69 | initView();
70 |
71 | new Handler().postDelayed(new Runnable() {
72 | @Override
73 | public void run() {
74 | mPresenter.initData();
75 | }
76 | }, 399);
77 | }
78 |
79 | @Override
80 | public void onDestroyView() {
81 | super.onDestroyView();
82 | mPresenter.destroy();
83 | }
84 |
85 |
86 | private void initView() {
87 | LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this.getContext());
88 | linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
89 | mPostListView.setLayoutManager(linearLayoutManager);
90 | mPostListView.addOnScrollListener(getScrollToBottomListener(linearLayoutManager));
91 | }
92 |
93 | @Override
94 | public void initList(final List postList) {
95 | mPostListAdapter = new PostListAdapter(this, postList);
96 | mPostListAdapter.setOnItemClickListener(new PostListAdapter.OnItemClickListener() {
97 | @Override
98 | public void onItemClick(View view, int position) {
99 | PostAnswersActivity.startActivity(getContext(), postList.get(position));
100 | }
101 | });
102 | mPostListView.setAdapter(mPostListAdapter);
103 | }
104 |
105 | /**
106 | * 重新刷新界面
107 | */
108 | @Override
109 | public void refreshList() {
110 | if (mPostListAdapter != null) {
111 | mPostListAdapter.notifyDataSetChanged();
112 | }
113 | }
114 |
115 | /**
116 | * 控制下拉刷新的控件
117 | *
118 | * @param refreshing
119 | */
120 | @Override
121 | public void setSwipeRefreshing(boolean refreshing) {
122 | setRefreshing(refreshing);
123 | }
124 |
125 | @Override
126 | public void onRefreshing() {
127 | // 界面有数据, 而且此刻没有正在加载下一页, 这个下拉就自己关掉吧
128 | if (mPresenter.hasData() && !mPresenter.isLoadingNextPage()) {
129 | super.onRefreshing();
130 | }
131 | }
132 |
133 | private RecyclerView.OnScrollListener getScrollToBottomListener(final LinearLayoutManager linearLayoutManager) {
134 | return new RecyclerView.OnScrollListener() {
135 | @Override
136 | public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
137 | int lastItem = linearLayoutManager.findLastCompletelyVisibleItemPosition();
138 | int count = mPostListAdapter.getItemCount() - 1;
139 | boolean isBottom = (lastItem == count);
140 | if (!mSwipeRefreshLayout.isRefreshing() && isBottom) {
141 | if (!mIsFirstTimeTouchBottom) {
142 | Log.e("onScrolled=>", "refresh.... ");
143 | mPresenter.loadNextPage();
144 | } else {
145 | mIsFirstTimeTouchBottom = false;
146 | }
147 | }
148 | }
149 | };
150 | }
151 |
152 | }
153 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ouwenjie/zhizhihu/ui/adapter/PostAnswerAdapter.java:
--------------------------------------------------------------------------------
1 | package com.ouwenjie.zhizhihu.ui.adapter;
2 |
3 | import android.app.Activity;
4 | import android.content.Context;
5 | import android.support.v4.app.Fragment;
6 | import android.support.v7.widget.CardView;
7 | import android.support.v7.widget.RecyclerView;
8 | import android.view.LayoutInflater;
9 | import android.view.View;
10 | import android.view.ViewGroup;
11 | import android.widget.ImageView;
12 | import android.widget.TextView;
13 |
14 | import com.bumptech.glide.Glide;
15 | import com.ouwenjie.zhizhihu.R;
16 | import com.ouwenjie.zhizhihu.model.entity.Answer;
17 |
18 | import java.util.List;
19 |
20 | import butterknife.Bind;
21 | import butterknife.ButterKnife;
22 | import de.hdodenhof.circleimageview.CircleImageView;
23 | import io.realm.Realm;
24 |
25 | /**
26 | * Created by 文杰 on 2015/10/18.
27 | */
28 | public class PostAnswerAdapter extends RecyclerView.Adapter {
29 |
30 | private List mAnswerList;
31 | private List mFavoriteList;
32 | private Context mContext;
33 | private OnItemClickListener mOnItemClickListener;
34 |
35 | public PostAnswerAdapter(Activity activity, List mAnswerList) {
36 | this.mContext = activity;
37 | this.mAnswerList = mAnswerList;
38 | mFavoriteList = Realm.getDefaultInstance().allObjects(Answer.class);
39 | }
40 |
41 | public PostAnswerAdapter(Fragment fragment, List mAnswerList) {
42 | this.mContext = fragment.getContext();
43 | this.mAnswerList = mAnswerList;
44 | }
45 |
46 | @Override
47 | public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
48 | final View itemView = LayoutInflater.from(parent.getContext())
49 | .inflate(R.layout.layout_answer_list_item, parent, false);
50 | itemView.setOnClickListener(new View.OnClickListener() {
51 | @Override
52 | public void onClick(final View v) {
53 | if (mOnItemClickListener != null) {
54 | mOnItemClickListener.onItemClick(v, (Integer) itemView.getTag());
55 | }
56 | }
57 | });
58 | itemView.findViewById(R.id.favorite_img).setOnClickListener(new View.OnClickListener() {
59 | @Override
60 | public void onClick(View v) {
61 | if (mOnItemClickListener != null) {
62 | mOnItemClickListener.onFavoriteClick(v, (Integer) itemView.getTag());
63 | }
64 | }
65 | });
66 | return new ViewHolder(itemView);
67 | }
68 |
69 | @Override
70 | public void onBindViewHolder(ViewHolder holder, int position) {
71 | holder.itemView.setTag(position);
72 | Answer answer = mAnswerList.get(position);
73 |
74 | String title = answer.getTitle();
75 | String time = answer.getTime();
76 | String summary = answer.getSummary();
77 | String questionid = answer.getQuestionid();
78 | String answerid = answer.getAnswerid();
79 | String authorName = answer.getAuthorname();
80 | String authorhash = answer.getAuthorhash();
81 | String avatar = answer.getAvatar();
82 | String vote = answer.getVote();
83 |
84 | holder.questionTxt.setText(title);
85 | holder.answerTxt.setText(summary);
86 | holder.authorNameTxt.setText(authorName);
87 | holder.likesTxt.setText(vote);
88 |
89 | Glide.with(mContext)
90 | .load(avatar)
91 | .fitCenter()
92 | .crossFade()
93 | .into(holder.authorAvatarImg);
94 |
95 | boolean isFavorite = false;
96 | for (Answer favorite : mFavoriteList) {
97 | if (favorite.getAnswerid().equals(answerid)) {
98 | isFavorite = true;
99 | break;
100 | }
101 | }
102 | if (isFavorite) {
103 | Glide.with(mContext)
104 | .load(R.drawable.ic_favorite_light)
105 | .into(holder.favoriteImg);
106 | } else {
107 | Glide.with(mContext)
108 | .load(R.drawable.ic_favorite_normal)
109 | .into(holder.favoriteImg);
110 | }
111 | }
112 |
113 | @Override
114 | public int getItemCount() {
115 | return mAnswerList.size();
116 | }
117 |
118 | public class ViewHolder extends RecyclerView.ViewHolder {
119 | @Bind(R.id.question_txt)
120 | TextView questionTxt;
121 | @Bind(R.id.author_avatar_img)
122 | CircleImageView authorAvatarImg;
123 | @Bind(R.id.answer_txt)
124 | TextView answerTxt;
125 | @Bind(R.id.author_name_txt)
126 | TextView authorNameTxt;
127 | @Bind(R.id.likes_txt)
128 | TextView likesTxt;
129 | @Bind(R.id.favorite_img)
130 | ImageView favoriteImg;
131 | @Bind(R.id.layout_item_root)
132 | CardView layoutItemRoot;
133 |
134 | public ViewHolder(View itemView) {
135 | super(itemView);
136 | ButterKnife.bind(this, itemView);
137 | }
138 | }
139 |
140 |
141 | public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
142 | this.mOnItemClickListener = onItemClickListener;
143 | }
144 |
145 | public interface OnItemClickListener {
146 | void onItemClick(View view, int position);
147 |
148 | void onFavoriteClick(View view, int position);
149 | }
150 | }
151 |
--------------------------------------------------------------------------------