Twidere-App-Android-Twitter.../twidere/src/main/java/org/mariotaku/twidere/fragment/support/StatusFragment.java

1722 lines
75 KiB
Java

/*
* 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.LayoutParams;
import android.support.v7.widget.RecyclerView.ViewHolder;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
import android.text.style.ClickableSpan;
import android.text.style.URLSpan;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
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.apache.commons.lang3.ArrayUtils;
import org.mariotaku.twidere.R;
import org.mariotaku.twidere.activity.support.ColorPickerDialogActivity;
import org.mariotaku.twidere.adapter.AbsStatusesAdapter.StatusAdapterListener;
import org.mariotaku.twidere.adapter.BaseRecyclerViewAdapter;
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.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.HtmlSpanBuilder;
import org.mariotaku.twidere.util.KeyboardShortcutsHandler;
import org.mariotaku.twidere.util.KeyboardShortcutsHandler.KeyboardShortcutCallback;
import org.mariotaku.twidere.util.LinkCreator;
import org.mariotaku.twidere.util.MathUtils;
import org.mariotaku.twidere.util.MediaLoaderWrapper;
import org.mariotaku.twidere.util.MediaLoadingHandler;
import org.mariotaku.twidere.util.MenuUtils;
import org.mariotaku.twidere.util.RecyclerViewNavigationHelper;
import org.mariotaku.twidere.util.RecyclerViewUtils;
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.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 org.mariotaku.twidere.view.holder.iface.IStatusViewHolder;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import edu.tsinghua.hotmobi.HotMobiLogger;
import edu.tsinghua.hotmobi.model.MediaEvent;
import edu.tsinghua.hotmobi.model.TimelineType;
import edu.tsinghua.hotmobi.model.TweetEvent;
/**
* 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 mNavigationHelper;
// Data fields
private boolean mRepliesLoaderInitialized;
private ParcelableStatus mSelectedStatus;
private TweetEvent mStatusEvent;
// Listeners
private LoaderCallbacks<List<ParcelableStatus>> mRepliesLoaderCallback = new LoaderCallbacks<List<ParcelableStatus>>() {
@Override
public Loader<List<ParcelableStatus>> onCreateLoader(int id, Bundle args) {
mStatusAdapter.setRepliesLoading(true);
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 boolean twitterOptimizedSearches = mPreferences.getBoolean(KEY_TWITTER_OPTIMIZED_SEARCHES);
final StatusRepliesLoader loader = new StatusRepliesLoader(getActivity(), accountId,
screenName, statusId, maxId, sinceId, null, null, 0, true, twitterOptimizedSearches);
loader.setComparator(ParcelableStatus.REVERSE_ID_COMPARATOR);
return loader;
}
@Override
public void onLoadFinished(Loader<List<ParcelableStatus>> loader, List<ParcelableStatus> data) {
mStatusAdapter.updateItemDecoration();
setReplies(data);
final ParcelableCredentials account = mStatusAdapter.getStatusAccount();
if (Utils.hasOfficialAPIAccess(loader.getContext(), mPreferences, account)) {
mStatusAdapter.setReplyError(null);
} else {
final SpannableStringBuilder error = SpannableStringBuilder.valueOf(HtmlSpanBuilder.fromHtml(getString(R.string.cant_load_all_replies_message)));
ClickableSpan dialogSpan = null;
for (URLSpan span : error.getSpans(0, error.length(), URLSpan.class)) {
if ("#dialog".equals(span.getURL())) {
dialogSpan = span;
break;
}
}
if (dialogSpan != null) {
final int spanStart = error.getSpanStart(dialogSpan), spanEnd = error.getSpanEnd(dialogSpan);
error.removeSpan(dialogSpan);
error.setSpan(new ClickableSpan() {
@Override
public void onClick(View widget) {
final FragmentActivity activity = getActivity();
if (activity == null) return;
SupportMessageDialogFragment.show(activity,
getString(R.string.cant_load_all_replies_explanation),
"cant_load_all_replies_explanation");
}
}, spanStart, spanEnd, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
}
mStatusAdapter.setReplyError(error);
}
mStatusAdapter.setRepliesLoading(false);
}
@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(), mTwitterWrapper, 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);
setHasOptionsMenu(true);
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)),
});
}
});
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);
mNavigationHelper = 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(IStatusViewHolder 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);
MediaEvent event = MediaEvent.create(getActivity(), status, media, TimelineType.OTHER,
mStatusAdapter.isMediaPreviewEnabled());
HotMobiLogger.getInstance(getActivity()).log(status.account_id, event);
}
@Override
public void onStatusActionClick(IStatusViewHolder 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 = mTwitterWrapper;
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(IStatusViewHolder holder, int position) {
Utils.openStatus(getActivity(), mStatusAdapter.getStatus(position), null);
}
@Override
public boolean onStatusLongClick(IStatusViewHolder holder, int position) {
return false;
}
@Override
public void onStatusMenuClick(IStatusViewHolder 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(), mPreferences, popupMenu.getMenu(), status);
popupMenu.show();
mPopupMenu = popupMenu;
mSelectedStatus = status;
}
@Override
public void onUserProfileClick(IStatusViewHolder 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, options);
// BEGIN HotMobi
MediaEvent event = MediaEvent.create(getActivity(), status, media, TimelineType.OTHER,
mStatusAdapter.isMediaPreviewEnabled());
HotMobiLogger.getInstance(getActivity()).log(status.account_id, event);
// END HotMobi
}
@Override
public boolean handleKeyboardShortcutSingle(@NonNull KeyboardShortcutsHandler handler, int keyCode, @NonNull KeyEvent event, int metaState) {
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, metaState);
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 = mTwitterWrapper;
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 isKeyboardShortcutHandled(@NonNull KeyboardShortcutsHandler handler, int keyCode, @NonNull KeyEvent event, int metaState) {
final String action = handler.getKeyAction(CONTEXT_TAG_STATUS, keyCode, event, metaState);
if (action == null) return false;
switch (action) {
case ACTION_STATUS_REPLY:
case ACTION_STATUS_RETWEET:
case ACTION_STATUS_FAVORITE:
return true;
}
return mNavigationHelper.isKeyboardShortcutHandled(handler, keyCode, event, metaState);
}
@Override
public boolean handleKeyboardShortcutRepeat(@NonNull final KeyboardShortcutsHandler handler,
final int keyCode, final int repeatCount,
@NonNull final KeyEvent event, int metaState) {
return mNavigationHelper.handleKeyboardShortcutRepeat(handler, keyCode,
repeatCount, event, metaState);
}
@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);
}
@Override
public void onLoadFinished(final Loader<SingleResponse<ParcelableStatus>> loader,
final SingleResponse<ParcelableStatus> data) {
if (data.hasData()) {
final Pair<Long, Integer> readPosition = saveReadPosition();
final ParcelableStatus status = data.getData();
final Bundle dataExtra = data.getExtras();
final ParcelableCredentials credentials = dataExtra.getParcelable(EXTRA_ACCOUNT);
if (mStatusAdapter.setStatus(status, credentials)) {
mStatusAdapter.setConversation(null);
mStatusAdapter.setReplies(null);
loadConversation(status);
loadReplies(status);
final int position = mStatusAdapter.getFirstPositionOfItem(StatusAdapter.ITEM_IDX_STATUS);
if (position != RecyclerView.NO_POSITION) {
mLayoutManager.scrollToPositionWithOffset(position, 0);
}
final TweetEvent event = TweetEvent.create(getActivity(), status, TimelineType.OTHER);
event.setAction(TweetEvent.Action.OPEN);
mStatusEvent = event;
} else if (readPosition != null) {
restoreReadPosition(readPosition);
}
setState(STATE_LOADED);
} else {
//TODO show errors
setState(STATE_ERROR);
}
invalidateOptionsMenu();
}
@Override
public void onLoaderReset(final Loader<SingleResponse<ParcelableStatus>> loader) {
final TweetEvent event = mStatusEvent;
if (event == null) return;
event.markEnd();
HotMobiLogger.getInstance(getActivity()).log(event.getAccountId(), event);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.menu_status, menu);
}
@Override
public void onPrepareOptionsMenu(Menu menu) {
MenuUtils.setMenuItemAvailability(menu, R.id.current_status, mStatusAdapter.getStatus() != null);
super.onPrepareOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.current_status: {
if (mStatusAdapter.getStatus() != null) {
final int position = mStatusAdapter.getFirstPositionOfItem(StatusAdapter.ITEM_IDX_STATUS);
mRecyclerView.smoothScrollToPosition(position);
}
return true;
}
}
return super.onOptionsItemSelected(item);
}
private void setConversation(List<ParcelableStatus> data) {
final Pair<Long, Integer> readPosition = saveReadPosition();
mStatusAdapter.setConversation(data);
restoreReadPosition(readPosition);
}
private void addConversation(ParcelableStatus status, int position) {
mStatusAdapter.addConversation(status, position);
}
private StatusAdapter getAdapter() {
return mStatusAdapter;
}
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;
}
private void restoreReadPosition(@Nullable Pair<Long, Integer> position) {
if (position == null) return;
// FIXME We don't know why we need to -1 here, but only -1 works.
final int adapterPosition = mStatusAdapter.findPositionById(position.first) - 1;
if (adapterPosition < 0) return;
//TODO maintain read position
mLayoutManager.scrollToPositionWithOffset(adapterPosition, position.second);
}
@Nullable
private Pair<Long, Integer> saveReadPosition() {
final int position = mLayoutManager.findFirstVisibleItemPosition();
if (position < 0) return null;
final int itemType = mStatusAdapter.getItemType(position);
long itemId = mStatusAdapter.getItemId(position);
final View positionView;
if (itemType == StatusAdapter.ITEM_IDX_CONVERSATION_LOAD_MORE) {
// Should be next item
final int statusPosition = mStatusAdapter.getFirstPositionOfItem(StatusAdapter.ITEM_IDX_STATUS);
positionView = mLayoutManager.findViewByPosition(statusPosition);
itemId = mStatusAdapter.getItemId(statusPosition);
} else {
positionView = mLayoutManager.findViewByPosition(position);
}
return new Pair<>(itemId, positionView != null ? positionView.getTop() : -1);
}
private void setReplies(List<ParcelableStatus> data) {
final Pair<Long, Integer> readPosition = saveReadPosition();
mStatusAdapter.setReplies(data);
//TODO maintain read position
restoreReadPosition(readPosition);
}
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 void showConversationError(Exception exception) {
}
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();
}
}
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 onPreExecute() {
super.onPreExecute();
fragment.getAdapter().setConversationsLoading(true);
}
@Override
protected void onPostExecute(final ListResponse<ParcelableStatus> data) {
final StatusAdapter adapter = fragment.getAdapter();
if (data.hasData()) {
fragment.setConversation(data.getData());
} else if (data.hasException()) {
fragment.showConversationError(data.getException());
} else {
Utils.showErrorMessage(context, context.getString(R.string.action_getting_status), data.getException(), true);
}
adapter.setConversationsLoading(false);
}
@Override
protected void onProgressUpdate(ParcelableStatus... values) {
for (ParcelableStatus status : values) {
// fragment.addConversation(status, 0);
}
}
@Override
protected void onCancelled() {
}
}
private static class DetailStatusViewHolder extends ViewHolder implements OnClickListener,
ActionMenuView.OnMenuItemClickListener {
private final StatusFragment fragment;
private final StatusAdapter adapter;
private final ActionMenuView menuBar;
private final TextView nameView, screenNameView;
private final StatusTextView textView;
private final TextView quotedTextView;
private final TextView quotedNameView, quotedScreenNameView;
private final ImageView 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 TextView quoteOriginalLink;
private final ColorLabelRelativeLayout profileContainer;
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;
private final TextView favoritesLabel;
public DetailStatusViewHolder(StatusFragment fragment, StatusAdapter adapter, View itemView) {
super(itemView);
this.fragment = fragment;
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 = (ImageView) 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);
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);
quoteOriginalLink = (TextView) itemView.findViewById(R.id.quote_original_link);
profileContainer = (ColorLabelRelativeLayout) itemView.findViewById(R.id.profile_container);
twitterCard = (TwitterCardContainer) itemView.findViewById(R.id.twitter_card);
favoritesLabel = (TextView) itemView.findViewById(R.id.favorites_label);
quotedTextView = (TextView) itemView.findViewById(R.id.quoted_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));
retweetedByView.setVisibility(View.VISIBLE);
} else {
retweetedByView.setText(null);
retweetedByView.setVisibility(View.GONE);
}
profileContainer.drawEnd(Utils.getAccountColor(context, status.account_id));
final int layoutPosition = getLayoutPosition();
if (status.is_quote && ArrayUtils.isEmpty(status.media)) {
quoteOriginalLink.setVisibility(View.VISIBLE);
quotedNameContainer.setVisibility(View.VISIBLE);
quotedTextView.setVisibility(View.VISIBLE);
quoteIndicator.setVisibility(View.VISIBLE);
quotedNameView.setText(manager.getUserNickname(status.quoted_user_id, status.quoted_user_name, false));
quotedScreenNameView.setText("@" + status.quoted_user_screen_name);
final Spanned quotedText = HtmlSpanBuilder.fromHtml(status.quoted_text_html);
if (!TextUtils.equals(quotedTextView.getText(), quotedText)) {
quotedTextView.setText(quotedText);
}
linkify.applyAllLinks(quotedTextView, status.account_id, layoutPosition, status.is_possibly_sensitive);
ThemeUtils.applyParagraphSpacing(quotedTextView, 1.1f);
quoteIndicator.setColor(manager.getUserColor(status.user_id, false));
profileContainer.drawStart(manager.getUserColor(status.quoted_user_id, false));
} else {
quoteOriginalLink.setVisibility(View.GONE);
quotedNameContainer.setVisibility(View.GONE);
quotedTextView.setVisibility(View.GONE);
quoteIndicator.setVisibility(View.GONE);
profileContainer.drawStart(manager.getUserColor(status.user_id, false));
}
final long timestamp;
if (status.is_retweet) {
timestamp = status.retweet_timestamp;
} else {
timestamp = status.timestamp;
}
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);
final int typeIconRes = Utils.getUserTypeIconRes(status.user_is_verified, status.user_is_protected);
final int typeDescriptionRes = Utils.getUserTypeDescriptionRes(status.user_is_verified, status.user_is_protected);
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(status.source)) {
timeSourceView.setText(HtmlSpanBuilder.fromHtml(context.getString(R.string.time_source, timeString, status.source)));
} else if (TextUtils.isEmpty(timeString) && !TextUtils.isEmpty(status.source)) {
timeSourceView.setText(HtmlSpanBuilder.fromHtml(context.getString(R.string.source, status.source)));
} else if (!TextUtils.isEmpty(timeString) && TextUtils.isEmpty(status.source)) {
timeSourceView.setText(timeString);
}
timeSourceView.setMovementMethod(LinkMovementMethod.getInstance());
final Spanned text = HtmlSpanBuilder.fromHtml(status.text_html);
if (!TextUtils.equals(textView.getText(), text))
textView.setText(text);
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));
final ParcelableMedia[] media = Utils.getPrimaryMedia(status);
if (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(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, fragment.mPreferences, menuBar.getMenu(), status,
adapter.getStatusAccount());
textView.setTextIsSelectable(true);
quotedTextView.setTextIsSelectable(true);
textView.setMovementMethod(LinkMovementMethod.getInstance());
quotedTextView.setMovementMethod(LinkMovementMethod.getInstance());
}
@Override
public void onClick(View v) {
final ParcelableStatus status = adapter.getStatus(getLayoutPosition());
final StatusFragment fragment = adapter.getFragment();
if (status == null || fragment == null) return;
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));
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: {
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.quoted_user_id,
status.quoted_user_screen_name, null);
break;
}
case R.id.quote_original_link: {
Utils.openStatus(adapter.getContext(), status.account_id, status.quoted_id);
}
}
}
@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.mTwitterWrapper;
final FragmentActivity activity = fragment.getActivity();
final FragmentManager fm = fragment.getFragmentManager();
if (item.getItemId() == R.id.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_detail_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);
retweetedByView.setOnClickListener(this);
locationView.setOnClickListener(this);
quoteOriginalLink.setOnClickListener(this);
final float defaultTextSize = adapter.getTextSize();
nameView.setTextSize(defaultTextSize * 1.25f);
quotedNameView.setTextSize(defaultTextSize * 1.25f);
textView.setTextSize(defaultTextSize * 1.25f);
quotedTextView.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());
quotedTextView.setCustomSelectionActionModeCallback(new StatusActionModeCallback(quotedTextView, activity));
textView.setCustomSelectionActionModeCallback(new StatusActionModeCallback(textView, activity));
if (adapter.shouldUseStarsForLikes()) {
favoritesLabel.setText(R.string.favorites);
}
}
}
private static class SpaceViewHolder extends ViewHolder {
public SpaceViewHolder(View itemView) {
super(itemView);
}
}
private static class StatusAdapter extends BaseRecyclerViewAdapter<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_REPLY_ERROR = 4;
private static final int VIEW_TYPE_SPACE = 5;
private static final int ITEM_IDX_CONVERSATION_ERROR = 0;
private static final int ITEM_IDX_CONVERSATION_LOAD_MORE = 1;
private static final int ITEM_IDX_CONVERSATION = 2;
private static final int ITEM_IDX_STATUS = 3;
private static final int ITEM_IDX_REPLY = 4;
private static final int ITEM_IDX_REPLY_LOAD_MORE = 5;
private static final int ITEM_IDX_REPLY_ERROR = 6;
private static final int ITEM_IDX_SPACE = 7;
private static final int ITEM_TYPES_SUM = 8;
private final StatusFragment mFragment;
private final LayoutInflater mInflater;
private final MediaLoadingHandler mMediaLoadingHandler;
private final TwidereLinkify mTwidereLinkify;
private final int[] mItemCounts;
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 final boolean mUseStarsForLikes;
private boolean mLoadMoreSupported;
private boolean mLoadMoreIndicatorVisible;
private boolean mDetailMediaExpanded;
private ParcelableStatus mStatus;
private ParcelableCredentials mStatusAccount;
private List<ParcelableStatus> mConversation, mReplies;
private StatusAdapterListener mStatusAdapterListener;
private RecyclerView mRecyclerView;
private CharSequence mReplyError;
public StatusAdapter(StatusFragment fragment, boolean compact) {
super(fragment.getContext());
setHasStableIds(true);
final Context context = fragment.getActivity();
final Resources res = context.getResources();
mItemCounts = new int[ITEM_TYPES_SUM];
// There's always a space at the end of the list
mItemCounts[ITEM_IDX_SPACE] = 1;
mFragment = fragment;
mInflater = LayoutInflater.from(context);
mMediaLoadingHandler = new MediaLoadingHandler(R.id.media_preview_progress);
mCardBackgroundColor = ThemeUtils.getCardBackgroundColor(context, ThemeUtils.getThemeBackgroundOption(context), ThemeUtils.getUserThemeBackgroundAlpha(context));
mNameFirst = mPreferences.getBoolean(KEY_NAME_FIRST, true);
mTextSize = mPreferences.getInt(KEY_TEXT_SIZE, res.getInteger(R.integer.default_text_size));
mProfileImageStyle = Utils.getProfileImageStyle(mPreferences.getString(KEY_PROFILE_IMAGE_STYLE, null));
mMediaPreviewStyle = Utils.getMediaPreviewStyle(mPreferences.getString(KEY_MEDIA_PREVIEW_STYLE, null));
mLinkHighlightingStyle = Utils.getLinkHighlightingStyleInt(mPreferences.getString(KEY_LINK_HIGHLIGHT_OPTION, null));
mIsCompact = compact;
mDisplayProfileImage = mPreferences.getBoolean(KEY_DISPLAY_PROFILE_IMAGE, true);
mDisplayMediaPreview = mPreferences.getBoolean(KEY_MEDIA_PREVIEW, false);
mSensitiveContentEnabled = mPreferences.getBoolean(KEY_DISPLAY_SENSITIVE_CONTENTS, false);
mHideCardActions = mPreferences.getBoolean(KEY_HIDE_CARD_ACTIONS, false);
mUseStarsForLikes = mPreferences.getBoolean(KEY_I_WANT_MY_STARS_BACK);
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);
mItemCounts[ITEM_IDX_CONVERSATION] = mConversation.size();
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 int getProfileImageStyle() {
return mProfileImageStyle;
}
@Override
public float getTextSize() {
return mTextSize;
}
@NonNull
@Override
public AsyncTwitterWrapper getTwitterWrapper() {
return mFragment.mTwitterWrapper;
}
@Override
public boolean isProfileImageEnabled() {
return mDisplayProfileImage;
}
@NonNull
@Override
public MediaLoaderWrapper getMediaLoader() {
return mMediaLoader;
}
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 itemStart = getItemTypeStart(position);
final int itemType = getItemType(position);
return getStatusByItemType(position, itemStart, itemType);
}
private ParcelableStatus getStatusByItemType(int position, int itemStart, int itemType) {
switch (itemType) {
case ITEM_IDX_CONVERSATION: {
return mConversation != null ? mConversation.get(position - itemStart) : null;
}
case ITEM_IDX_REPLY: {
return mReplies != null ? mReplies.get(position - itemStart) : null;
}
case ITEM_IDX_STATUS: {
return mStatus;
}
}
return null;
}
@Override
public long getStatusId(int position) {
final ParcelableStatus status = getStatus(position);
return status != null ? status.hashCode() : position;
}
@Override
public int getStatusesCount() {
return mItemCounts[ITEM_IDX_CONVERSATION] + mItemCounts[ITEM_IDX_STATUS] + mItemCounts[ITEM_IDX_REPLY];
}
@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 boolean shouldUseStarsForLikes() {
return mUseStarsForLikes;
}
@Override
public MediaLoadingHandler getMediaLoadingHandler() {
return mMediaLoadingHandler;
}
@NonNull
@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 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();
updateItemDecoration();
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case VIEW_TYPE_DETAIL_STATUS: {
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(mFragment, 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(getContext()));
}
case VIEW_TYPE_REPLY_ERROR: {
final View view = mInflater.inflate(R.layout.adapter_item_status_error, parent,
false);
return new StatusErrorItemViewHolder(view);
}
}
return null;
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
final int itemType = getItemType(position);
final int itemViewType = getItemViewTypeByItemType(itemType);
switch (itemViewType) {
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 IStatusViewHolder statusHolder = (IStatusViewHolder) holder;
// Display 'in reply to' for first item
// useful to indicate whether first tweet has reply or not
// We only display that indicator for first conversation item
statusHolder.displayStatus(status, itemType == ITEM_IDX_CONVERSATION
&& (position - getItemTypeStart(position)) == 0);
break;
}
case VIEW_TYPE_REPLY_ERROR: {
final StatusErrorItemViewHolder errorHolder = (StatusErrorItemViewHolder) holder;
errorHolder.showError(mReplyError);
break;
}
}
}
@Override
public int getItemViewType(int position) {
return getItemViewTypeByItemType(getItemType(position));
}
private int getItemViewTypeByItemType(int type) {
switch (type) {
case ITEM_IDX_CONVERSATION:
case ITEM_IDX_REPLY:
return VIEW_TYPE_LIST_STATUS;
case ITEM_IDX_CONVERSATION_LOAD_MORE:
return VIEW_TYPE_CONVERSATION_LOAD_INDICATOR;
case ITEM_IDX_REPLY_LOAD_MORE:
return VIEW_TYPE_REPLIES_LOAD_INDICATOR;
case ITEM_IDX_STATUS:
return VIEW_TYPE_DETAIL_STATUS;
case ITEM_IDX_SPACE:
return VIEW_TYPE_SPACE;
case ITEM_IDX_REPLY_ERROR:
return VIEW_TYPE_REPLY_ERROR;
}
throw new IllegalStateException();
}
private int getItemType(int position) {
int typeStart = 0;
for (int type = 0; type < ITEM_TYPES_SUM; type++) {
int typeCount = mItemCounts[type];
final int typeEnd = typeStart + typeCount;
if (position >= typeStart && position < typeEnd) return type;
typeStart = typeEnd;
}
throw new IllegalStateException("Unknown position " + position);
}
private int getItemTypeStart(int position) {
int typeStart = 0;
for (int type = 0; type < ITEM_TYPES_SUM; type++) {
int typeCount = mItemCounts[type];
final int typeEnd = typeStart + typeCount;
if (position >= typeStart && position < typeEnd) return typeStart;
typeStart = typeEnd;
}
throw new IllegalStateException();
}
@Override
public long getItemId(int position) {
final ParcelableStatus status = getStatus(position);
if (status != null) return status.id;
return getItemType(position);
}
@Override
public int getItemCount() {
return MathUtils.sum(mItemCounts);
}
@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((IStatusViewHolder) holder, id, position);
}
}
@Override
public void onItemMenuClick(ViewHolder holder, View itemView, int position) {
if (mStatusAdapterListener != null) {
mStatusAdapterListener.onStatusMenuClick((IStatusViewHolder) holder, itemView, position);
}
}
@Override
public void onMediaClick(IStatusViewHolder holder, View view, ParcelableMedia media, int position) {
if (mStatusAdapterListener != null) {
mStatusAdapterListener.onMediaClick(holder, view, media, position);
}
}
@Override
public final void onStatusClick(IStatusViewHolder holder, int position) {
if (mStatusAdapterListener != null) {
mStatusAdapterListener.onStatusClick(holder, position);
}
}
@Override
public boolean onStatusLongClick(IStatusViewHolder holder, int position) {
return false;
}
@Override
public void onUserProfileClick(IStatusViewHolder 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;
setCount(ITEM_IDX_CONVERSATION, conversation != null ? conversation.size() : 0);
updateItemDecoration();
}
private void setCount(int idx, int size) {
mItemCounts[idx] = size;
notifyDataSetChanged();
}
public void setEventListener(StatusAdapterListener listener) {
mStatusAdapterListener = listener;
}
public void setReplyError(CharSequence error) {
mReplyError = error;
setCount(ITEM_IDX_REPLY_ERROR, error != null ? 1 : 0);
updateItemDecoration();
}
public void setReplies(List<ParcelableStatus> replies) {
mReplies = replies;
setCount(ITEM_IDX_REPLY, replies != null ? replies.size() : 0);
updateItemDecoration();
}
public boolean setStatus(final ParcelableStatus status, final ParcelableCredentials credentials) {
final ParcelableStatus old = mStatus;
mStatus = status;
mStatusAccount = credentials;
setCount(ITEM_IDX_STATUS, status != null ? 1 : 0);
updateItemDecoration();
return !CompareUtils.objectEquals(old, status);
}
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();
}
public void setRepliesLoading(boolean loading) {
setCount(ITEM_IDX_REPLY_LOAD_MORE, loading ? 1 : 0);
updateItemDecoration();
}
public void setConversationsLoading(boolean loading) {
setCount(ITEM_IDX_CONVERSATION_LOAD_MORE, loading ? 1 : 0);
updateItemDecoration();
}
public int getFirstPositionOfItem(int itemIdx) {
int position = 0;
for (int i = 0; i < ITEM_TYPES_SUM; i++) {
if (itemIdx == i) return position;
position += mItemCounts[i];
}
return RecyclerView.NO_POSITION;
}
public static class StatusErrorItemViewHolder extends ViewHolder {
private final TextView textView;
public StatusErrorItemViewHolder(View itemView) {
super(itemView);
textView = (TextView) itemView.findViewById(android.R.id.text1);
textView.setMovementMethod(LinkMovementMethod.getInstance());
textView.setLinksClickable(true);
}
public void showError(CharSequence text) {
textView.setText(text);
}
}
}
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_SPACE) {
break;
}
if (typeToMeasure == StatusAdapter.VIEW_TYPE_DETAIL_STATUS || heightBeforeSpace != 0) {
heightBeforeSpace += super.getDecoratedMeasuredHeight(childToMeasure)
+ paramsToMeasure.topMargin + paramsToMeasure.bottomMargin;
}
}
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);
}
}
}