public enum Tab {

    all(R.string.app_name),

    good(R.string.tab_good),

    unknown(R.string.tab_unknown),

    share(R.string.tab_share),

    ask(R.string.tab_ask),

    job(R.string.tab_job),

    dev(R.string.tab_dev);

    @StringRes
    private final int nameId;

    Tab(@StringRes int nameId) {
        this.nameId = nameId;
    }

    @StringRes
    public int getNameId() {
        return nameId;
    }

    public static List<Tab> getPublishableTabList() {
        List<Tab> tabList = new ArrayList<>();
        if (BuildConfig.DEBUG) {
            tabList.add(dev);
        }
        tabList.add(share);
        tabList.add(ask);
        tabList.add(job);
        return tabList;
    }

} public class Topic extends TopicSimple {

    @SerializedName("author_id")
    private String authorId;

    private Tab tab;

    private String content;

    private boolean good;

    private boolean top;

    @SerializedName("reply_count")
    private int replyCount;

    @SerializedName("visit_count")
    private int visitCount;

    @SerializedName("create_at")
    private DateTime createAt;

    public String getAuthorId() {
        return authorId;
    }

    public void setAuthorId(String authorId) {
        this.authorId = authorId;
    }

    @NonNull
    public Tab getTab() {
        return tab == null ? Tab.unknown : tab; // 接口中有些话题没有 Tab 属性，这里保证 Tab 不为空
    }

    public void setTab(Tab tab) {
        this.tab = tab;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
        cleanContentCache();
    }

    public boolean isGood() {
        return good;
    }

    public void setGood(boolean good) {
        this.good = good;
    }

    public boolean isTop() {
        return top;
    }

    public void setTop(boolean top) {
        this.top = top;
    }

    public int getReplyCount() {
        return replyCount;
    }

    public void setReplyCount(int replyCount) {
        this.replyCount = replyCount;
    }

    public int getVisitCount() {
        return visitCount;
    }

    public void setVisitCount(int visitCount) {
        this.visitCount = visitCount;
    }

    public DateTime getCreateAt() {
        return createAt;
    }

    public void setCreateAt(DateTime createAt) {
        this.createAt = createAt;
    }

    /**
     * Html渲染缓存
     */

    @SerializedName("content_html")
    private String contentHtml;

    @SerializedName("content_summary")
    private String contentSummary;

    public void markSureHandleContent() {
        if (contentHtml == null || contentSummary == null) {
            Document document;
            if (ApiDefine.MD_RENDER) {
                document = FormatUtils.handleHtml(content);
            } else {
                document = FormatUtils.handleHtml(FormatUtils.renderMarkdown(content));
            }
            if (contentHtml == null) {
                contentHtml = document.body().html();
            }
            if (contentSummary == null) {
                contentSummary = document.body().text().trim();
            }
        }
    }

    public void cleanContentCache() {
        contentHtml = null;
        contentSummary = null;
    }

    public String getContentHtml() {
        markSureHandleContent();
        return contentHtml;
    }

    public String getContentSummary() {
        markSureHandleContent();
        return contentSummary;
    }

} public final class LoginShared {

    private LoginShared() {}

    private static final String TAG = "LoginShared";

    private static final String KEY_ACCESS_TOKEN = "accessToken";
    private static final String KEY_ID = "id";
    private static final String KEY_LOGIN_NAME = "loginName";
    private static final String KEY_AVATAR_URL = "avatarUrl";
    private static final String KEY_SCORE = "score";

    private static String accessToken;
    private static String id;
    private static String loginName;
    private static String avatarUrl;
    private static Integer score;

    public static void login(@NonNull Context context, @NonNull String accessToken, @NonNull LoginResult loginResult) {
        SharedUtils.with(context, TAG)
                .setString(KEY_ACCESS_TOKEN, accessToken)
                .setString(KEY_ID, loginResult.getId())
                .setString(KEY_LOGIN_NAME, loginResult.getLoginName())
                .setString(KEY_AVATAR_URL, loginResult.getAvatarUrl());
        LoginShared.accessToken = accessToken;
        id = loginResult.getId();
        loginName = loginResult.getLoginName();
        avatarUrl = loginResult.getAvatarUrl();
    }

    public static void update(@NonNull Context context, @NonNull User user) {
        SharedUtils.with(context, TAG)
                .setString(KEY_LOGIN_NAME, user.getLoginName())
                .setString(KEY_AVATAR_URL, user.getAvatarUrl())
                .setInt(KEY_SCORE, user.getScore());
        loginName = user.getLoginName();
        avatarUrl = user.getAvatarUrl();
        score = user.getScore();
    }

    public static void logout(@NonNull Context context) {
        SharedUtils.with(context, TAG).clear();
        accessToken = null;
        id = null;
        loginName = null;
        avatarUrl = null;
        score = null;
    }

    public static String getAccessToken(@NonNull Context context) {
        if (TextUtils.isEmpty(accessToken)) {
            accessToken = SharedUtils.with(context, TAG).getString(KEY_ACCESS_TOKEN, null);
        }
        return accessToken;
    }

    public static String getId(@NonNull Context context) {
        if (TextUtils.isEmpty(id)) {
            id = SharedUtils.with(context, TAG).getString(KEY_ID, null);
        }
        return id;
    }

    public static String getLoginName(@NonNull Context context) {
        if (TextUtils.isEmpty(loginName)) {
            loginName = SharedUtils.with(context, TAG).getString(KEY_LOGIN_NAME, null);
        }
        return loginName;
    }

    public static String getAvatarUrl(@NonNull Context context) {
        if (TextUtils.isEmpty(avatarUrl)) {
            avatarUrl = SharedUtils.with(context, TAG).getString(KEY_AVATAR_URL, null);
        }
        return avatarUrl;
    }

    public static int getScore(@NonNull Context context) {
        if (score == null) {
            score = SharedUtils.with(context, TAG).getInt(KEY_SCORE, 0);
        }
        return score;
    }

} public final class SettingShared {

    private SettingShared() {}

    private static final String TAG = "SettingShared";

    private static final String KEY_ENABLE_NOTIFICATION = "enableNotification";
    private static final String KEY_ENABLE_THEME_DARK = "enableThemeDark";
    private static final String KEY_ENABLE_TOPIC_DRAFT = "enableTopicDraft";
    private static final String KEY_ENABLE_TOPIC_SIGN = "enableTopicSign";
    private static final String KEY_TOPIC_SIGN_CONTENT = "topicSignContent";
    private static final String KEY_ENABLE_TOPIC_RENDER_COMPAT = "enableTopicRenderCompat";
    private static final String KEY_SHOW_TOPIC_RENDER_COMPAT_TIP = "showTopicRenderCompatTip";

    public static final String DEFAULT_TOPIC_SIGN_CONTENT = "来自酷炫的 [CNodeMD](https://github.com/TakWolf/CNode-Material-Design)";

    public static boolean isEnableNotification(@NonNull Context context) {
        return SharedUtils.with(context, TAG).getBoolean(KEY_ENABLE_NOTIFICATION, true);
    }

    public static void setEnableNotification(@NonNull Context context, boolean enable) {
        SharedUtils.with(context, TAG).setBoolean(KEY_ENABLE_NOTIFICATION, enable);
    }

    public static boolean isEnableThemeDark(@NonNull Context context) {
        return SharedUtils.with(context, TAG).getBoolean(KEY_ENABLE_THEME_DARK, false);
    }

    public static void setEnableThemeDark(@NonNull Context context, boolean enable) {
        SharedUtils.with(context, TAG).setBoolean(KEY_ENABLE_THEME_DARK, enable);
    }

    public static boolean isEnableTopicDraft(@NonNull Context context) {
        return SharedUtils.with(context, TAG).getBoolean(KEY_ENABLE_TOPIC_DRAFT, true);
    }

    public static void setEnableTopicDraft(@NonNull Context context, boolean enable) {
        SharedUtils.with(context, TAG).setBoolean(KEY_ENABLE_TOPIC_DRAFT, enable);
    }

    public static boolean isEnableTopicSign(@NonNull Context context) {
        return SharedUtils.with(context, TAG).getBoolean(KEY_ENABLE_TOPIC_SIGN, true);
    }

    public static void setEnableTopicSign(@NonNull Context context, boolean enable) {
        SharedUtils.with(context, TAG).setBoolean(KEY_ENABLE_TOPIC_SIGN, enable);
    }

    public static String getTopicSignContent(@NonNull Context context) {
        return SharedUtils.with(context, TAG).getString(KEY_TOPIC_SIGN_CONTENT, DEFAULT_TOPIC_SIGN_CONTENT);
    }

    public static void setTopicSignContent(@NonNull Context context, @Nullable String content) {
        SharedUtils.with(context, TAG).setString(KEY_TOPIC_SIGN_CONTENT, content);
    }

    public static boolean isEnableTopicRenderCompat(@NonNull Context context) {
        return SharedUtils.with(context, TAG).getBoolean(KEY_ENABLE_TOPIC_RENDER_COMPAT, true);
    }

    public static void setEnableTopicRenderCompat(@NonNull Context context, boolean enable) {
        SharedUtils.with(context, TAG).setBoolean(KEY_ENABLE_TOPIC_RENDER_COMPAT, enable);
    }

    public static boolean isShowTopicRenderCompatTip(@NonNull Context context) {
        return SharedUtils.with(context, TAG).getBoolean(KEY_SHOW_TOPIC_RENDER_COMPAT_TIP, true);
    }

    public static void markShowTopicRenderCompatTip(@NonNull Context context) {
        SharedUtils.with(context, TAG).setBoolean(KEY_SHOW_TOPIC_RENDER_COMPAT_TIP, false);
    }

} public interface IMainPresenter {

    void switchTab(@NonNull Tab tab);

    void refreshTopicListAsyncTask();

    void loadMoreTopicListAsyncTask(int page);

    void getUserAsyncTask();

    void getMessageCountAsyncTask();

} public class MainPresenter implements IMainPresenter {

    private static final int PAGE_LIMIT = 20;

    private final Activity activity;
    private final IMainView mainView;

    private Tab tab = Tab.all;
    private Call<DataResult<List<Topic>>> refreshCall = null;
    private Call<DataResult<List<Topic>>> loadMoreCall = null;

    public MainPresenter(@NonNull Activity activity, @NonNull IMainView mainView) {
        this.activity = activity;
        this.mainView = mainView;
    }

    private void cancelRefreshCall() {
        if (refreshCall != null) {
            if (!refreshCall.isCanceled()) {
                refreshCall.cancel();
            }
            refreshCall = null;
        }
    }

    private void cancelLoadMoreCall() {
        if (loadMoreCall != null) {
            if (!loadMoreCall.isCanceled()) {
                loadMoreCall.cancel();
            }
            loadMoreCall = null;
        }
    }

    @Override
    public void switchTab(@NonNull Tab tab) {
        if (this.tab != tab) {
            this.tab = tab;
            cancelRefreshCall();
            cancelLoadMoreCall();
            mainView.onSwitchTabOk(tab);
        }
    }

    @Override
    public void refreshTopicListAsyncTask() {
        if (refreshCall == null) {
            final Call<DataResult<List<Topic>>> call = ApiClient.service.getTopicList(tab, 1, PAGE_LIMIT, ApiDefine.MD_RENDER);
            refreshCall = call;
            refreshCall.enqueue(new ToastCallback<DataResult<List<Topic>>>(activity) {

                @Override
                public boolean onResultOk(int code, Headers headers, DataResult<List<Topic>> result) {
                    handleTopicList(result.getData(), new OnHandleTopicListFinishListener() {

                        @Override
                        public void onHandleTopicListFinishListener(@NonNull List<Topic> topicList) {
                            if (call == refreshCall) {
                                cancelLoadMoreCall();
                                mainView.onRefreshTopicListOk(topicList);
                                onFinish();
                            }
                        }

                    });
                    return true;
                }

                @Override
                public boolean onAnyError(ErrorResult errorResult) {
                    mainView.onRefreshTopicListError();
                    return super.onAnyError(errorResult);
                }

                @Override
                public boolean onCallCancel() {
                    return true;
                }

                @Override
                public void onFinish() {
                    refreshCall = null;
                }

            });
        }
    }

    @Override
    public void loadMoreTopicListAsyncTask(int page) {
        if (loadMoreCall == null) {
            final Call<DataResult<List<Topic>>> call = ApiClient.service.getTopicList(tab, page, PAGE_LIMIT, ApiDefine.MD_RENDER);
            loadMoreCall = call;
            loadMoreCall.enqueue(new ToastCallback<DataResult<List<Topic>>>(activity) {

                @Override
                public boolean onResultOk(int code, Headers headers, DataResult<List<Topic>> result) {
                    handleTopicList(result.getData(), new OnHandleTopicListFinishListener() {

                        @Override
                        public void onHandleTopicListFinishListener(@NonNull List<Topic> topicList) {
                            if (call == loadMoreCall) {
                                mainView.onLoadMoreTopicListOk(topicList);
                                onFinish();
                            }
                        }

                    });
                    return true;
                }

                @Override
                public boolean onAnyError(ErrorResult errorResult) {
                    mainView.onLoadMoreTopicListError();
                    return super.onAnyError(errorResult);
                }

                @Override
                public boolean onCallCancel() {
                    return true;
                }

                @Override
                public void onFinish() {
                    loadMoreCall = null;
                }

            });
        }
    }

    @Override
    public void getUserAsyncTask() {
        final String accessToken = LoginShared.getAccessToken(activity);
        if (!TextUtils.isEmpty(accessToken)) {
            ApiClient.service.getUser(LoginShared.getLoginName(activity)).enqueue(new ForegroundCallback<DataResult<User>>(activity) {

                @Override
                public boolean onResultOk(int code, Headers headers, DataResult<User> result) {
                    if (TextUtils.equals(accessToken, LoginShared.getAccessToken(activity))) {
                        LoginShared.update(activity, result.getData());
                        mainView.updateUserInfoViews();
                    }
                    return false;
                }

            });
        }
    }

    @Override
    public void getMessageCountAsyncTask() {
        final String accessToken = LoginShared.getAccessToken(activity);
        if (!TextUtils.isEmpty(accessToken)) {
            ApiClient.service.getMessageCount(accessToken).enqueue(new ForegroundCallback<DataResult<Integer>>(activity) {

                @Override
                public boolean onResultOk(int code, Headers headers, DataResult<Integer> result) {
                    if (TextUtils.equals(accessToken, LoginShared.getAccessToken(activity))) {
                        mainView.updateMessageCountViews(result.getData());
                    }
                    return false;
                }

            });
        }
    }

    private void handleTopicList(@NonNull final List<Topic> topicList, @NonNull final OnHandleTopicListFinishListener listener) {
        if (topicList.isEmpty()) {
            listener.onHandleTopicListFinishListener(topicList);
        } else {
            new Thread(new Runnable() {

                @Override
                public void run() {
                    for (Topic topic : topicList) {
                        topic.markSureHandleContent();
                    }
                    HandlerUtils.handler.post(new Runnable() {

                        @Override
                        public void run() {
                            listener.onHandleTopicListFinishListener(topicList);
                        }

                    });
                }

            }).start();
        }
    }

    private interface OnHandleTopicListFinishListener {

        void onHandleTopicListFinishListener(@NonNull List<Topic> topicList);

    }

} public class TopicListAdapter extends RecyclerView.Adapter<TopicListAdapter.ViewHolder> {

    private final Activity activity;
    private final LayoutInflater inflater;
    private final List<Topic> topicList = new ArrayList<>();

    public TopicListAdapter(@NonNull Activity activity) {
        this.activity = activity;
        inflater = LayoutInflater.from(activity);
    }

    public void clearTopicListAndNotify() {
        topicList.clear();
        notifyDataSetChanged();
    }

    public void setTopicListAndNotify(@NonNull List<Topic> topicList) {
        this.topicList.clear();
        this.topicList.addAll(topicList);
        notifyDataSetChanged();
    }

    public void appendTopicListAndNotify(@NonNull List<Topic> topicList) {
        int startPosition = this.topicList.size();
        this.topicList.addAll(topicList);
        notifyItemRangeInserted(startPosition, topicList.size());
    }

    @Override
    public int getItemCount() {
        return topicList.size();
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return new ViewHolder(inflater.inflate(R.layout.item_topic, parent, false));
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        holder.bind(topicList.get(position));
    }

    class ViewHolder extends RecyclerView.ViewHolder {

        @BindView(R.id.ctv_tab)
        CheckedTextView ctvTab;

        @BindView(R.id.tv_reply_count)
        TextView tvReplyCount;

        @BindView(R.id.tv_visit_count)
        TextView tvVisitCount;

        @BindView(R.id.tv_last_reply_time)
        TextView tvLastReplyTime;

        @BindView(R.id.tv_title)
        TextView tvTitle;

        @BindView(R.id.tv_summary)
        TextView tvSummary;

        @BindView(R.id.img_avatar)
        ImageView imgAvatar;

        @BindView(R.id.tv_author)
        TextView tvAuthor;

        @BindView(R.id.tv_create_time)
        TextView tvCreateTime;

        @BindView(R.id.icon_good)
        View iconGood;

        private Topic topic;

        ViewHolder(@NonNull View itemView) {
            super(itemView);
            ButterKnife.bind(this, itemView);
        }

        void bind(@NonNull Topic topic) {
            this.topic = topic;

            ctvTab.setText(topic.isTop() ? R.string.tab_top : topic.getTab().getNameId());
            ctvTab.setChecked(topic.isTop());
            tvReplyCount.setText(String.valueOf(topic.getReplyCount()));
            tvVisitCount.setText(String.valueOf(topic.getVisitCount()));
            tvLastReplyTime.setText(FormatUtils.getRelativeTimeSpanString(topic.getLastReplyAt()));
            tvTitle.setText(topic.getTitle());
            tvSummary.setText(topic.getContentSummary());
            GlideApp.with(activity).load(topic.getAuthor().getAvatarUrl()).placeholder(R.drawable.image_placeholder).into(imgAvatar);
            tvAuthor.setText(topic.getAuthor().getLoginName());
            tvCreateTime.setText(activity.getString(R.string.create_at__, topic.getCreateAt().toString("yyyy-MM-dd HH:mm:ss")));
            iconGood.setVisibility(topic.isGood() ? View.VISIBLE : View.GONE);
        }

        @OnClick(R.id.btn_topic)
        void onBtnTopicClick() {
            Navigator.TopicWithAutoCompat.start(activity, topic);
        }

        @OnClick(R.id.btn_user)
        void onBtnUserClick() {
            UserDetailActivity.startWithTransitionAnimation(activity, topic.getAuthor().getLoginName(), imgAvatar, topic.getAuthor().getAvatarUrl());
        }

    }

} public final class AlertDialogUtils {

    private AlertDialogUtils() {}

    public static AlertDialog.Builder createBuilderWithAutoTheme(@NonNull Activity activity) {
        return new AlertDialog.Builder(activity, SettingShared.isEnableThemeDark(activity) ? R.style.AppDialogDark_Alert : R.style.AppDialogLight_Alert);
    }

} public class LoadMoreFooter {

    public static final int STATE_DISABLED = 0;
    public static final int STATE_LOADING = 1;
    public static final int STATE_FINISHED = 2;
    public static final int STATE_ENDLESS = 3;
    public static final int STATE_FAILED = 4;

    @IntDef({STATE_DISABLED, STATE_LOADING, STATE_FINISHED, STATE_ENDLESS, STATE_FAILED})
    @Retention(RetentionPolicy.SOURCE)
    public @interface State {}

    public interface OnLoadMoreListener {

        void onLoadMore();

    }

    @BindView(R.id.progress_wheel)
    ProgressWheel progressWheel;

    @BindView(R.id.tv_text)
    TextView tvText;

    @State
    private int state = STATE_DISABLED;

    private final OnLoadMoreListener loadMoreListener;

    public LoadMoreFooter(@NonNull Context context, @NonNull HeaderAndFooterRecyclerView recyclerView, @NonNull OnLoadMoreListener loadMoreListener) {
        this.loadMoreListener = loadMoreListener;
        View footerView = LayoutInflater.from(context).inflate(R.layout.footer_load_more, recyclerView.getFooterContainer(), false);
        recyclerView.addFooterView(footerView);
        ButterKnife.bind(this, footerView);
        recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {

            @Override
            public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
                if (!recyclerView.canScrollVertically(1)) {
                    checkLoadMore();
                }
            }

            @Override
            public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
                if (!recyclerView.canScrollVertically(1)) {
                    checkLoadMore();
                }
            }

        });
    }

    @State
    public int getState() {
        return state;
    }

    public void setState(@State int state) {
        if (this.state != state) {
            this.state = state;
            switch (state) {
                case STATE_DISABLED:
                    progressWheel.setVisibility(View.INVISIBLE);
                    progressWheel.stopSpinning();
                    tvText.setVisibility(View.INVISIBLE);
                    tvText.setText(null);
                    tvText.setClickable(false);
                    break;
                case STATE_LOADING:
                    progressWheel.setVisibility(View.VISIBLE);
                    progressWheel.spin();
                    tvText.setVisibility(View.INVISIBLE);
                    tvText.setText(null);
                    tvText.setClickable(false);
                    break;
                case STATE_FINISHED:
                    progressWheel.setVisibility(View.INVISIBLE);
                    progressWheel.stopSpinning();
                    tvText.setVisibility(View.VISIBLE);
                    tvText.setText(R.string.load_more_finished);
                    tvText.setClickable(false);
                    break;
                case STATE_ENDLESS:
                    progressWheel.setVisibility(View.INVISIBLE);
                    progressWheel.stopSpinning();
                    tvText.setVisibility(View.VISIBLE);
                    tvText.setText(null);
                    tvText.setClickable(true);
                    break;
                case STATE_FAILED:
                    progressWheel.setVisibility(View.INVISIBLE);
                    progressWheel.stopSpinning();
                    tvText.setVisibility(View.VISIBLE);
                    tvText.setText(R.string.load_more_failed);
                    tvText.setClickable(true);
                    break;
                default:
                    throw new AssertionError("Unknown load more state.");
            }
        }
    }

    private void checkLoadMore() {
        if (getState() == STATE_ENDLESS || getState() == STATE_FAILED) {
            setState(STATE_LOADING);
            loadMoreListener.onLoadMore();
        }
    }

    @OnClick(R.id.tv_text)
    void onBtnTextClick() {
        checkLoadMore();
    }

} public class DoubleClickBackToContentTopListener extends OnDoubleClickListener {

    private final IBackToContentTopView backToContentTopView;

    public DoubleClickBackToContentTopListener(@NonNull IBackToContentTopView backToContentTopView) {
        super(300);
        this.backToContentTopView = backToContentTopView;
    }

    @Override
    public void onDoubleClick(View v) {
        backToContentTopView.backToContentTop();
    }

} public final class FloatingActionButtonBehaviorListener {

    private FloatingActionButtonBehaviorListener() {}

    private static final int SCROLL_THRESHOLD = 4;

    public static class ForRecyclerView extends RecyclerView.OnScrollListener {

        private final FloatingActionButton floatingActionButton;

        public ForRecyclerView(@NonNull FloatingActionButton floatingActionButton) {
            this.floatingActionButton = floatingActionButton;
        }

        @Override
        public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
            if (dy > SCROLL_THRESHOLD) {
                floatingActionButton.hide();
            } else if (dy < -SCROLL_THRESHOLD) {
                floatingActionButton.show();
            }
        }

    }

    public static class ForWebView implements CNodeWebView.OnScrollListener {

        private final FloatingActionButton floatingActionButton;

        public ForWebView(@NonNull FloatingActionButton floatingActionButton) {
            this.floatingActionButton = floatingActionButton;
        }

        @Override
        public void onScrollChanged(int l, int t, int oldl, int oldt) {
            if (t - oldt > SCROLL_THRESHOLD) {
                floatingActionButton.hide();
            } else if (t - oldt < -SCROLL_THRESHOLD) {
                floatingActionButton.show();
            }
        }

    }

} public class NavigationOpenClickListener implements View.OnClickListener {

    private final DrawerLayout drawerLayout;

    public NavigationOpenClickListener(@NonNull DrawerLayout drawerLayout) {
        this.drawerLayout = drawerLayout;
    }

    @Override
    public void onClick(View v) {
        drawerLayout.openDrawer(GravityCompat.START);
    }

} public final class Navigator {

    private Navigator() {}

    public static void openInMarket(@NonNull Context context) {
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.setData(Uri.parse("market://details?id=" + context.getPackageName()));
        if (intent.resolveActivity(context.getPackageManager()) != null) {
            context.startActivity(intent);
        } else {
            ToastUtils.with(context).show(R.string.no_market_install_in_system);
        }
    }

    public static void openInBrowser(@NonNull Context context, @NonNull String url) {
        Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        if (intent.resolveActivity(context.getPackageManager()) != null) {
            context.startActivity(intent);
        } else {
            ToastUtils.with(context).show(R.string.no_browser_install_in_system);
        }
    }

    public static void openEmail(@NonNull Context context, @NonNull String email, @NonNull String subject, @NonNull String text) {
        Intent intent = new Intent(Intent.ACTION_SENDTO);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.setData(Uri.parse("mailto:" + email));
        if (intent.resolveActivity(context.getPackageManager()) != null) {
            intent.putExtra(Intent.EXTRA_SUBJECT, subject);
            intent.putExtra(Intent.EXTRA_TEXT, text);
            context.startActivity(intent);
        } else {
            ToastUtils.with(context).show(R.string.no_email_client_install_in_system);
        }
    }

    public static void openShare(@NonNull Context context, @NonNull String text) {
        Intent intent = new Intent(Intent.ACTION_SEND);
        intent.setType("text/plain");
        intent.putExtra(Intent.EXTRA_TEXT, text);
        context.startActivity(Intent.createChooser(intent, context.getString(R.string.share)));
    }

    public static boolean openStandardLink(@NonNull Context context, @Nullable String url) {
        if (FormatUtils.isUserLinkUrl(url)) {
            UserDetailActivity.start(context, Uri.parse(url).getPath().replace(ApiDefine.USER_PATH_PREFIX, ""));
            return true;
        } else if (FormatUtils.isTopicLinkUrl(url)) {
            TopicWithAutoCompat.start(context, Uri.parse(url).getPath().replace(ApiDefine.TOPIC_PATH_PREFIX, ""));
            return true;
        } else {
            return false;
        }
    }

    public static final class TopicWithAutoCompat {

        private TopicWithAutoCompat() {}

        public static final String EXTRA_TOPIC_ID = "topicId";
        public static final String EXTRA_TOPIC = "topic";

        private static Class<?> getTargetClass(@NonNull Context context) {
            return SettingShared.isEnableTopicRenderCompat(context) ? TopicCompatActivity.class : TopicActivity.class;
        }

        public static void start(@NonNull Activity activity, @NonNull Topic topic) {
            Intent intent = new Intent(activity, getTargetClass(activity));
            intent.putExtra(EXTRA_TOPIC_ID, topic.getId());
            intent.putExtra(EXTRA_TOPIC, EntityUtils.gson.toJson(topic));
            activity.startActivity(intent);
        }

        public static void start(@NonNull Activity activity, String topicId) {
            Intent intent = new Intent(activity, getTargetClass(activity));
            intent.putExtra(EXTRA_TOPIC_ID, topicId);
            activity.startActivity(intent);
        }

        public static void start(@NonNull Context context, String topicId) {
            Intent intent = new Intent(context, getTargetClass(context));
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            intent.putExtra(EXTRA_TOPIC_ID, topicId);
            context.startActivity(intent);
        }

    }

    public static final class NotificationWithAutoCompat {

        private NotificationWithAutoCompat() {}

        private static Class<?> getTargetClass(@NonNull Context context) {
            return SettingShared.isEnableTopicRenderCompat(context) ? NotificationCompatActivity.class : NotificationActivity.class;
        }

        public static void start(@NonNull Activity activity) {
            activity.startActivity(new Intent(activity, getTargetClass(activity)));
        }

    }

} public final class ThemeUtils {

    private ThemeUtils() {}

    public static boolean configThemeBeforeOnCreate(@NonNull Activity activity, @StyleRes int light, @StyleRes int dark) {
        boolean enable = SettingShared.isEnableThemeDark(activity);
        activity.setTheme(enable ? dark : light);
        return enable;
    }

    public static void notifyThemeApply(@NonNull final Activity activity) {
        HandlerUtils.handler.post(new Runnable() {

            @Override
            public void run() {
                activity.recreate();
            }

        });
    }

} public final class ToastUtils {

    private volatile static ToastUtils singleton;

    public static ToastUtils with(@NonNull Context context) {
        if (singleton == null) {
            synchronized (ToastUtils.class) {
                if (singleton == null) {
                    singleton = new ToastUtils(context);
                }
            }
        }
        return singleton;
    }

    private final Toast toast;

    @SuppressLint("ShowToast")
    private ToastUtils(@NonNull Context context) {
        toast = Toast.makeText(context.getApplicationContext(), null, Toast.LENGTH_SHORT);
    }

    public void show(CharSequence msg) {
        toast.setText(msg);
        toast.show();
    }

    public void show(@StringRes int resId) {
        toast.setText(resId);
        toast.show();
    }

} public interface IBackToContentTopView {

    void backToContentTop();

} public interface IMainView {

    void onSwitchTabOk(@NonNull Tab tab);

    void onRefreshTopicListOk(@NonNull List<Topic> topicList);

    void onRefreshTopicListError();

    void onLoadMoreTopicListOk(@NonNull List<Topic> topicList);

    void onLoadMoreTopicListError();

    void updateUserInfoViews();

    void updateMessageCountViews(int count);

} public class NavigationItem extends FrameLayout implements Checkable {

    @BindView(R.id.icon_item)
    View iconItem;

    @BindView(R.id.img_icon)
    ImageView imgIcon;

    @BindView(R.id.tv_title)
    TextView tvTitle;

    @BindView(R.id.tv_badge)
    TextView tvBadge;

    private Drawable iconNormal;
    private Drawable iconChecked;
    private boolean checked;

    public NavigationItem(@NonNull Context context) {
        super(context);
        init(context, null, 0, 0);
    }

    public NavigationItem(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs, 0, 0);
    }

    public NavigationItem(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs, defStyleAttr, 0);
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public NavigationItem(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init(context, attrs, defStyleAttr, defStyleRes);
    }

    private void init(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
        LayoutInflater.from(context).inflate(R.layout.widget_navigation_item, this, true);
        ButterKnife.bind(this, this);

        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.NavigationItem, defStyleAttr, defStyleRes);

        iconNormal = a.getDrawable(R.styleable.NavigationItem_iconNormal);
        iconChecked = a.getDrawable(R.styleable.NavigationItem_iconChecked);
        checked = a.getBoolean(R.styleable.NavigationItem_android_checked, false);

        imgIcon.setImageDrawable(checked ? iconChecked : iconNormal);
        tvTitle.setTextColor(ResUtils.getThemeAttrColor(context, checked ? R.attr.colorAccent : android.R.attr.textColorPrimary));
        iconItem.setBackgroundColor(checked ? ResUtils.getThemeAttrColor(context, R.attr.widgetBackgroundDark) : Color.TRANSPARENT);

        tvTitle.setText(a.getText(R.styleable.NavigationItem_title));
        setBadge(a.getInt(R.styleable.NavigationItem_badge, 0));

        a.recycle();
    }

    @Override
    public void setChecked(boolean checked) {
        if (this.checked != checked) {
            this.checked = checked;
            imgIcon.setImageDrawable(checked ? iconChecked : iconNormal);
            tvTitle.setTextColor(ResUtils.getThemeAttrColor(getContext(), checked ? R.attr.colorAccent : android.R.attr.textColorPrimary));
            iconItem.setBackgroundColor(checked ? ResUtils.getThemeAttrColor(getContext(), R.attr.widgetBackgroundDark) : Color.TRANSPARENT);
        }
    }

    @Override
    public boolean isChecked() {
        return checked;
    }

    @Override
    public void toggle() {
        setChecked(!checked);
    }

    public void setBadge(int count) {
        if (count > 99) {
            tvBadge.setText(R.string.navigation_badge_count_max);
        } else if (count <= 0) {
            tvBadge.setText(null);
        } else {
            tvBadge.setText(String.valueOf(count));
        }
        tvBadge.setVisibility(count > 0 ? VISIBLE : GONE);
    }

} public final class HandlerUtils {

    private HandlerUtils() {}

    public static final Handler handler = new Handler(Looper.getMainLooper());

}
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.FloatingActionButton;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.Toolbar;
import android.text.TextUtils;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import com.takwolf.android.hfrecyclerview.HeaderAndFooterRecyclerView;
import org.cnodejs.android.md.BuildConfig;
import org.cnodejs.android.md.R;
import org.cnodejs.android.md.model.entity.Tab;
import org.cnodejs.android.md.model.entity.Topic;
import org.cnodejs.android.md.model.glide.GlideApp;
import org.cnodejs.android.md.model.storage.LoginShared;
import org.cnodejs.android.md.model.storage.SettingShared;
import org.cnodejs.android.md.presenter.contract.IMainPresenter;
import org.cnodejs.android.md.presenter.implement.MainPresenter;
import org.cnodejs.android.md.ui.adapter.TopicListAdapter;
import org.cnodejs.android.md.ui.dialog.AlertDialogUtils;
import org.cnodejs.android.md.ui.holder.LoadMoreFooter;
import org.cnodejs.android.md.ui.listener.DoubleClickBackToContentTopListener;
import org.cnodejs.android.md.ui.listener.FloatingActionButtonBehaviorListener;
import org.cnodejs.android.md.ui.listener.NavigationOpenClickListener;
import org.cnodejs.android.md.ui.util.Navigator;
import org.cnodejs.android.md.ui.util.ThemeUtils;
import org.cnodejs.android.md.ui.util.ToastUtils;
import org.cnodejs.android.md.ui.view.IBackToContentTopView;
import org.cnodejs.android.md.ui.view.IMainView;
import org.cnodejs.android.md.ui.widget.NavigationItem;
import org.cnodejs.android.md.util.HandlerUtils;
import java.util.List;
import butterknife.BindView;
import butterknife.BindViews;
import butterknife.ButterKnife;
import butterknife.OnClick;
package org.cnodejs.android.md.ui.activity;






public class MainActivity extends FullLayoutActivity implements IMainView, IBackToContentTopView, SwipeRefreshLayout.OnRefreshListener, LoadMoreFooter.OnLoadMoreListener {

    /*
     * 抽屉导航布局
     */

    @BindView(R.id.drawer_layout)
    DrawerLayout drawerLayout;

    /*
     * 导航部分的个人信息
     */

    @BindView(R.id.img_avatar)
    ImageView imgAvatar;

    @BindView(R.id.tv_login_name)
    TextView tvLoginName;

    @BindView(R.id.tv_score)
    TextView tvScore;

    @BindView(R.id.btn_logout)
    View btnLogout;

    @BindView(R.id.btn_theme_dark)
    ImageView imgThemeDark;

    @BindView(R.id.nav_top_background)
    View navTopBackground;

    /*
     * 导航项
     */

    @BindViews({
            R.id.btn_nav_all,
            R.id.btn_nav_good,
            R.id.btn_nav_share,
            R.id.btn_nav_ask,
            R.id.btn_nav_job,
            R.id.btn_nav_dev
    })
    List<NavigationItem> navMainItemList;

    @BindView(R.id.btn_nav_dev)
    NavigationItem navItemDev;

    @BindView(R.id.btn_nav_notification)
    NavigationItem navItemNotification;

    /*
     * 内容部分
     */

    @BindView(R.id.toolbar)
    Toolbar toolbar;

    @BindView(R.id.refresh_layout)
    SwipeRefreshLayout refreshLayout;

    @BindView(R.id.recycler_view)
    HeaderAndFooterRecyclerView recyclerView;

    @BindView(R.id.fab_create_topic)
    FloatingActionButton fabCreateTopic;

    private LoadMoreFooter loadMoreFooter;
    private TopicListAdapter adapter;

    private IMainPresenter mainPresenter;

    private int page = 0;
    private long firstBackPressedTime = 0;
    private boolean enableThemeDark;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        enableThemeDark = ThemeUtils.configThemeBeforeOnCreate(this, R.style.AppThemeLight, R.style.AppThemeDark);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);

        drawerLayout.setDrawerShadow(R.drawable.navigation_drawer_shadow, GravityCompat.START);
        drawerLayout.addDrawerListener(drawerListener);
        toolbar.setNavigationOnClickListener(new NavigationOpenClickListener(drawerLayout));
        toolbar.setOnClickListener(new DoubleClickBackToContentTopListener(this));

        navItemDev.setVisibility(BuildConfig.DEBUG ? View.VISIBLE : View.GONE);

        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        loadMoreFooter = new LoadMoreFooter(this, recyclerView, this);
        adapter = new TopicListAdapter(this);
        recyclerView.setAdapter(adapter);
        recyclerView.addOnScrollListener(new FloatingActionButtonBehaviorListener.ForRecyclerView(fabCreateTopic));