org.mariotaku.twidere.fragment.support.StatusFragment.java Source code

Java tutorial

Introduction

Here is the source code for org.mariotaku.twidere.fragment.support.StatusFragment.java

Source

/*
 * Twidere - Twitter client for Android
 *
 *  Copyright (C) 2012-2014 Mariotaku Lee <mariotaku.lee@gmail.com>
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.mariotaku.twidere.fragment.support;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Point;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.NfcAdapter.CreateNdefMessageCallback;
import android.nfc.NfcEvent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentManagerTrojan;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.Loader;
import android.support.v4.util.Pair;
import android.support.v7.widget.ActionMenuView;
import android.support.v7.widget.CardView;
import android.support.v7.widget.FixedLinearLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.PopupMenu;
import android.support.v7.widget.PopupMenu.OnMenuItemClickListener;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.Adapter;
import android.support.v7.widget.RecyclerView.LayoutParams;
import android.support.v7.widget.RecyclerView.ViewHolder;
import android.text.Html;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.method.ArrowKeyMovementMethod;
import android.text.method.LinkMovementMethod;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.Space;
import android.widget.TextView;

import org.mariotaku.twidere.R;
import org.mariotaku.twidere.activity.support.ColorPickerDialogActivity;
import org.mariotaku.twidere.adapter.AbsStatusesAdapter.StatusAdapterListener;
import org.mariotaku.twidere.adapter.decorator.DividerItemDecoration;
import org.mariotaku.twidere.adapter.iface.IStatusesAdapter;
import org.mariotaku.twidere.api.twitter.Twitter;
import org.mariotaku.twidere.api.twitter.TwitterException;
import org.mariotaku.twidere.api.twitter.model.Paging;
import org.mariotaku.twidere.app.TwidereApplication;
import org.mariotaku.twidere.constant.IntentConstants;
import org.mariotaku.twidere.loader.support.ParcelableStatusLoader;
import org.mariotaku.twidere.loader.support.StatusRepliesLoader;
import org.mariotaku.twidere.model.ListResponse;
import org.mariotaku.twidere.model.ParcelableCredentials;
import org.mariotaku.twidere.model.ParcelableLocation;
import org.mariotaku.twidere.model.ParcelableMedia;
import org.mariotaku.twidere.model.ParcelableStatus;
import org.mariotaku.twidere.model.SingleResponse;
import org.mariotaku.twidere.util.AsyncTaskUtils;
import org.mariotaku.twidere.util.AsyncTwitterWrapper;
import org.mariotaku.twidere.util.CompareUtils;
import org.mariotaku.twidere.util.KeyboardShortcutsHandler;
import org.mariotaku.twidere.util.KeyboardShortcutsHandler.KeyboardShortcutCallback;
import org.mariotaku.twidere.util.LinkCreator;
import org.mariotaku.twidere.util.MediaLoaderWrapper;
import org.mariotaku.twidere.util.MediaLoadingHandler;
import org.mariotaku.twidere.util.RecyclerViewNavigationHelper;
import org.mariotaku.twidere.util.RecyclerViewUtils;
import org.mariotaku.twidere.util.SharedPreferencesWrapper;
import org.mariotaku.twidere.util.StatusActionModeCallback;
import org.mariotaku.twidere.util.StatusAdapterLinkClickHandler;
import org.mariotaku.twidere.util.StatusLinkClickHandler;
import org.mariotaku.twidere.util.ThemeUtils;
import org.mariotaku.twidere.util.TwidereLinkify;
import org.mariotaku.twidere.util.TwitterAPIFactory;
import org.mariotaku.twidere.util.TwitterCardUtils;
import org.mariotaku.twidere.util.UserColorNameManager;
import org.mariotaku.twidere.util.Utils;
import org.mariotaku.twidere.view.CardMediaContainer;
import org.mariotaku.twidere.view.CardMediaContainer.OnMediaClickListener;
import org.mariotaku.twidere.view.ColorLabelRelativeLayout;
import org.mariotaku.twidere.view.ForegroundColorView;
import org.mariotaku.twidere.view.ShapedImageView;
import org.mariotaku.twidere.view.StatusTextView;
import org.mariotaku.twidere.view.TwitterCardContainer;
import org.mariotaku.twidere.view.holder.GapViewHolder;
import org.mariotaku.twidere.view.holder.LoadIndicatorViewHolder;
import org.mariotaku.twidere.view.holder.StatusViewHolder;

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

import edu.tsinghua.spice.Utilies.SpiceProfilingUtil;
import edu.tsinghua.spice.Utilies.TypeMappingUtil;

/**
 * Created by mariotaku on 14/12/5.
 */
public class StatusFragment extends BaseSupportFragment
        implements LoaderCallbacks<SingleResponse<ParcelableStatus>>, OnMediaClickListener, StatusAdapterListener,
        KeyboardShortcutCallback {

    // Constants
    private static final int LOADER_ID_DETAIL_STATUS = 1;
    private static final int LOADER_ID_STATUS_REPLIES = 2;
    private static final int STATE_LOADED = 1;
    private static final int STATE_LOADING = 2;
    private static final int STATE_ERROR = 3;

    // Views
    private View mStatusContent;
    private View mProgressContainer;
    private View mErrorContainer;
    private RecyclerView mRecyclerView;

    private DividerItemDecoration mItemDecoration;
    private PopupMenu mPopupMenu;

    private StatusAdapter mStatusAdapter;
    private LinearLayoutManager mLayoutManager;

    private LoadConversationTask mLoadConversationTask;
    private RecyclerViewNavigationHelper mRecyclerViewNavigationHelper;

    // Data fields
    private boolean mRepliesLoaderInitialized;
    private ParcelableStatus mSelectedStatus;

    // Listeners
    private LoaderCallbacks<List<ParcelableStatus>> mRepliesLoaderCallback = new LoaderCallbacks<List<ParcelableStatus>>() {
        @Override
        public Loader<List<ParcelableStatus>> onCreateLoader(int id, Bundle args) {
            mStatusAdapter.updateItemDecoration();
            final long accountId = args.getLong(EXTRA_ACCOUNT_ID, -1);
            final String screenName = args.getString(EXTRA_SCREEN_NAME);
            final long statusId = args.getLong(EXTRA_STATUS_ID, -1);
            final long maxId = args.getLong(EXTRA_MAX_ID, -1);
            final long sinceId = args.getLong(EXTRA_SINCE_ID, -1);

            final StatusRepliesLoader loader = new StatusRepliesLoader(getActivity(), accountId, screenName,
                    statusId, maxId, sinceId, null, null, 0, true);
            loader.setComparator(ParcelableStatus.REVERSE_ID_COMPARATOR);
            return loader;
        }

        @Override
        public void onLoadFinished(Loader<List<ParcelableStatus>> loader, List<ParcelableStatus> data) {
            mStatusAdapter.updateItemDecoration();
            final Pair<Long, Integer> readPosition = saveReadPosition();
            setReplies(data);
            restoreReadPosition(readPosition);
        }

        @Override
        public void onLoaderReset(Loader<List<ParcelableStatus>> loader) {

        }
    };
    private OnMenuItemClickListener mOnStatusMenuItemClickListener = new OnMenuItemClickListener() {
        @Override
        public boolean onMenuItemClick(MenuItem item) {
            final ParcelableStatus status = mSelectedStatus;
            if (status == null)
                return false;
            return Utils.handleMenuItemClick(getActivity(), StatusFragment.this, getFragmentManager(),
                    getTwitterWrapper(), status, item);
        }
    };

    @Override
    public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
        final FragmentActivity activity = getActivity();
        if (activity == null)
            return;
        switch (requestCode) {
        case REQUEST_SET_COLOR: {
            final ParcelableStatus status = mStatusAdapter.getStatus();
            if (status == null)
                return;
            final UserColorNameManager manager = UserColorNameManager.getInstance(activity);
            if (resultCode == Activity.RESULT_OK) {
                if (data == null)
                    return;
                final int color = data.getIntExtra(EXTRA_COLOR, Color.TRANSPARENT);
                manager.setUserColor(status.user_id, color);
            } else if (resultCode == ColorPickerDialogActivity.RESULT_CLEARED) {
                manager.clearUserColor(status.user_id);
            }
            break;
        }
        case REQUEST_SELECT_ACCOUNT: {
            final ParcelableStatus status = mStatusAdapter.getStatus();
            if (status == null)
                return;
            if (resultCode == Activity.RESULT_OK) {
                if (data == null || !data.hasExtra(EXTRA_ID))
                    return;
                final long accountId = data.getLongExtra(EXTRA_ID, -1);
                Utils.openStatus(activity, accountId, status.id);
            }
            break;
        }
        }
    }

    @Override
    public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGroup container,
            @Nullable final Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_status, container, false);
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        final View view = getView();
        if (view == null)
            throw new AssertionError();
        final Context context = view.getContext();
        final boolean compact = Utils.isCompactCards(context);
        Utils.setNdefPushMessageCallback(getActivity(), new CreateNdefMessageCallback() {
            @Override
            public NdefMessage createNdefMessage(NfcEvent event) {
                final ParcelableStatus status = getStatus();
                if (status == null)
                    return null;
                return new NdefMessage(new NdefRecord[] { NdefRecord
                        .createUri(LinkCreator.getTwitterStatusLink(status.user_screen_name, status.id)), });
            }
        });
        mLayoutManager = new StatusListLinearLayoutManager(context, mRecyclerView);
        mItemDecoration = new DividerItemDecoration(context, mLayoutManager.getOrientation());
        if (compact) {
            mRecyclerView.addItemDecoration(mItemDecoration);
        }
        mLayoutManager.setRecycleChildrenOnDetach(true);
        mRecyclerView.setLayoutManager(mLayoutManager);
        mRecyclerView.setClipToPadding(false);
        mStatusAdapter = new StatusAdapter(this, compact);
        mStatusAdapter.setEventListener(this);
        mRecyclerView.setAdapter(mStatusAdapter);

        mRecyclerViewNavigationHelper = new RecyclerViewNavigationHelper(mRecyclerView, mLayoutManager,
                mStatusAdapter, null);

        setState(STATE_LOADING);

        getLoaderManager().initLoader(LOADER_ID_DETAIL_STATUS, getArguments(), this);
    }

    @Override
    public void onBaseViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onBaseViewCreated(view, savedInstanceState);
        mStatusContent = view.findViewById(R.id.status_content);
        mRecyclerView = (RecyclerView) view.findViewById(R.id.recycler_view);
        mProgressContainer = view.findViewById(R.id.progress_container);
        mErrorContainer = view.findViewById(R.id.error_container);
    }

    @Override
    public void onGapClick(GapViewHolder holder, int position) {

    }

    @Override
    public void onMediaClick(StatusViewHolder holder, View view, ParcelableMedia media, int position) {
        final ParcelableStatus status = mStatusAdapter.getStatus(position);
        if (status == null)
            return;
        final Bundle options = Utils.createMediaViewerActivityOption(view);
        Utils.openMedia(getActivity(), status, media, options);
        SpiceProfilingUtil.log(status.id + ",Clicked," + status.account_id + "," + status.user_id + ","
                + status.text_plain.length() + "," + media.preview_url + "," + media.media_url + ","
                + TypeMappingUtil.getMediaType(media.type) + "," + mStatusAdapter.isMediaPreviewEnabled() + ","
                + status.timestamp);
        SpiceProfilingUtil.profile(getActivity(), status.account_id,
                status.id + ",Clicked," + status.account_id + "," + status.user_id + ","
                        + status.text_plain.length() + "," + media.preview_url + "," + media.media_url + ","
                        + TypeMappingUtil.getMediaType(media.type) + "," + mStatusAdapter.isMediaPreviewEnabled()
                        + "," + status.timestamp);
    }

    @Override
    public void onStatusActionClick(StatusViewHolder holder, int id, int position) {
        final ParcelableStatus status = mStatusAdapter.getStatus(position);
        if (status == null)
            return;
        switch (id) {
        case R.id.reply_count: {
            final Context context = getActivity();
            final Intent intent = new Intent(IntentConstants.INTENT_ACTION_REPLY);
            intent.setPackage(context.getPackageName());
            intent.putExtra(IntentConstants.EXTRA_STATUS, status);
            context.startActivity(intent);
            break;
        }
        case R.id.retweet_count: {
            RetweetQuoteDialogFragment.show(getFragmentManager(), status);
            break;
        }
        case R.id.favorite_count: {
            final AsyncTwitterWrapper twitter = getTwitterWrapper();
            if (twitter == null)
                return;
            if (status.is_favorite) {
                twitter.destroyFavoriteAsync(status.account_id, status.id);
            } else {
                twitter.createFavoriteAsync(status.account_id, status.id);
            }
            break;
        }
        }
    }

    @Override
    public void onStatusClick(StatusViewHolder holder, int position) {
        Utils.openStatus(getActivity(), mStatusAdapter.getStatus(position), null);
    }

    @Override
    public boolean onStatusLongClick(StatusViewHolder holder, int position) {
        return false;
    }

    @Override
    public void onStatusMenuClick(StatusViewHolder holder, View menuView, int position) {
        //TODO show status menu
        if (mPopupMenu != null) {
            mPopupMenu.dismiss();
        }
        final PopupMenu popupMenu = new PopupMenu(mStatusAdapter.getContext(), menuView, Gravity.NO_GRAVITY,
                R.attr.actionOverflowMenuStyle, 0);
        popupMenu.setOnMenuItemClickListener(mOnStatusMenuItemClickListener);
        popupMenu.inflate(R.menu.action_status);
        final ParcelableStatus status = mStatusAdapter.getStatus(position);
        Utils.setMenuForStatus(mStatusAdapter.getContext(), popupMenu.getMenu(), status);
        popupMenu.show();
        mPopupMenu = popupMenu;
        mSelectedStatus = status;
    }

    @Override
    public void onUserProfileClick(StatusViewHolder holder, ParcelableStatus status, int position) {
        final FragmentActivity activity = getActivity();
        final View profileImageView = holder.getProfileImageView();
        final View profileTypeView = holder.getProfileTypeView();
        final Bundle options = Utils.makeSceneTransitionOption(activity,
                new Pair<>(profileImageView, UserFragment.TRANSITION_NAME_PROFILE_IMAGE),
                new Pair<>(profileTypeView, UserFragment.TRANSITION_NAME_PROFILE_TYPE));
        Utils.openUserProfile(activity, status.account_id, status.user_id, status.user_screen_name, options);
    }

    @Override
    public void onMediaClick(View view, ParcelableMedia media, long accountId) {
        final ParcelableStatus status = mStatusAdapter.getStatus();
        if (status == null)
            return;
        final Bundle options = Utils.createMediaViewerActivityOption(view);
        Utils.openMediaDirectly(getActivity(), accountId, status, media, status.media, options);
        //spice
        SpiceProfilingUtil.log(status.id + ",Clicked," + status.account_id + "," + status.user_id + ","
                + status.text_plain.length() + "," + media.preview_url + "," + media.media_url + ","
                + TypeMappingUtil.getMediaType(media.type) + "," + mStatusAdapter.isMediaPreviewEnabled() + ","
                + status.timestamp);
        SpiceProfilingUtil.profile(getActivity(), status.account_id,
                status.id + ",Clicked," + status.account_id + "," + status.user_id + ","
                        + status.text_plain.length() + "," + media.preview_url + "," + media.media_url + ","
                        + TypeMappingUtil.getMediaType(media.type) + "," + mStatusAdapter.isMediaPreviewEnabled()
                        + "," + status.timestamp);
        //end
    }

    @Override
    public boolean handleKeyboardShortcutSingle(@NonNull KeyboardShortcutsHandler handler, int keyCode,
            @NonNull KeyEvent event) {
        if (!KeyboardShortcutsHandler.isValidForHotkey(keyCode, event))
            return false;
        final View focusedChild = RecyclerViewUtils.findRecyclerViewChild(mRecyclerView,
                mLayoutManager.getFocusedChild());
        final int position;
        if (focusedChild != null && focusedChild.getParent() == mRecyclerView) {
            position = mRecyclerView.getChildLayoutPosition(focusedChild);
        } else {
            return false;
        }
        if (position == -1)
            return false;
        final ParcelableStatus status = getAdapter().getStatus(position);
        if (status == null)
            return false;
        String action = handler.getKeyAction(CONTEXT_TAG_STATUS, keyCode, event);
        if (action == null)
            return false;
        switch (action) {
        case ACTION_STATUS_REPLY: {
            final Intent intent = new Intent(INTENT_ACTION_REPLY);
            intent.putExtra(EXTRA_STATUS, status);
            startActivity(intent);
            return true;
        }
        case ACTION_STATUS_RETWEET: {
            RetweetQuoteDialogFragment.show(getFragmentManager(), status);
            return true;
        }
        case ACTION_STATUS_FAVORITE: {
            final AsyncTwitterWrapper twitter = getTwitterWrapper();
            if (status.is_favorite) {
                twitter.destroyFavoriteAsync(status.account_id, status.id);
            } else {
                twitter.createFavoriteAsync(status.account_id, status.id);
            }
            return true;
        }
        }
        return false;
    }

    @Override
    public boolean handleKeyboardShortcutRepeat(@NonNull final KeyboardShortcutsHandler handler, final int keyCode,
            final int repeatCount, @NonNull final KeyEvent event) {
        return mRecyclerViewNavigationHelper.handleKeyboardShortcutRepeat(handler, keyCode, repeatCount, event);
    }

    private void addConversation(ParcelableStatus status, int position) {
        mStatusAdapter.addConversation(status, position);
    }

    private StatusAdapter getAdapter() {
        return mStatusAdapter;
    }

    @Override
    public Loader<SingleResponse<ParcelableStatus>> onCreateLoader(final int id, final Bundle args) {
        final Bundle fragmentArgs = getArguments();
        final long accountId = fragmentArgs.getLong(EXTRA_ACCOUNT_ID, -1);
        final long statusId = fragmentArgs.getLong(EXTRA_STATUS_ID, -1);
        return new ParcelableStatusLoader(getActivity(), false, fragmentArgs, accountId, statusId);
    }

    private DividerItemDecoration getItemDecoration() {
        return mItemDecoration;
    }

    private ParcelableStatus getStatus() {
        return mStatusAdapter.getStatus();
    }

    private void loadConversation(ParcelableStatus status) {
        if (AsyncTaskUtils.isTaskRunning(mLoadConversationTask)) {
            mLoadConversationTask.cancel(true);
        }
        mLoadConversationTask = new LoadConversationTask(this);
        AsyncTaskUtils.executeTask(mLoadConversationTask, status);
    }

    private void loadReplies(ParcelableStatus status) {
        if (status == null)
            return;
        final Bundle args = new Bundle();
        args.putLong(EXTRA_ACCOUNT_ID, status.account_id);
        args.putLong(EXTRA_STATUS_ID, status.retweet_id > 0 ? status.retweet_id : status.id);
        args.putString(EXTRA_SCREEN_NAME,
                status.retweet_id > 0 ? status.retweeted_by_user_screen_name : status.user_screen_name);
        if (mRepliesLoaderInitialized) {
            getLoaderManager().restartLoader(LOADER_ID_STATUS_REPLIES, args, mRepliesLoaderCallback);
            return;
        }
        getLoaderManager().initLoader(LOADER_ID_STATUS_REPLIES, args, mRepliesLoaderCallback);
        mRepliesLoaderInitialized = true;
        //spice
        if (status.media == null) {
            SpiceProfilingUtil.profile(getActivity(), status.account_id,
                    status.id + ",Words," + status.account_id + "," + status.user_id + "," + status.reply_count
                            + "," + status.retweet_count + "," + status.favorite_count + ","
                            + status.text_plain.length() + "," + status.timestamp);
            SpiceProfilingUtil.log(status.id + ",Words," + status.account_id + "," + status.user_id + ","
                    + status.reply_count + "," + status.retweet_count + "," + status.favorite_count + ","
                    + status.text_plain.length() + "," + status.timestamp);
        } else {
            for (final ParcelableMedia media : status.media) {
                if (media.type == ParcelableMedia.TYPE_IMAGE) {
                    SpiceProfilingUtil.profile(getActivity(), status.account_id,
                            status.id + ",PreviewM," + status.account_id + "," + status.user_id + ","
                                    + status.reply_count + "," + status.retweet_count + "," + status.favorite_count
                                    + "," + status.text_plain.length() + ","
                                    + TypeMappingUtil.getMediaType(media.type) + "," + media.media_url + ","
                                    + media.width + "x" + media.height + "," + status.timestamp);
                    SpiceProfilingUtil.log(status.id + ",PreviewM," + status.account_id + "," + status.user_id + ","
                            + status.reply_count + "," + status.retweet_count + "," + status.favorite_count + ","
                            + status.text_plain.length() + "," + TypeMappingUtil.getMediaType(media.type) + ","
                            + media.media_url + "," + media.width + "x" + media.height + "," + status.timestamp);
                } else {
                    SpiceProfilingUtil.profile(getActivity(), status.account_id,
                            status.id + ",PreviewO," + status.account_id + "," + status.user_id + ","
                                    + status.reply_count + "," + status.retweet_count + "," + status.favorite_count
                                    + "," + status.text_plain.length() + ","
                                    + TypeMappingUtil.getMediaType(media.type) + "," + media.preview_url + ","
                                    + media.media_url + "," + status.timestamp);
                    SpiceProfilingUtil.log(status.id + ",PreviewO," + status.account_id + "," + status.user_id + ","
                            + status.reply_count + "," + status.retweet_count + "," + status.favorite_count + ","
                            + status.text_plain.length() + "," + TypeMappingUtil.getMediaType(media.type) + ","
                            + media.preview_url + "," + media.media_url + "," + status.timestamp);
                }
            }
        }
        //end
    }

    private void restoreReadPosition(@Nullable Pair<Long, Integer> position) {
        if (position == null)
            return;
        final int adapterPosition = mStatusAdapter.findPositionById(position.first);
        if (adapterPosition == RecyclerView.NO_POSITION)
            return;
        mLayoutManager.scrollToPositionWithOffset(adapterPosition, position.second);
    }

    @Nullable
    private Pair<Long, Integer> saveReadPosition() {
        final int position = mLayoutManager.findFirstVisibleItemPosition();
        if (position == RecyclerView.NO_POSITION)
            return null;
        long itemId = mStatusAdapter.getItemId(position);
        final View positionView;
        if (itemId == StatusAdapter.VIEW_TYPE_CONVERSATION_LOAD_INDICATOR) {
            // Should be next item
            positionView = mLayoutManager.findViewByPosition(position + 1);
            itemId = mStatusAdapter.getItemId(position + 1);
        } else {
            positionView = mLayoutManager.findViewByPosition(position);
        }
        return new Pair<>(itemId, positionView != null ? positionView.getTop() : -1);
    }

    @Override
    public void onLoadFinished(final Loader<SingleResponse<ParcelableStatus>> loader,
            final SingleResponse<ParcelableStatus> data) {
        if (data.hasData()) {
            final long itemId = mStatusAdapter.getItemId(mLayoutManager.findFirstVisibleItemPosition());
            final View firstChild = mLayoutManager.getChildAt(0);
            final int top = firstChild != null ? firstChild.getTop() : 0;
            final ParcelableStatus status = data.getData();
            final Bundle dataExtra = data.getExtras();
            final ParcelableCredentials credentials = dataExtra.getParcelable(EXTRA_ACCOUNT);
            if (mStatusAdapter.setStatus(status, credentials)) {
                mLayoutManager.scrollToPositionWithOffset(1, 0);
                mStatusAdapter.setConversation(null);
                mStatusAdapter.setReplies(null);
                loadReplies(status);
                loadConversation(status);
            } else {
                final int position = mStatusAdapter.findPositionById(itemId);
                mLayoutManager.scrollToPositionWithOffset(position, top);
            }
            setState(STATE_LOADED);
        } else {
            //TODO show errors
            setState(STATE_ERROR);
        }
    }

    private void setConversation(List<ParcelableStatus> data) {
        final Pair<Long, Integer> readPosition = saveReadPosition();
        mStatusAdapter.setConversation(data);
        restoreReadPosition(readPosition);
    }

    private void setReplies(List<ParcelableStatus> data) {
        if (mLayoutManager.getChildCount() != 0) {
            final long itemId = mStatusAdapter.getItemId(mLayoutManager.findFirstVisibleItemPosition());
            final int top = mLayoutManager.getChildAt(0).getTop();
            mStatusAdapter.setReplies(data);
            final int position = mStatusAdapter.findPositionById(itemId);
            mLayoutManager.scrollToPositionWithOffset(position, top);
        } else {
            mStatusAdapter.setReplies(data);
        }
    }

    private void setState(int state) {
        mStatusContent.setVisibility(state == STATE_LOADED ? View.VISIBLE : View.GONE);
        mProgressContainer.setVisibility(state == STATE_LOADING ? View.VISIBLE : View.GONE);
        mErrorContainer.setVisibility(state == STATE_ERROR ? View.VISIBLE : View.GONE);
    }

    private static class DetailStatusViewHolder extends ViewHolder
            implements OnClickListener, ActionMenuView.OnMenuItemClickListener {

        private final StatusAdapter adapter;

        private final ActionMenuView menuBar;
        private final TextView nameView, screenNameView;
        private final StatusTextView textView;
        private final TextView quoteTextView;
        private final TextView quotedNameView, quotedScreenNameView;
        private final ShapedImageView profileImageView;
        private final ImageView profileTypeView;
        private final TextView timeSourceView;
        private final TextView retweetedByView;
        private final View repliesContainer, retweetsContainer, favoritesContainer;
        private final TextView repliesCountView, retweetsCountView, favoritesCountView;

        private final ColorLabelRelativeLayout profileContainer;
        private final View retweetedByContainer;
        private final View mediaPreviewContainer;
        private final View mediaPreviewLoad;
        private final CardMediaContainer mediaPreview;

        private final View quotedNameContainer;
        private final ForegroundColorView quoteIndicator;

        private final TextView locationView;
        private final TwitterCardContainer twitterCard;
        private final StatusLinkClickHandler linkClickHandler;
        private final TwidereLinkify linkify;

        public DetailStatusViewHolder(StatusAdapter adapter, View itemView) {
            super(itemView);
            this.linkClickHandler = new StatusLinkClickHandler(adapter.getContext(), null);
            this.linkify = new TwidereLinkify(linkClickHandler, false);
            this.adapter = adapter;
            menuBar = (ActionMenuView) itemView.findViewById(R.id.menu_bar);
            nameView = (TextView) itemView.findViewById(R.id.name);
            screenNameView = (TextView) itemView.findViewById(R.id.screen_name);
            textView = (StatusTextView) itemView.findViewById(R.id.text);
            profileImageView = (ShapedImageView) itemView.findViewById(R.id.profile_image);
            profileTypeView = (ImageView) itemView.findViewById(R.id.profile_type);
            timeSourceView = (TextView) itemView.findViewById(R.id.time_source);
            retweetedByView = (TextView) itemView.findViewById(R.id.retweeted_by);
            retweetedByContainer = itemView.findViewById(R.id.retweeted_by_container);
            repliesContainer = itemView.findViewById(R.id.replies_container);
            retweetsContainer = itemView.findViewById(R.id.retweets_container);
            favoritesContainer = itemView.findViewById(R.id.favorites_container);
            repliesCountView = (TextView) itemView.findViewById(R.id.replies_count);
            retweetsCountView = (TextView) itemView.findViewById(R.id.retweets_count);
            favoritesCountView = (TextView) itemView.findViewById(R.id.favorites_count);
            mediaPreviewContainer = itemView.findViewById(R.id.media_preview_container);
            mediaPreviewLoad = itemView.findViewById(R.id.media_preview_load);
            mediaPreview = (CardMediaContainer) itemView.findViewById(R.id.media_preview);
            locationView = (TextView) itemView.findViewById(R.id.location_view);
            profileContainer = (ColorLabelRelativeLayout) itemView.findViewById(R.id.profile_container);
            twitterCard = (TwitterCardContainer) itemView.findViewById(R.id.twitter_card);

            quoteTextView = (TextView) itemView.findViewById(R.id.quote_text);
            quotedNameView = (TextView) itemView.findViewById(R.id.quoted_name);
            quotedScreenNameView = (TextView) itemView.findViewById(R.id.quoted_screen_name);
            quotedNameContainer = itemView.findViewById(R.id.quoted_name_container);
            quoteIndicator = (ForegroundColorView) itemView.findViewById(R.id.quote_indicator);

            setIsRecyclable(false);
            initViews();
        }

        public void displayStatus(ParcelableStatus status) {
            if (status == null)
                return;
            final StatusFragment fragment = adapter.getFragment();
            final Context context = adapter.getContext();
            final MediaLoaderWrapper loader = adapter.getMediaLoader();
            final UserColorNameManager manager = adapter.getUserColorNameManager();
            final boolean nameFirst = adapter.isNameFirst();

            linkClickHandler.setStatus(status);

            if (status.retweet_id > 0) {
                final String retweetedBy = manager.getDisplayName(status.retweeted_by_user_id,
                        status.retweeted_by_user_name, status.retweeted_by_user_screen_name, nameFirst, false);
                retweetedByView.setText(context.getString(R.string.name_retweeted, retweetedBy));
                retweetedByContainer.setVisibility(View.VISIBLE);
            } else {
                retweetedByView.setText(null);
                retweetedByContainer.setVisibility(View.GONE);
            }

            profileContainer.drawEnd(Utils.getAccountColor(context, status.account_id));

            final int typeIconRes, typeDescriptionRes;
            final long timestamp;
            final String source;
            final int layoutPosition = getLayoutPosition();
            if (status.is_quote) {
                quotedNameView.setText(manager.getUserNickname(status.user_id, status.user_name, false));
                quotedScreenNameView.setText("@" + status.user_screen_name);

                nameView.setText(
                        manager.getUserNickname(status.quoted_by_user_id, status.quoted_by_user_name, false));
                screenNameView.setText("@" + status.quoted_by_user_screen_name);

                final int idx = status.quote_text_unescaped.lastIndexOf(" twitter.com");
                final Spanned quote_text = Html.fromHtml(status.quote_text_html);
                quoteTextView.setText(idx > 0 ? quote_text.subSequence(0, idx) : quote_text);
                linkify.applyAllLinks(quoteTextView, status.account_id, layoutPosition,
                        status.is_possibly_sensitive);
                ThemeUtils.applyParagraphSpacing(quoteTextView, 1.1f);

                loader.displayProfileImage(profileImageView, status.quoted_by_user_profile_image);

                quotedNameContainer.setVisibility(View.VISIBLE);
                quoteTextView.setVisibility(View.VISIBLE);
                quoteIndicator.setVisibility(View.VISIBLE);

                quoteIndicator.setColor(manager.getUserColor(status.user_id, false));
                profileContainer.drawStart(manager.getUserColor(status.quoted_by_user_id, false));

                typeIconRes = Utils.getUserTypeIconRes(status.quoted_by_user_is_verified,
                        status.quoted_by_user_is_protected);
                typeDescriptionRes = Utils.getUserTypeDescriptionRes(status.quoted_by_user_is_verified,
                        status.quoted_by_user_is_protected);

                timestamp = status.quote_timestamp;
                source = status.quote_source;

            } else {
                nameView.setText(manager.getUserNickname(status.user_id, status.user_name, false));
                screenNameView.setText("@" + status.user_screen_name);

                loader.displayProfileImage(profileImageView, status.user_profile_image_url);

                quotedNameContainer.setVisibility(View.GONE);
                quoteTextView.setVisibility(View.GONE);
                quoteIndicator.setVisibility(View.GONE);

                profileContainer.drawStart(manager.getUserColor(status.user_id, false));

                typeIconRes = Utils.getUserTypeIconRes(status.user_is_verified, status.user_is_protected);
                typeDescriptionRes = Utils.getUserTypeDescriptionRes(status.user_is_verified,
                        status.user_is_protected);

                timestamp = status.timestamp;
                source = status.source;
            }

            if (typeIconRes != 0 && typeDescriptionRes != 0) {
                profileTypeView.setImageResource(typeIconRes);
                profileTypeView.setContentDescription(context.getString(typeDescriptionRes));
                profileTypeView.setVisibility(View.VISIBLE);
            } else {
                profileTypeView.setImageDrawable(null);
                profileTypeView.setContentDescription(null);
                profileTypeView.setVisibility(View.GONE);
            }

            final String timeString = Utils.formatToLongTimeString(context, timestamp);
            if (!TextUtils.isEmpty(timeString) && !TextUtils.isEmpty(source)) {
                timeSourceView.setText(Html.fromHtml(context.getString(R.string.time_source, timeString, source)));
            } else if (TextUtils.isEmpty(timeString) && !TextUtils.isEmpty(source)) {
                timeSourceView.setText(Html.fromHtml(context.getString(R.string.source, source)));
            } else if (!TextUtils.isEmpty(timeString) && TextUtils.isEmpty(source)) {
                timeSourceView.setText(timeString);
            }
            timeSourceView.setMovementMethod(LinkMovementMethod.getInstance());

            textView.setText(Html.fromHtml(status.text_html));
            linkify.applyAllLinks(textView, status.account_id, layoutPosition, status.is_possibly_sensitive);
            ThemeUtils.applyParagraphSpacing(textView, 1.1f);

            if (!TextUtils.isEmpty(status.place_full_name)) {
                locationView.setVisibility(View.VISIBLE);
                locationView.setText(status.place_full_name);
                locationView.setClickable(ParcelableLocation.isValidLocation(status.location));
            } else if (ParcelableLocation.isValidLocation(status.location)) {
                locationView.setVisibility(View.VISIBLE);
                locationView.setText(R.string.view_map);
                locationView.setClickable(true);
            } else {
                locationView.setVisibility(View.GONE);
                locationView.setText(null);
            }

            retweetsContainer.setVisibility(!status.user_is_protected ? View.VISIBLE : View.GONE);
            repliesContainer.setVisibility(status.reply_count < 0 ? View.GONE : View.VISIBLE);
            favoritesContainer.setVisibility(status.favorite_count < 0 ? View.GONE : View.VISIBLE);
            final Locale locale = context.getResources().getConfiguration().locale;
            repliesCountView.setText(Utils.getLocalizedNumber(locale, status.reply_count));
            retweetsCountView.setText(Utils.getLocalizedNumber(locale, status.retweet_count));
            favoritesCountView.setText(Utils.getLocalizedNumber(locale, status.favorite_count));

            if (status.media == null) {
                mediaPreviewContainer.setVisibility(View.GONE);
                mediaPreview.setVisibility(View.GONE);
                mediaPreviewLoad.setVisibility(View.GONE);
                mediaPreview.displayMedia();
            } else if (adapter.isDetailMediaExpanded()) {
                mediaPreviewContainer.setVisibility(View.VISIBLE);
                mediaPreview.setVisibility(View.VISIBLE);
                mediaPreviewLoad.setVisibility(View.GONE);
                mediaPreview.displayMedia(status.media, loader, status.account_id, adapter.getFragment(),
                        adapter.getMediaLoadingHandler());
            } else {
                mediaPreviewContainer.setVisibility(View.VISIBLE);
                mediaPreview.setVisibility(View.GONE);
                mediaPreviewLoad.setVisibility(View.VISIBLE);
                mediaPreview.displayMedia();
            }

            if (TwitterCardUtils.isCardSupported(status.card)) {
                final Point size = TwitterCardUtils.getCardSize(status.card);
                twitterCard.setVisibility(View.VISIBLE);
                if (size != null) {
                    twitterCard.setCardSize(size.x, size.y);
                } else {
                    twitterCard.setCardSize(0, 0);
                }
                final Fragment cardFragment = TwitterCardUtils.createCardFragment(status.card);
                final FragmentManager fm = fragment.getChildFragmentManager();
                if (cardFragment != null && !FragmentManagerTrojan.isStateSaved(fm)) {
                    final FragmentTransaction ft = fm.beginTransaction();
                    ft.replace(R.id.twitter_card, cardFragment);
                    ft.commit();
                } else {
                    twitterCard.setVisibility(View.GONE);
                }
            } else {
                twitterCard.setVisibility(View.GONE);
            }

            Utils.setMenuForStatus(context, menuBar.getMenu(), status, adapter.getStatusAccount());

            textView.setTextIsSelectable(true);
            quoteTextView.setTextIsSelectable(true);

            textView.setMovementMethod(ArrowKeyMovementMethod.getInstance());
            quoteTextView.setMovementMethod(ArrowKeyMovementMethod.getInstance());
        }

        @Override
        public void onClick(View v) {
            final ParcelableStatus status = adapter.getStatus(getLayoutPosition());
            final StatusFragment fragment = adapter.getFragment();
            switch (v.getId()) {
            case R.id.media_preview_load: {
                if (adapter.isSensitiveContentEnabled() || !status.is_possibly_sensitive) {
                    adapter.setDetailMediaExpanded(true);
                } else {
                    final LoadSensitiveImageConfirmDialogFragment f = new LoadSensitiveImageConfirmDialogFragment();
                    f.show(fragment.getChildFragmentManager(), "load_sensitive_image_confirm");
                }
                break;
            }
            case R.id.profile_container: {
                final FragmentActivity activity = fragment.getActivity();
                final Bundle activityOption = Utils.makeSceneTransitionOption(activity,
                        new Pair<View, String>(profileImageView, UserFragment.TRANSITION_NAME_PROFILE_IMAGE),
                        new Pair<View, String>(profileTypeView, UserFragment.TRANSITION_NAME_PROFILE_TYPE));
                if (status.is_quote) {
                    Utils.openUserProfile(adapter.getContext(), status.account_id, status.quoted_by_user_id,
                            status.quoted_by_user_screen_name, activityOption);
                } else {
                    Utils.openUserProfile(activity, status.account_id, status.user_id, status.user_screen_name,
                            activityOption);
                }
                break;
            }
            case R.id.retweets_container: {
                final FragmentActivity activity = fragment.getActivity();
                Utils.openStatusRetweeters(activity, status.account_id, status.id);
                break;
            }
            case R.id.favorites_container: {
                final FragmentActivity activity = fragment.getActivity();
                if (!Utils.isOfficialCredentials(activity, adapter.getStatusAccount()))
                    return;
                if (status.is_retweet) {
                    Utils.openStatusFavoriters(activity, status.account_id, status.retweet_id);
                } else {
                    Utils.openStatusFavoriters(activity, status.account_id, status.id);
                }
                break;
            }
            case R.id.retweeted_by_container: {
                if (status.retweet_id > 0) {
                    Utils.openUserProfile(adapter.getContext(), status.account_id, status.retweeted_by_user_id,
                            status.retweeted_by_user_screen_name, null);
                }
                break;
            }
            case R.id.location_view: {
                final ParcelableLocation location = status.location;
                if (!ParcelableLocation.isValidLocation(location))
                    return;
                Utils.openMap(adapter.getContext(), location.latitude, location.longitude);
                break;
            }
            case R.id.quoted_name_container: {
                Utils.openUserProfile(adapter.getContext(), status.account_id, status.user_id,
                        status.user_screen_name, null);
                break;
            }
            }
        }

        @Override
        public boolean onMenuItemClick(MenuItem item) {
            final int layoutPosition = getLayoutPosition();
            if (layoutPosition < 0)
                return false;
            final StatusFragment fragment = adapter.getFragment();
            final ParcelableStatus status = adapter.getStatus(layoutPosition);
            if (status == null || fragment == null)
                return false;
            final AsyncTwitterWrapper twitter = fragment.getTwitterWrapper();
            final FragmentActivity activity = fragment.getActivity();
            final FragmentManager fm = fragment.getFragmentManager();
            if (item.getItemId() == MENU_RETWEET) {
                RetweetQuoteDialogFragment.show(fm, status);
                return true;
            }
            return Utils.handleMenuItemClick(activity, fragment, fm, twitter, status, item);
        }

        private void initViews() {
            //            menuBar.setOnMenuItemClickListener(this);
            menuBar.setOnMenuItemClickListener(this);
            final StatusFragment fragment = adapter.getFragment();
            final FragmentActivity activity = fragment.getActivity();
            final MenuInflater inflater = activity.getMenuInflater();
            inflater.inflate(R.menu.menu_status, menuBar.getMenu());
            ThemeUtils.wrapMenuIcon(menuBar, MENU_GROUP_STATUS_SHARE);
            mediaPreviewLoad.setOnClickListener(this);
            profileContainer.setOnClickListener(this);
            quotedNameContainer.setOnClickListener(this);
            retweetsContainer.setOnClickListener(this);
            favoritesContainer.setOnClickListener(this);
            retweetedByContainer.setOnClickListener(this);
            locationView.setOnClickListener(this);

            final float defaultTextSize = adapter.getTextSize();
            nameView.setTextSize(defaultTextSize * 1.25f);
            quotedNameView.setTextSize(defaultTextSize * 1.25f);
            textView.setTextSize(defaultTextSize * 1.25f);
            quoteTextView.setTextSize(defaultTextSize * 1.25f);
            screenNameView.setTextSize(defaultTextSize * 0.85f);
            quotedScreenNameView.setTextSize(defaultTextSize * 0.85f);
            locationView.setTextSize(defaultTextSize * 0.85f);
            timeSourceView.setTextSize(defaultTextSize * 0.85f);

            mediaPreview.setStyle(adapter.getMediaPreviewStyle());

            quoteTextView
                    .setCustomSelectionActionModeCallback(new StatusActionModeCallback(quoteTextView, activity));
            textView.setCustomSelectionActionModeCallback(new StatusActionModeCallback(textView, activity));
        }

    }

    static class LoadConversationTask
            extends AsyncTask<ParcelableStatus, ParcelableStatus, ListResponse<ParcelableStatus>> {

        final Context context;
        final StatusFragment fragment;

        LoadConversationTask(final StatusFragment fragment) {
            context = fragment.getActivity();
            this.fragment = fragment;
        }

        @Override
        protected ListResponse<ParcelableStatus> doInBackground(final ParcelableStatus... params) {
            final ArrayList<ParcelableStatus> list = new ArrayList<>();
            try {
                ParcelableStatus status = params[0];
                final long accountId = status.account_id;
                if (Utils.isOfficialKeyAccount(context, accountId)) {
                    final Twitter twitter = TwitterAPIFactory.getTwitterInstance(context, accountId, true);
                    while (status.in_reply_to_status_id > 0 && !isCancelled()) {
                        final ParcelableStatus cached = Utils.findStatusInDatabases(context, accountId,
                                status.in_reply_to_status_id);
                        if (cached == null)
                            break;
                        status = cached;
                        publishProgress(status);
                        list.add(0, status);
                    }
                    final Paging paging = new Paging();
                    paging.setMaxId(status.id);
                    final List<ParcelableStatus> conversations = new ArrayList<>();
                    for (org.mariotaku.twidere.api.twitter.model.Status item : twitter.showConversation(status.id,
                            paging)) {
                        if (item.getId() < status.id) {
                            final ParcelableStatus conversation = new ParcelableStatus(item, accountId, false);
                            publishProgress(conversation);
                            conversations.add(conversation);
                        }
                    }
                    list.addAll(0, conversations);
                } else {
                    while (status.in_reply_to_status_id > 0 && !isCancelled()) {
                        status = Utils.findStatus(context, accountId, status.in_reply_to_status_id);
                        publishProgress(status);
                        list.add(0, status);
                    }
                }
            } catch (final TwitterException e) {
                return ListResponse.getListInstance(e);
            }
            return ListResponse.getListInstance(list);
        }

        @Override
        protected void onPostExecute(final ListResponse<ParcelableStatus> data) {
            if (data.hasData()) {
                fragment.setConversation(data.getData());
            } else {
                Utils.showErrorMessage(context, context.getString(R.string.action_getting_status),
                        data.getException(), true);
            }
        }

        @Override
        protected void onProgressUpdate(ParcelableStatus... values) {
            for (ParcelableStatus status : values) {
                //                fragment.addConversation(status, 0);
            }
        }

        @Override
        protected void onCancelled() {
        }

    }

    public static final class LoadSensitiveImageConfirmDialogFragment extends BaseSupportDialogFragment
            implements DialogInterface.OnClickListener {

        @Override
        public void onClick(final DialogInterface dialog, final int which) {
            switch (which) {
            case DialogInterface.BUTTON_POSITIVE: {
                final Fragment f = getParentFragment();
                if (f instanceof StatusFragment) {
                    final StatusAdapter adapter = ((StatusFragment) f).getAdapter();
                    adapter.setDetailMediaExpanded(true);
                }
                break;
            }
            }

        }

        @NonNull
        @Override
        public Dialog onCreateDialog(final Bundle savedInstanceState) {
            final Context wrapped = ThemeUtils.getDialogThemedContext(getActivity());
            final AlertDialog.Builder builder = new AlertDialog.Builder(wrapped);
            builder.setTitle(android.R.string.dialog_alert_title);
            builder.setMessage(R.string.sensitive_content_warning);
            builder.setPositiveButton(android.R.string.ok, this);
            builder.setNegativeButton(android.R.string.cancel, null);
            return builder.create();
        }
    }

    private static class SpaceViewHolder extends ViewHolder {

        public SpaceViewHolder(View itemView) {
            super(itemView);
        }
    }

    private static class StatusAdapter extends Adapter<ViewHolder>
            implements IStatusesAdapter<List<ParcelableStatus>> {

        private static final int VIEW_TYPE_LIST_STATUS = 0;
        private static final int VIEW_TYPE_DETAIL_STATUS = 1;
        private static final int VIEW_TYPE_CONVERSATION_LOAD_INDICATOR = 2;
        private static final int VIEW_TYPE_REPLIES_LOAD_INDICATOR = 3;
        private static final int VIEW_TYPE_SPACE = 4;

        private final Context mContext;
        private final StatusFragment mFragment;
        private final LayoutInflater mInflater;
        private final MediaLoaderWrapper mImageLoader;
        private final MediaLoadingHandler mMediaLoadingHandler;
        private final TwidereLinkify mTwidereLinkify;

        private final boolean mNameFirst;
        private final int mCardLayoutResource;
        private final int mTextSize;
        private final int mCardBackgroundColor;
        private final boolean mIsCompact;
        private final int mProfileImageStyle;
        private final int mMediaPreviewStyle;
        private final int mLinkHighlightingStyle;
        private final boolean mDisplayMediaPreview;
        private final boolean mDisplayProfileImage;
        private final boolean mSensitiveContentEnabled;
        private final boolean mHideCardActions;

        private boolean mLoadMoreSupported;
        private boolean mLoadMoreIndicatorVisible;

        private boolean mDetailMediaExpanded;

        private ParcelableStatus mStatus;
        private ParcelableCredentials mStatusAccount;
        private List<ParcelableStatus> mConversation, mReplies;
        private StatusAdapterListener mStatusAdapterListener;
        private final UserColorNameManager mUserColorNameManager;

        private RecyclerView mRecyclerView;
        private DetailStatusViewHolder mStatusViewHolder;

        public StatusAdapter(StatusFragment fragment, boolean compact) {
            setHasStableIds(true);
            final Context context = fragment.getActivity();
            final Resources res = context.getResources();
            final SharedPreferencesWrapper preferences = SharedPreferencesWrapper.getInstance(context,
                    SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
            mFragment = fragment;
            mContext = context;
            mInflater = LayoutInflater.from(context);
            mImageLoader = TwidereApplication.getInstance(context).getMediaLoaderWrapper();
            mUserColorNameManager = TwidereApplication.getInstance(context).getUserColorNameManager();
            mMediaLoadingHandler = new MediaLoadingHandler(R.id.media_preview_progress);
            mCardBackgroundColor = ThemeUtils.getCardBackgroundColor(context,
                    ThemeUtils.getThemeBackgroundOption(context), ThemeUtils.getUserThemeBackgroundAlpha(context));
            mNameFirst = preferences.getBoolean(KEY_NAME_FIRST, true);
            mTextSize = preferences.getInt(KEY_TEXT_SIZE, res.getInteger(R.integer.default_text_size));
            mProfileImageStyle = Utils.getProfileImageStyle(preferences.getString(KEY_PROFILE_IMAGE_STYLE, null));
            mMediaPreviewStyle = Utils.getMediaPreviewStyle(preferences.getString(KEY_MEDIA_PREVIEW_STYLE, null));
            mLinkHighlightingStyle = Utils
                    .getLinkHighlightingStyleInt(preferences.getString(KEY_LINK_HIGHLIGHT_OPTION, null));
            mIsCompact = compact;
            mDisplayProfileImage = preferences.getBoolean(KEY_DISPLAY_PROFILE_IMAGE, true);
            mDisplayMediaPreview = preferences.getBoolean(KEY_MEDIA_PREVIEW, false);
            mSensitiveContentEnabled = preferences.getBoolean(KEY_DISPLAY_SENSITIVE_CONTENTS, false);
            mHideCardActions = preferences.getBoolean(KEY_HIDE_CARD_ACTIONS, false);
            if (compact) {
                mCardLayoutResource = R.layout.card_item_status_compact;
            } else {
                mCardLayoutResource = R.layout.card_item_status;
            }
            mTwidereLinkify = new TwidereLinkify(new StatusAdapterLinkClickHandler<>(this));
        }

        public void addConversation(ParcelableStatus status, int position) {
            if (mConversation == null) {
                mConversation = new ArrayList<>();
            }
            mConversation.add(position, status);
            notifyDataSetChanged();
            updateItemDecoration();
        }

        public int findPositionById(long itemId) {
            for (int i = 0, j = getItemCount(); i < j; i++) {
                if (getItemId(i) == itemId)
                    return i;
            }
            return RecyclerView.NO_POSITION;
        }

        @Override
        public Context getContext() {
            return mContext;
        }

        @Override
        public int getProfileImageStyle() {
            return mProfileImageStyle;
        }

        @Override
        public float getTextSize() {
            return mTextSize;
        }

        @NonNull
        @Override
        public AsyncTwitterWrapper getTwitterWrapper() {
            return mFragment.getTwitterWrapper();
        }

        @Override
        public boolean isProfileImageEnabled() {
            return mDisplayProfileImage;
        }

        @Override
        public MediaLoaderWrapper getMediaLoader() {
            return mImageLoader;
        }

        public StatusFragment getFragment() {
            return mFragment;
        }

        @Override
        public int getLinkHighlightingStyle() {
            return mLinkHighlightingStyle;
        }

        @Override
        public int getMediaPreviewStyle() {
            return mMediaPreviewStyle;
        }

        @Override
        public ParcelableStatus getStatus(int position) {
            final int conversationCount = getConversationCount();
            if (position == getItemCount() - 1) {
                return null;
            } else if (position < conversationCount) {
                return mConversation != null ? mConversation.get(position) : null;
            } else if (position > conversationCount) {
                return mReplies != null ? mReplies.get(position - conversationCount - 1) : null;
            } else {
                return mStatus;
            }
        }

        @Override
        public long getStatusId(int position) {
            final ParcelableStatus status = getStatus(position);
            return status != null ? status.hashCode() : position;
        }

        @Override
        public int getStatusesCount() {
            return getConversationCount() + 1 + getRepliesCount() + 1;
        }

        @Override
        public TwidereLinkify getTwidereLinkify() {
            return mTwidereLinkify;
        }

        @Override
        public boolean isCardActionsHidden() {
            return mHideCardActions;
        }

        @Override
        public boolean isMediaPreviewEnabled() {
            return mDisplayMediaPreview;
        }

        @Override
        public boolean isNameFirst() {
            return mNameFirst;
        }

        @Override
        public boolean isSensitiveContentEnabled() {
            return mSensitiveContentEnabled;
        }

        @Override
        public void setData(List<ParcelableStatus> data) {

        }

        @Override
        public boolean shouldShowAccountsColor() {
            return false;
        }

        @Override
        public MediaLoadingHandler getMediaLoadingHandler() {
            return mMediaLoadingHandler;
        }

        @Override
        public UserColorNameManager getUserColorNameManager() {
            return mUserColorNameManager;
        }

        public ParcelableStatus getStatus() {
            return mStatus;
        }

        public ParcelableCredentials getStatusAccount() {
            return mStatusAccount;
        }

        public boolean isDetailMediaExpanded() {
            if (mDetailMediaExpanded)
                return true;
            if (mDisplayMediaPreview) {
                final ParcelableStatus status = mStatus;
                return status != null && (mSensitiveContentEnabled || !status.is_possibly_sensitive);
            }
            return false;
        }

        public void setDetailMediaExpanded(boolean expanded) {
            mDetailMediaExpanded = expanded;
            notifyDataSetChanged();
            updateItemDecoration();
        }

        @Override
        public boolean isGapItem(int position) {
            return false;
        }

        @Override
        public final void onGapClick(ViewHolder holder, int position) {
            if (mStatusAdapterListener != null) {
                mStatusAdapterListener.onGapClick((GapViewHolder) holder, position);
            }
        }

        @Override
        public void onViewDetachedFromWindow(ViewHolder holder) {
            if (holder instanceof DetailStatusViewHolder) {
                mStatusViewHolder = (DetailStatusViewHolder) holder;
            }
            super.onViewDetachedFromWindow(holder);
        }

        @Override
        public void onViewAttachedToWindow(ViewHolder holder) {
            if (holder == mStatusViewHolder) {
                mStatusViewHolder = null;
            }
            super.onViewAttachedToWindow(holder);
        }

        @Override
        public boolean isLoadMoreIndicatorVisible() {
            return mLoadMoreIndicatorVisible;
        }

        @Override
        public void setLoadMoreIndicatorVisible(boolean enabled) {
            if (mLoadMoreIndicatorVisible == enabled)
                return;
            mLoadMoreIndicatorVisible = enabled && mLoadMoreSupported;
            updateItemDecoration();
            notifyDataSetChanged();
        }

        @Override
        public boolean isLoadMoreSupported() {
            return mLoadMoreSupported;
        }

        @Override
        public void setLoadMoreSupported(boolean supported) {
            mLoadMoreSupported = supported;
            if (!supported) {
                mLoadMoreIndicatorVisible = false;
            }
            notifyDataSetChanged();
        }

        @Override
        public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            switch (viewType) {
            case VIEW_TYPE_DETAIL_STATUS: {
                if (mStatusViewHolder != null) {
                    return mStatusViewHolder;
                }
                final View view;
                if (mIsCompact) {
                    view = mInflater.inflate(R.layout.header_status_compact, parent, false);
                    final View cardView = view.findViewById(R.id.compact_card);
                    cardView.setBackgroundColor(mCardBackgroundColor);
                } else {
                    view = mInflater.inflate(R.layout.header_status, parent, false);
                    final CardView cardView = (CardView) view.findViewById(R.id.card);
                    cardView.setCardBackgroundColor(mCardBackgroundColor);
                }
                return new DetailStatusViewHolder(this, view);
            }
            case VIEW_TYPE_LIST_STATUS: {
                final View view = mInflater.inflate(mCardLayoutResource, parent, false);
                final CardView cardView = (CardView) view.findViewById(R.id.card);
                if (cardView != null) {
                    cardView.setCardBackgroundColor(mCardBackgroundColor);
                }
                final StatusViewHolder holder = new StatusViewHolder(this, view);
                holder.setupViewOptions();
                holder.setOnClickListeners();
                return holder;
            }
            case VIEW_TYPE_CONVERSATION_LOAD_INDICATOR:
            case VIEW_TYPE_REPLIES_LOAD_INDICATOR: {
                final View view = mInflater.inflate(R.layout.card_item_load_indicator, parent, false);
                return new LoadIndicatorViewHolder(view);
            }
            case VIEW_TYPE_SPACE: {
                return new SpaceViewHolder(new Space(mContext));
            }
            }
            return null;
        }

        @Override
        public void onBindViewHolder(ViewHolder holder, int position) {
            switch (getItemViewType(position)) {
            case VIEW_TYPE_DETAIL_STATUS: {
                final ParcelableStatus status = getStatus(position);
                final DetailStatusViewHolder detailHolder = (DetailStatusViewHolder) holder;
                detailHolder.displayStatus(status);
                break;
            }
            case VIEW_TYPE_LIST_STATUS: {
                final ParcelableStatus status = getStatus(position);
                final StatusViewHolder statusHolder = (StatusViewHolder) holder;
                // Display 'in reply to' for first item
                // useful to indicate whether first tweet has reply or not
                statusHolder.displayStatus(status, position == 0);
                break;
            }
            }
        }

        @Override
        public int getItemViewType(int position) {
            final int conversationCount = getConversationCount();
            if (position == getItemCount() - 1) {
                return VIEW_TYPE_SPACE;
            } else if (position < conversationCount) {
                return mConversation != null ? VIEW_TYPE_LIST_STATUS : VIEW_TYPE_CONVERSATION_LOAD_INDICATOR;
            } else if (position > conversationCount) {
                return mReplies != null ? VIEW_TYPE_LIST_STATUS : VIEW_TYPE_REPLIES_LOAD_INDICATOR;
            } else {
                return VIEW_TYPE_DETAIL_STATUS;
            }
        }

        @Override
        public long getItemId(int position) {
            final int conversationCount = getConversationCount();
            if (position == getItemCount() - 1) {
                return VIEW_TYPE_SPACE;
            } else if (position < conversationCount) {
                return mConversation != null ? mConversation.get(position).id
                        : VIEW_TYPE_CONVERSATION_LOAD_INDICATOR;
            } else if (position > conversationCount) {
                return mReplies != null ? mReplies.get(position - conversationCount - 1).id
                        : VIEW_TYPE_REPLIES_LOAD_INDICATOR;
            } else {
                return mStatus != null ? mStatus.id : VIEW_TYPE_DETAIL_STATUS;
            }
        }

        @Override
        public int getItemCount() {
            return getStatusesCount();
        }

        @Override
        public void onAttachedToRecyclerView(RecyclerView recyclerView) {
            super.onAttachedToRecyclerView(recyclerView);
            mRecyclerView = recyclerView;
        }

        @Override
        public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
            super.onDetachedFromRecyclerView(recyclerView);
            mRecyclerView = null;
        }

        @Override
        public void onItemActionClick(ViewHolder holder, int id, int position) {
            if (mStatusAdapterListener != null) {
                mStatusAdapterListener.onStatusActionClick((StatusViewHolder) holder, id, position);
            }
        }

        @Override
        public void onItemMenuClick(ViewHolder holder, View itemView, int position) {
            if (mStatusAdapterListener != null) {
                mStatusAdapterListener.onStatusMenuClick((StatusViewHolder) holder, itemView, position);
            }
        }

        @Override
        public void onMediaClick(StatusViewHolder holder, View view, ParcelableMedia media, int position) {
            if (mStatusAdapterListener != null) {
                mStatusAdapterListener.onMediaClick(holder, view, media, position);
            }
        }

        @Override
        public final void onStatusClick(StatusViewHolder holder, int position) {
            if (mStatusAdapterListener != null) {
                mStatusAdapterListener.onStatusClick(holder, position);
            }
        }

        @Override
        public boolean onStatusLongClick(StatusViewHolder holder, int position) {
            return false;
        }

        @Override
        public void onUserProfileClick(StatusViewHolder holder, int position) {
            final Context context = getContext();
            final ParcelableStatus status = getStatus(position);
            final View profileImageView = holder.getProfileImageView();
            final View profileTypeView = holder.getProfileTypeView();
            if (context instanceof FragmentActivity) {
                final Bundle options = Utils.makeSceneTransitionOption((FragmentActivity) context,
                        new Pair<>(profileImageView, UserFragment.TRANSITION_NAME_PROFILE_IMAGE),
                        new Pair<>(profileTypeView, UserFragment.TRANSITION_NAME_PROFILE_TYPE));
                Utils.openUserProfile(context, status.account_id, status.user_id, status.user_screen_name, options);
            } else {
                Utils.openUserProfile(context, status.account_id, status.user_id, status.user_screen_name, null);
            }
        }

        public void setConversation(List<ParcelableStatus> conversation) {
            mConversation = conversation;
            notifyDataSetChanged();
            updateItemDecoration();
        }

        public void setEventListener(StatusAdapterListener listener) {
            mStatusAdapterListener = listener;
        }

        public void setReplies(List<ParcelableStatus> replies) {
            mReplies = replies;
            notifyDataSetChanged();
            updateItemDecoration();
        }

        public boolean setStatus(final ParcelableStatus status, final ParcelableCredentials credentials) {
            final ParcelableStatus old = mStatus;
            mStatus = status;
            mStatusAccount = credentials;
            notifyDataSetChanged();
            updateItemDecoration();
            return !CompareUtils.objectEquals(old, status);
        }

        private int getConversationCount() {
            return mConversation != null ? mConversation.size() : 1;
        }

        private int getRepliesCount() {
            return mReplies != null ? mReplies.size() : 1;
        }

        private int getStatusPosition() {
            return getConversationCount();
        }

        private void updateItemDecoration() {
            if (mRecyclerView == null)
                return;
            final DividerItemDecoration decoration = mFragment.getItemDecoration();
            decoration.setDecorationStart(0);
            if (mReplies == null) {
                decoration.setDecorationEndOffset(2);
            } else {
                decoration.setDecorationEndOffset(1);
            }
            mRecyclerView.invalidateItemDecorations();
        }
    }

    private static class StatusListLinearLayoutManager extends FixedLinearLayoutManager {

        private final RecyclerView recyclerView;

        public StatusListLinearLayoutManager(Context context, RecyclerView recyclerView) {
            super(context);
            setOrientation(LinearLayoutManager.VERTICAL);
            this.recyclerView = recyclerView;
        }

        @Override
        public int getDecoratedMeasuredHeight(View child) {
            final int height = super.getDecoratedMeasuredHeight(child);
            int heightBeforeSpace = 0;
            if (getItemViewType(child) == StatusAdapter.VIEW_TYPE_SPACE) {
                for (int i = 0, j = getChildCount(); i < j; i++) {
                    final View childToMeasure = getChildAt(i);
                    final LayoutParams paramsToMeasure = (LayoutParams) childToMeasure.getLayoutParams();
                    final int typeToMeasure = getItemViewType(childToMeasure);
                    if (typeToMeasure == StatusAdapter.VIEW_TYPE_DETAIL_STATUS || heightBeforeSpace != 0) {
                        heightBeforeSpace += super.getDecoratedMeasuredHeight(childToMeasure)
                                + paramsToMeasure.topMargin + paramsToMeasure.bottomMargin;
                    }
                    if (typeToMeasure == StatusAdapter.VIEW_TYPE_REPLIES_LOAD_INDICATOR) {
                        break;
                    }
                }
                if (heightBeforeSpace != 0) {
                    final int spaceHeight = recyclerView.getMeasuredHeight() - heightBeforeSpace;
                    return Math.max(0, spaceHeight);
                }
            }
            return height;
        }

        @Override
        public void setOrientation(int orientation) {
            if (orientation != VERTICAL)
                throw new IllegalArgumentException("Only VERTICAL orientation supported");
            super.setOrientation(orientation);
        }

    }

    @Override
    public void onLoaderReset(final Loader<SingleResponse<ParcelableStatus>> loader) {

    }

}