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

1212 lines
51 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.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.graphics.Color;
import android.location.Address;
import android.location.Geocoder;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.LoaderManager;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.AsyncTaskLoader;
import android.support.v4.content.Loader;
import android.support.v4.util.Pair;
import android.support.v4.view.ViewCompat;
import android.text.Html;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
import android.text.style.URLSpan;
import android.util.Log;
import android.view.ActionMode;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MenuItem.OnMenuItemClickListener;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.webkit.URLUtil;
import android.widget.AbsListView;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import org.mariotaku.refreshnow.widget.RefreshMode;
import org.mariotaku.twidere.R;
import org.mariotaku.twidere.activity.support.AccountSelectorActivity;
import org.mariotaku.twidere.activity.support.ColorPickerDialogActivity;
import org.mariotaku.twidere.adapter.ParcelableStatusesListAdapter;
import org.mariotaku.twidere.adapter.iface.IStatusesListAdapter;
import org.mariotaku.twidere.app.TwidereApplication;
import org.mariotaku.twidere.model.ParcelableAccount;
import org.mariotaku.twidere.model.ParcelableAccount.ParcelableAccountWithCredentials;
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.task.AsyncTask;
import org.mariotaku.twidere.text.method.StatusContentMovementMethod;
import org.mariotaku.twidere.util.AsyncTwitterWrapper;
import org.mariotaku.twidere.util.ClipboardUtils;
import org.mariotaku.twidere.util.ImageLoaderWrapper;
import org.mariotaku.twidere.util.MediaPreviewUtils;
import org.mariotaku.twidere.util.MediaPreviewUtils.OnMediaClickListener;
import org.mariotaku.twidere.util.OnLinkClickHandler;
import org.mariotaku.twidere.util.ParseUtils;
import org.mariotaku.twidere.util.ThemeUtils;
import org.mariotaku.twidere.util.TwidereLinkify;
import org.mariotaku.twidere.util.Utils;
import org.mariotaku.twidere.view.CircularImageView;
import org.mariotaku.twidere.view.ColorLabelRelativeLayout;
import org.mariotaku.twidere.view.ExtendedFrameLayout;
import org.mariotaku.twidere.view.StatusTextView;
import org.mariotaku.twidere.view.TwidereMenuBar;
import java.io.IOException;
import java.util.List;
import java.util.Locale;
import edu.ucdavis.earlybird.ProfilingUtil;
import twitter4j.Relationship;
import twitter4j.Twitter;
import twitter4j.TwitterException;
import static android.text.TextUtils.isEmpty;
import static org.mariotaku.twidere.util.UserColorNicknameUtils.clearUserColor;
import static org.mariotaku.twidere.util.UserColorNicknameUtils.clearUserNickname;
import static org.mariotaku.twidere.util.UserColorNicknameUtils.getUserColor;
import static org.mariotaku.twidere.util.UserColorNicknameUtils.getUserNickname;
import static org.mariotaku.twidere.util.UserColorNicknameUtils.setUserColor;
import static org.mariotaku.twidere.util.Utils.cancelRetweet;
import static org.mariotaku.twidere.util.Utils.findStatus;
import static org.mariotaku.twidere.util.Utils.formatToLongTimeString;
import static org.mariotaku.twidere.util.Utils.getAccountColor;
import static org.mariotaku.twidere.util.Utils.getDefaultTextSize;
import static org.mariotaku.twidere.util.Utils.getDisplayName;
import static org.mariotaku.twidere.util.Utils.getLocalizedNumber;
import static org.mariotaku.twidere.util.Utils.getMapStaticImageUri;
import static org.mariotaku.twidere.util.Utils.getTwitterInstance;
import static org.mariotaku.twidere.util.Utils.getUserTypeIconRes;
import static org.mariotaku.twidere.util.Utils.isMyRetweet;
import static org.mariotaku.twidere.util.Utils.isSameAccount;
import static org.mariotaku.twidere.util.Utils.openImageDirectly;
import static org.mariotaku.twidere.util.Utils.openMap;
import static org.mariotaku.twidere.util.Utils.openStatus;
import static org.mariotaku.twidere.util.Utils.openStatusFavoriters;
import static org.mariotaku.twidere.util.Utils.openStatusReplies;
import static org.mariotaku.twidere.util.Utils.openStatusRetweeters;
import static org.mariotaku.twidere.util.Utils.openUserProfile;
import static org.mariotaku.twidere.util.Utils.scrollListToPosition;
import static org.mariotaku.twidere.util.Utils.setMenuForStatus;
import static org.mariotaku.twidere.util.Utils.showErrorMessage;
import static org.mariotaku.twidere.util.Utils.showOkMessage;
import static org.mariotaku.twidere.util.Utils.startStatusShareChooser;
public class StatusFragment extends ParcelableStatusesListFragment implements OnClickListener,
OnMediaClickListener, OnSharedPreferenceChangeListener, ActionMode.Callback {
public static final String TRANSITION_NAME_CARD = "card";
private static final int LOADER_ID_STATUS = 1;
private static final int LOADER_ID_FOLLOW = 2;
private static final int LOADER_ID_LOCATION = 3;
private ParcelableStatus mStatus;
private Locale mLocale;
private boolean mLoadMoreAutomatically;
private boolean mFollowInfoDisplayed, mLocationInfoDisplayed;
private boolean mStatusLoaderInitialized, mLocationLoaderInitialized;
private boolean mFollowInfoLoaderInitialized;
private boolean mShouldScroll;
private SharedPreferences mPreferences;
private AsyncTwitterWrapper mTwitterWrapper;
private ImageLoaderWrapper mImageLoader;
private Handler mHandler;
private TextView mNameView, mScreenNameView, mTimeSourceView, mInReplyToView, mLocationView;
private TextView mRepliesCountView, mRetweetsCountView, mFavoritesCountView;
private StatusTextView mTextView;
private CircularImageView mProfileImageView;
private ImageView mProfileTypeView, mMapView;
private Button mFollowButton;
private Button mRetryButton;
private View mMainContent, mFollowIndicator, mImagePreviewContainer, mLocationContainer, mLocationBackgroundView;
private ColorLabelRelativeLayout mProfileView;
private TwidereMenuBar mMenuBar;
private ProgressBar mDetailsLoadProgress, mFollowInfoProgress;
private LinearLayout mImagePreviewGrid;
private View mHeaderView;
private View mLoadImagesIndicator;
private View mRepliesContainer, mRetweetsContainer, mFavoritesContainer;
private ExtendedFrameLayout mDetailsContainer;
private ListView mListView;
private LoadConversationTask mConversationTask;
private final BroadcastReceiver mStatusReceiver = new BroadcastReceiver() {
@Override
public void onReceive(final Context context, final Intent intent) {
if (getActivity() == null || !isAdded() || isDetached()) return;
final String action = intent.getAction();
switch (action) {
case BROADCAST_FRIENDSHIP_CHANGED: {
if (mStatus != null && mStatus.user_id == intent.getLongExtra(EXTRA_USER_ID, -1)) {
showFollowInfo(true);
}
break;
}
case BROADCAST_FAVORITE_CHANGED: {
final ParcelableStatus status = intent.getParcelableExtra(EXTRA_STATUS);
if (mStatus != null && status != null && isSameAccount(context, status.account_id, mStatus.account_id)
&& status.id == getStatusId()) {
getStatus(true);
}
break;
}
case BROADCAST_STATUS_RETWEETED: {
final long status_id = intent.getLongExtra(EXTRA_STATUS_ID, -1);
if (status_id > 0 && status_id == getStatusId()) {
getStatus(true);
}
break;
}
}
}
};
private final LoaderCallbacks<SingleResponse<ParcelableStatus>> mStatusLoaderCallbacks = new LoaderCallbacks<SingleResponse<ParcelableStatus>>() {
@Override
public Loader<SingleResponse<ParcelableStatus>> onCreateLoader(final int id, final Bundle args) {
mDetailsLoadProgress.setVisibility(View.VISIBLE);
mMainContent.setVisibility(View.INVISIBLE);
mRetryButton.setVisibility(View.GONE);
mMainContent.setEnabled(false);
setProgressBarIndeterminateVisibility(true);
final boolean omitIntentExtra = args.getBoolean(EXTRA_OMIT_INTENT_EXTRA, true);
final long accountId = args.getLong(EXTRA_ACCOUNT_ID, -1);
final long statusId = args.getLong(EXTRA_STATUS_ID, -1);
return new ParcelableStatusLoader(getActivity(), omitIntentExtra, getArguments(), accountId, statusId);
}
@Override
public void onLoaderReset(final Loader<SingleResponse<ParcelableStatus>> loader) {
}
@Override
public void onLoadFinished(final Loader<SingleResponse<ParcelableStatus>> loader,
final SingleResponse<ParcelableStatus> data) {
if (data.getData() == null) {
// TODO
mRetryButton.setVisibility(View.VISIBLE);
showErrorMessage(getActivity(), getString(R.string.action_getting_status), data.getException(), true);
} else {
mRetryButton.setVisibility(View.GONE);
displayStatus(data.getData());
mDetailsLoadProgress.setVisibility(View.GONE);
mMainContent.setVisibility(View.VISIBLE);
mMainContent.setEnabled(true);
}
setProgressBarIndeterminateVisibility(false);
}
};
private final LoaderCallbacks<String> mLocationLoaderCallbacks = new LoaderCallbacks<String>() {
@Override
public Loader<String> onCreateLoader(final int id, final Bundle args) {
return new LocationInfoLoader(getActivity(), mStatus != null ? mStatus.location : null);
}
@Override
public void onLoaderReset(final Loader<String> loader) {
}
@Override
public void onLoadFinished(final Loader<String> loader, final String data) {
if (data != null) {
mLocationView.setText(data);
mLocationInfoDisplayed = true;
} else {
mLocationView.setText(R.string.view_map);
mLocationInfoDisplayed = false;
}
}
};
private final LoaderCallbacks<SingleResponse<Boolean>> mFollowInfoLoaderCallbacks = new LoaderCallbacks<SingleResponse<Boolean>>() {
@Override
public Loader<SingleResponse<Boolean>> onCreateLoader(final int id, final Bundle args) {
mFollowIndicator.setVisibility(View.VISIBLE);
mFollowButton.setVisibility(View.GONE);
mFollowInfoProgress.setVisibility(View.VISIBLE);
return new FollowInfoLoader(getActivity(), mStatus);
}
@Override
public void onLoaderReset(final Loader<SingleResponse<Boolean>> loader) {
}
@Override
public void onLoadFinished(final Loader<SingleResponse<Boolean>> loader, final SingleResponse<Boolean> data) {
if (!data.hasException()) {
mFollowIndicator.setVisibility(!data.hasData() || data.getData() ? View.GONE : View.VISIBLE);
if (data.getData() != null) {
mFollowButton.setVisibility(data.getData() ? View.GONE : View.VISIBLE);
mFollowInfoDisplayed = true;
}
}
mFollowInfoProgress.setVisibility(View.GONE);
}
};
private final OnMenuItemClickListener mMenuItemClickListener = new OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(final MenuItem item) {
return handleMenuItemClick(item);
}
};
public void displayStatus(final ParcelableStatus status) {
final boolean status_unchanged = mStatus != null && status != null && status.equals(mStatus);
if (!status_unchanged) {
getListAdapter().setData(null);
if (mStatus != null) {
// UCD
ProfilingUtil.profile(getActivity(), mStatus.account_id, "End, " + mStatus.id);
}
} else {
setSelection(0);
}
if (mConversationTask != null && mConversationTask.getStatus() == AsyncTask.Status.RUNNING) {
mConversationTask.cancel(true);
}
mStatus = status;
if (mStatus != null) {
// UCD
ProfilingUtil.profile(getActivity(), mStatus.account_id, "Start, " + mStatus.id);
}
if (!status_unchanged) {
clearPreviewImages();
hidePreviewImages();
}
if (status == null || getActivity() == null) return;
final Bundle args = getArguments();
args.putLong(EXTRA_ACCOUNT_ID, status.account_id);
args.putLong(EXTRA_STATUS_ID, status.id);
args.putParcelable(EXTRA_STATUS, status);
setMenuForStatus(getActivity(), mMenuBar.getMenu(), status);
mMenuBar.show();
updateUserColor();
mProfileView.drawEnd(getAccountColor(getActivity(), status.account_id));
final boolean nickname_only = mPreferences.getBoolean(KEY_NICKNAME_ONLY, false);
final boolean name_first = mPreferences.getBoolean(KEY_NAME_FIRST, true);
final boolean display_image_preview = mPreferences.getBoolean(KEY_DISPLAY_IMAGE_PREVIEW, false);
final String nick = getUserNickname(getActivity(), status.user_id, true);
mNameView.setText(TextUtils.isEmpty(nick) ? status.user_name : nickname_only ? nick : getString(
R.string.name_with_nickname, status.user_name, nick));
final int typeIconRes = getUserTypeIconRes(status.user_is_verified, status.user_is_protected);
if (typeIconRes != 0) {
mProfileTypeView.setImageResource(typeIconRes);
mProfileTypeView.setVisibility(View.VISIBLE);
} else {
mProfileTypeView.setImageDrawable(null);
mProfileTypeView.setVisibility(View.GONE);
}
mScreenNameView.setText("@" + status.user_screen_name);
mTextView.setText(Html.fromHtml(status.text_html));
final TwidereLinkify linkify = new TwidereLinkify(
new OnLinkClickHandler(getActivity(), getMultiSelectManager()));
linkify.setLinkTextColor(ThemeUtils.getUserLinkTextColor(getActivity()));
linkify.applyAllLinks(mTextView, status.account_id, status.is_possibly_sensitive);
ThemeUtils.applyParagraphSpacing(mTextView, 1.1f);
mTextView.setMovementMethod(StatusContentMovementMethod.getInstance());
mTextView.setCustomSelectionActionModeCallback(this);
long timestamp = status.retweet_timestamp > 0 ? status.retweet_timestamp : status.timestamp;
final String timeString = formatToLongTimeString(getActivity(), timestamp);
final String sourceHtml = status.source;
if (!isEmpty(timeString) && !isEmpty(sourceHtml)) {
mTimeSourceView.setText(Html.fromHtml(getString(R.string.time_source, timeString, sourceHtml)));
} else if (isEmpty(timeString) && !isEmpty(sourceHtml)) {
mTimeSourceView.setText(Html.fromHtml(getString(R.string.source, sourceHtml)));
} else if (!isEmpty(timeString) && isEmpty(sourceHtml)) {
mTimeSourceView.setText(timeString);
}
mTimeSourceView.setMovementMethod(LinkMovementMethod.getInstance());
final String in_reply_to = getDisplayName(getActivity(), status.in_reply_to_user_id, status.in_reply_to_name,
status.in_reply_to_screen_name, name_first, nickname_only, true);
mInReplyToView.setText(getString(R.string.in_reply_to_name, in_reply_to));
if (mPreferences.getBoolean(KEY_DISPLAY_PROFILE_IMAGE, true)) {
mImageLoader.displayProfileImage(mProfileImageView, status.user_profile_image_url);
} else {
mProfileImageView.setImageResource(R.drawable.ic_profile_image_default);
}
mImagePreviewContainer.setVisibility(status.media == null || status.media.length == 0 ? View.GONE
: View.VISIBLE);
if (display_image_preview) {
loadPreviewImages();
}
mRetweetsContainer.setVisibility(!status.user_is_protected ? View.VISIBLE : View.GONE);
mRepliesContainer.setVisibility(status.reply_count < 0 ? View.GONE : View.VISIBLE);
mRepliesCountView.setText(getLocalizedNumber(mLocale, status.reply_count));
mRetweetsCountView.setText(getLocalizedNumber(mLocale, status.retweet_count));
mFavoritesCountView.setText(getLocalizedNumber(mLocale, status.favorite_count));
final ParcelableLocation location = status.location;
final boolean is_valid_location = ParcelableLocation.isValidLocation(location);
mLocationContainer.setVisibility(is_valid_location ? View.VISIBLE : View.GONE);
if (display_image_preview) {
mMapView.setVisibility(is_valid_location ? View.VISIBLE : View.GONE);
mLocationBackgroundView.setVisibility(is_valid_location ? View.VISIBLE : View.GONE);
mLocationView.setVisibility(View.VISIBLE);
if (is_valid_location) {
mHandler.post(new DisplayMapRunnable(location, mImageLoader, mMapView));
} else {
mMapView.setImageDrawable(null);
}
} else {
mMapView.setVisibility(View.GONE);
mLocationBackgroundView.setVisibility(View.GONE);
mMapView.setImageDrawable(null);
mLocationView.setVisibility(View.VISIBLE);
}
if (mLoadMoreAutomatically) {
showFollowInfo(true);
showLocationInfo(true);
showConversation();
} else {
mFollowIndicator.setVisibility(View.GONE);
}
updateConversationInfo();
scrollToStart();
}
@Override
public Loader<List<ParcelableStatus>> newLoaderInstance(final Context context, final Bundle args) {
return null;
}
@Override
public boolean onActionItemClicked(final ActionMode mode, final MenuItem item) {
switch (item.getItemId()) {
case android.R.id.copyUrl: {
final int start = mTextView.getSelectionStart(), end = mTextView.getSelectionEnd();
final SpannableString string = SpannableString.valueOf(mTextView.getText());
final URLSpan[] spans = string.getSpans(start, end, URLSpan.class);
if (spans.length != 1) return true;
ClipboardUtils.setText(getActivity(), spans[0].getURL());
mode.finish();
return true;
}
}
return false;
}
@Override
public void onActivityCreated(final Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mLocale = getResources().getConfiguration().locale;
setRefreshMode(shouldEnablePullToRefresh() ? RefreshMode.BOTH : RefreshMode.NONE);
setListShownNoAnimation(true);
mHandler = new Handler();
mListView = getListView();
getListAdapter().setGapDisallowed(true);
final TwidereApplication application = getApplication();
mPreferences = getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
getSharedPreferences(USER_COLOR_PREFERENCES_NAME, Context.MODE_PRIVATE)
.registerOnSharedPreferenceChangeListener(this);
getSharedPreferences(USER_NICKNAME_PREFERENCES_NAME, Context.MODE_PRIVATE)
.registerOnSharedPreferenceChangeListener(this);
mImageLoader = application.getImageLoaderWrapper();
mTwitterWrapper = getTwitterWrapper();
mLoadMoreAutomatically = mPreferences.getBoolean(KEY_LOAD_MORE_AUTOMATICALLY, false);
mLoadImagesIndicator.setOnClickListener(this);
mInReplyToView.setOnClickListener(this);
mFollowButton.setOnClickListener(this);
mProfileView.setOnClickListener(this);
mLocationContainer.setOnClickListener(this);
mRepliesContainer.setOnClickListener(this);
mRetweetsContainer.setOnClickListener(this);
mFavoritesContainer.setOnClickListener(this);
mMenuBar.inflate(R.menu.menu_status);
mMenuBar.setIsBottomBar(true);
mMenuBar.setOnMenuItemClickListener(mMenuItemClickListener);
getStatus(false);
}
@Override
public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
switch (requestCode) {
case REQUEST_SET_COLOR: {
if (mStatus == null) return;
if (resultCode == Activity.RESULT_OK) {
if (data == null) return;
final int color = data.getIntExtra(EXTRA_COLOR, Color.TRANSPARENT);
setUserColor(getActivity(), mStatus.user_id, color);
} else if (resultCode == ColorPickerDialogActivity.RESULT_CLEARED) {
clearUserColor(getActivity(), mStatus.user_id);
}
break;
}
case REQUEST_SELECT_ACCOUNT: {
if (mStatus == null) return;
if (resultCode == Activity.RESULT_OK) {
if (data == null || !data.hasExtra(EXTRA_ID)) return;
final long accountId = data.getLongExtra(EXTRA_ID, -1);
openStatus(getActivity(), accountId, mStatus.id);
}
break;
}
}
}
@Override
public void onClick(final View view) {
if (mStatus == null) return;
final ParcelableStatus status = mStatus;
switch (view.getId()) {
case R.id.profile: {
final FragmentActivity activity = getActivity();
final Bundle activityOption = Utils.makeSceneTransitionOption(activity,
new Pair<View, String>(mProfileImageView, UserFragment.TRANSITION_NAME_PROFILE_IMAGE),
new Pair<View, String>(mProfileTypeView, UserFragment.TRANSITION_NAME_PROFILE_TYPE));
openUserProfile(activity, status.account_id, status.user_id, status.user_screen_name,
activityOption);
break;
}
case R.id.follow: {
mTwitterWrapper.createFriendshipAsync(status.account_id, status.user_id);
break;
}
case R.id.in_reply_to: {
showConversation();
break;
}
case R.id.replies_container: {
openStatusReplies(getActivity(), status.account_id, status.id, status.user_screen_name);
break;
}
case R.id.location_container: {
final ParcelableLocation location = status.location;
if (!ParcelableLocation.isValidLocation(location)) return;
openMap(getActivity(), location.latitude, location.longitude);
break;
}
case R.id.load_images: {
if (status.is_possibly_sensitive) {
final LoadSensitiveImageConfirmDialogFragment f = new LoadSensitiveImageConfirmDialogFragment();
f.show(getChildFragmentManager(), "load_sensitive_image_confirmation");
} else {
loadPreviewImages();
}
// UCD
ProfilingUtil.profile(getActivity(), status.account_id, "Thumbnail click, " + status.id);
break;
}
case R.id.retweets_container: {
openStatusRetweeters(getActivity(), status.account_id, status.retweet_id > 0 ? status.retweet_id
: status.id);
break;
}
case R.id.favorites_container: {
// TODO
final ParcelableAccountWithCredentials account = ParcelableAccount.getAccountWithCredentials(getActivity(),
status.account_id);
if (ParcelableAccountWithCredentials.isOfficialCredentials(getActivity(), account)) {
openStatusFavoriters(getActivity(), status.account_id, status.retweet_id > 0 ? status.retweet_id
: status.id);
}
break;
}
}
}
@Override
public boolean onCreateActionMode(final ActionMode mode, final Menu menu) {
final FragmentActivity a = getActivity();
if (a == null) return false;
a.getMenuInflater().inflate(R.menu.action_status_text_selection, menu);
return true;
}
@Override
public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.fragment_details_page_with_menubar, container, false);
view.findViewById(R.id.menu_bar).setVisibility(View.GONE);
mMainContent = view.findViewById(R.id.content);
mDetailsLoadProgress = (ProgressBar) view.findViewById(R.id.details_load_progress);
mDetailsContainer = (ExtendedFrameLayout) view.findViewById(R.id.details_container);
mDetailsContainer.addView(super.onCreateView(inflater, mDetailsContainer, savedInstanceState));
mHeaderView = inflater.inflate(R.layout.header_status, null, false);
mMenuBar = (TwidereMenuBar) mHeaderView.findViewById(R.id.menu_bar);
mImagePreviewContainer = mHeaderView.findViewById(R.id.image_preview);
mLocationContainer = mHeaderView.findViewById(R.id.location_container);
mLocationView = (TextView) mHeaderView.findViewById(R.id.location_view);
mLocationBackgroundView = mHeaderView.findViewById(R.id.location_background_view);
mMapView = (ImageView) mHeaderView.findViewById(R.id.map_view);
mNameView = (TextView) mHeaderView.findViewById(R.id.name);
mScreenNameView = (TextView) mHeaderView.findViewById(R.id.screen_name);
mTextView = (StatusTextView) mHeaderView.findViewById(R.id.text);
mProfileImageView = (CircularImageView) mHeaderView.findViewById(R.id.profile_image);
mProfileTypeView = (ImageView) mHeaderView.findViewById(R.id.profile_type);
mTimeSourceView = (TextView) mHeaderView.findViewById(R.id.time_source);
mInReplyToView = (TextView) mHeaderView.findViewById(R.id.in_reply_to);
mFollowButton = (Button) mHeaderView.findViewById(R.id.follow);
mFollowIndicator = mHeaderView.findViewById(R.id.follow_indicator);
mFollowInfoProgress = (ProgressBar) mHeaderView.findViewById(R.id.follow_info_progress);
mProfileView = (ColorLabelRelativeLayout) mHeaderView.findViewById(R.id.profile);
mImagePreviewGrid = (LinearLayout) mHeaderView.findViewById(R.id.image_grid);
mRepliesContainer = mHeaderView.findViewById(R.id.replies_container);
mRetweetsContainer = mHeaderView.findViewById(R.id.retweets_container);
mFavoritesContainer = mHeaderView.findViewById(R.id.favorites_container);
mRepliesCountView = (TextView) mHeaderView.findViewById(R.id.replies_count);
mRetweetsCountView = (TextView) mHeaderView.findViewById(R.id.retweets_count);
mFavoritesCountView = (TextView) mHeaderView.findViewById(R.id.favorites_count);
mLoadImagesIndicator = mHeaderView.findViewById(R.id.load_images);
mRetryButton = (Button) view.findViewById(R.id.retry);
final View cardView = mHeaderView.findViewById(R.id.card);
ViewCompat.setTransitionName(cardView, TRANSITION_NAME_CARD);
return view;
}
@Override
public void onDestroyActionMode(final ActionMode mode) {
}
@Override
public void onDestroyView() {
// UCD
if (mStatus != null) {
ProfilingUtil.profile(getActivity(), mStatus.account_id, "End, " + mStatus.id);
}
mStatus = null;
final LoaderManager lm = getLoaderManager();
lm.destroyLoader(LOADER_ID_STATUS);
lm.destroyLoader(LOADER_ID_LOCATION);
lm.destroyLoader(LOADER_ID_FOLLOW);
if (mConversationTask != null && mConversationTask.getStatus() == AsyncTask.Status.RUNNING) {
mConversationTask.cancel(true);
}
super.onDestroyView();
}
@Override
public void onItemsCleared() {
}
@Override
public void onMediaClick(final View view, final ParcelableMedia media) {
final ParcelableStatus status = mStatus;
if (status == null) return;
// UCD
ProfilingUtil.profile(getActivity(), mStatus.account_id, "Large image click, " + mStatus.id + ", " + media.url);
openImageDirectly(getActivity(), status.account_id, media.url);
}
@Override
public boolean onPrepareActionMode(final ActionMode mode, final Menu menu) {
final int start = mTextView.getSelectionStart(), end = mTextView.getSelectionEnd();
final SpannableString string = SpannableString.valueOf(mTextView.getText());
final URLSpan[] spans = string.getSpans(start, end, URLSpan.class);
final boolean avail = spans.length == 1 && URLUtil.isValidUrl(spans[0].getURL());
Utils.setMenuItemAvailability(menu, android.R.id.copyUrl, avail);
return false;
}
@Override
public void onScroll(final AbsListView view, final int firstVisibleItem, final int visibleItemCount,
final int totalItemCount) {
}
@Override
public void onScrollStateChanged(final AbsListView view, final int scrollState) {
super.onScrollStateChanged(view, scrollState);
mShouldScroll = false;
}
@Override
public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences, final String key) {
if (mStatus == null || !ParseUtils.parseString(mStatus.user_id).equals(key)) return;
displayStatus(mStatus);
}
@Override
public void onStart() {
super.onStart();
final IntentFilter filter = new IntentFilter();
filter.addAction(BROADCAST_FRIENDSHIP_CHANGED);
filter.addAction(BROADCAST_FAVORITE_CHANGED);
filter.addAction(BROADCAST_STATUS_RETWEETED);
registerReceiver(mStatusReceiver, filter);
updateUserColor();
final int text_size = mPreferences.getInt(KEY_TEXT_SIZE, getDefaultTextSize(getActivity()));
mTextView.setTextSize(text_size * 1.25f);
mNameView.setTextSize(text_size * 1.25f);
mScreenNameView.setTextSize(text_size * 0.85f);
mTimeSourceView.setTextSize(text_size * 0.85f);
mInReplyToView.setTextSize(text_size * 0.85f);
mLocationView.setTextSize(text_size * 0.85f);
// mRetweetView.setTextSize(text_size * 0.85f);
}
@Override
public void onStop() {
unregisterReceiver(mStatusReceiver);
super.onStop();
}
@Override
public boolean scrollToStart() {
if (mListView == null) return false;
final IStatusesListAdapter<List<ParcelableStatus>> adapter = getListAdapter();
scrollListToPosition(mListView, adapter.getCount() + mListView.getFooterViewsCount() - 1, 0);
return true;
}
// @Override
// protected void setItemSelected(final ParcelableStatus status, final int
// position, final boolean selected) {
// final MultiSelectManager manager = getMultiSelectManager();
// final Object only_item = manager.getCount() == 1 ?
// manager.getSelectedItems().get(0) : null;
// final boolean only_item_selected = only_item != null &&
// !only_item.equals(mStatus);
// mListView.setItemChecked(0, only_item_selected);
// if (mStatus != null) {
// if (only_item_selected) {
// manager.selectItem(mStatus);
// } else {
// manager.unselectItem(mStatus);
// }
// }
// super.setItemSelected(status, position, selected);
// }
@Override
protected String[] getSavedStatusesFileArgs() {
return null;
}
protected boolean handleMenuItemClick(final MenuItem item) {
if (mStatus == null) return false;
final ParcelableStatus status = mStatus;
switch (item.getItemId()) {
case MENU_SHARE: {
startStatusShareChooser(getActivity(), status);
break;
}
case MENU_COPY: {
if (ClipboardUtils.setText(getActivity(), status.text_plain)) {
showOkMessage(getActivity(), R.string.text_copied, false);
}
break;
}
case MENU_RETWEET: {
if (isMyRetweet(status)) {
cancelRetweet(mTwitterWrapper, status);
} else {
final long id_to_retweet = status.is_retweet && status.retweet_id > 0 ? status.retweet_id
: status.id;
mTwitterWrapper.retweetStatus(status.account_id, id_to_retweet);
}
break;
}
case MENU_QUOTE: {
final Intent intent = new Intent(INTENT_ACTION_QUOTE);
intent.putExtra(EXTRA_STATUS, status);
startActivity(intent);
break;
}
case MENU_REPLY: {
final Intent intent = new Intent(INTENT_ACTION_REPLY);
intent.putExtra(EXTRA_STATUS, status);
startActivity(intent);
break;
}
case MENU_FAVORITE: {
if (status.is_favorite) {
mTwitterWrapper.destroyFavoriteAsync(status.account_id, status.id);
} else {
mTwitterWrapper.createFavoriteAsync(status.account_id, status.id);
}
break;
}
case MENU_DELETE: {
DestroyStatusDialogFragment.show(getFragmentManager(), status);
break;
}
case MENU_ADD_TO_FILTER: {
AddStatusFilterDialogFragment.show(getFragmentManager(), status);
break;
}
case MENU_SET_COLOR: {
final Intent intent = new Intent(getActivity(), ColorPickerDialogActivity.class);
final int color = getUserColor(getActivity(), status.user_id, true);
if (color != 0) {
intent.putExtra(EXTRA_COLOR, color);
}
intent.putExtra(EXTRA_CLEAR_BUTTON, color != 0);
intent.putExtra(EXTRA_ALPHA_SLIDER, false);
startActivityForResult(intent, REQUEST_SET_COLOR);
break;
}
case MENU_CLEAR_NICKNAME: {
clearUserNickname(getActivity(), status.user_id);
displayStatus(status);
break;
}
case MENU_SET_NICKNAME: {
final String nick = getUserNickname(getActivity(), status.user_id, true);
SetUserNicknameDialogFragment.show(getFragmentManager(), status.user_id, nick);
break;
}
case MENU_TRANSLATE: {
final ParcelableAccountWithCredentials account = ParcelableAccount.getAccountWithCredentials(getActivity(),
status.account_id);
if (ParcelableAccountWithCredentials.isOfficialCredentials(getActivity(), account)) {
StatusTranslateDialogFragment.show(getFragmentManager(), status);
} else {
}
break;
}
case MENU_OPEN_WITH_ACCOUNT: {
final Intent intent = new Intent(INTENT_ACTION_SELECT_ACCOUNT);
intent.setClass(getActivity(), AccountSelectorActivity.class);
intent.putExtra(EXTRA_SINGLE_SELECTION, true);
startActivityForResult(intent, REQUEST_SELECT_ACCOUNT);
break;
}
default: {
if (item.getIntent() != null) {
try {
startActivity(item.getIntent());
} catch (final ActivityNotFoundException e) {
Log.w(LOGTAG, e);
return false;
}
}
break;
}
}
return true;
}
@Override
protected void onReachedBottom() {
}
@Override
protected void setItemSelected(final ParcelableStatus status, final int position, final boolean selected) {
}
@Override
protected void setListHeaderFooters(final ListView list) {
if (getActivity() == null || isDetached()) return;
list.addHeaderView(mHeaderView, null, true);
}
@Override
protected boolean shouldEnablePullToRefresh() {
return false;
}
@Override
protected boolean shouldShowAccountColor() {
return false;
}
private void addConversationStatus(final ParcelableStatus status) {
if (getActivity() == null || isDetached()) return;
final List<ParcelableStatus> data = getData();
if (data == null) return;
data.add(status);
final ParcelableStatusesListAdapter adapter = (ParcelableStatusesListAdapter) getListAdapter();
adapter.setData(data);
if (!mLoadMoreAutomatically && mShouldScroll) {
setSelection(0);
}
}
private void clearPreviewImages() {
mImagePreviewGrid.removeAllViews();
}
private void getStatus(final boolean omit_intent_extra) {
final LoaderManager lm = getLoaderManager();
lm.destroyLoader(LOADER_ID_STATUS);
final Bundle args = new Bundle(getArguments());
args.putBoolean(EXTRA_OMIT_INTENT_EXTRA, omit_intent_extra);
if (!mStatusLoaderInitialized) {
lm.initLoader(LOADER_ID_STATUS, args, mStatusLoaderCallbacks);
mStatusLoaderInitialized = true;
} else {
lm.restartLoader(LOADER_ID_STATUS, args, mStatusLoaderCallbacks);
}
}
private long getStatusId() {
return mStatus != null ? mStatus.id : -1;
}
private void hidePreviewImages() {
mLoadImagesIndicator.setVisibility(View.VISIBLE);
mImagePreviewGrid.setVisibility(View.GONE);
}
private void loadPreviewImages() {
if (mStatus == null) return;
mLoadImagesIndicator.setVisibility(View.GONE);
mImagePreviewGrid.setVisibility(View.VISIBLE);
mImagePreviewGrid.removeAllViews();
if (mStatus.media != null) {
final int maxColumns = getResources().getInteger(R.integer.grid_column_image_preview);
MediaPreviewUtils.addToLinearLayout(mImagePreviewGrid, mImageLoader, mStatus.media, maxColumns, this);
}
}
private void showConversation() {
if (mConversationTask != null && mConversationTask.getStatus() == AsyncTask.Status.RUNNING) {
mConversationTask.cancel(true);
return;
}
final IStatusesListAdapter<List<ParcelableStatus>> adapter = getListAdapter();
final int count = adapter.getCount();
final ParcelableStatus status;
if (count == 0) {
mShouldScroll = !mLoadMoreAutomatically;
status = mStatus;
} else {
status = adapter.getStatus(adapter.getCount() - 1);
}
if (status == null || status.in_reply_to_status_id <= 0) return;
mConversationTask = new LoadConversationTask(this);
mConversationTask.execute(status);
}
private void showFollowInfo(final boolean force) {
if (mFollowInfoDisplayed && !force) return;
final LoaderManager lm = getLoaderManager();
lm.destroyLoader(LOADER_ID_FOLLOW);
if (!mFollowInfoLoaderInitialized) {
lm.initLoader(LOADER_ID_FOLLOW, null, mFollowInfoLoaderCallbacks);
mFollowInfoLoaderInitialized = true;
} else {
lm.restartLoader(LOADER_ID_FOLLOW, null, mFollowInfoLoaderCallbacks);
}
}
private void showLocationInfo(final boolean force) {
if (mLocationInfoDisplayed && !force) return;
final LoaderManager lm = getLoaderManager();
lm.destroyLoader(LOADER_ID_LOCATION);
if (!mLocationLoaderInitialized) {
lm.initLoader(LOADER_ID_LOCATION, null, mLocationLoaderCallbacks);
mLocationLoaderInitialized = true;
} else {
lm.restartLoader(LOADER_ID_LOCATION, null, mLocationLoaderCallbacks);
}
}
private void updateConversationInfo() {
final boolean has_converstion = mStatus != null && mStatus.in_reply_to_status_id > 0;
final IStatusesListAdapter<List<ParcelableStatus>> adapter = getListAdapter();
final boolean load_not_finished = adapter.isEmpty()
|| adapter.getStatus(adapter.getCount() - 1).in_reply_to_status_id > 0;
final boolean enable = has_converstion && load_not_finished;
mInReplyToView.setVisibility(enable ? View.VISIBLE : View.GONE);
mInReplyToView.setClickable(enable);
}
private void updateUserColor() {
if (mStatus == null) return;
mProfileImageView.setBorderColor(getUserColor(getActivity(), mStatus.user_id, true));
}
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) {
((StatusFragment) f).loadPreviewImages();
}
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 DisplayMapRunnable implements Runnable {
private final ParcelableLocation mLocation;
private final ImageLoaderWrapper mLoader;
private final ImageView mView;
DisplayMapRunnable(final ParcelableLocation location, final ImageLoaderWrapper loader, final ImageView view) {
mLocation = location;
mLoader = loader;
mView = view;
}
@Override
public void run() {
final String uri = getMapStaticImageUri(mLocation.latitude, mLocation.longitude, mView);
mLoader.displayPreviewImage(uri, mView);
}
}
static class FollowInfoLoader extends AsyncTaskLoader<SingleResponse<Boolean>> {
private final ParcelableStatus status;
private final Context context;
public FollowInfoLoader(final Context context, final ParcelableStatus status) {
super(context);
this.context = context;
this.status = status;
}
@Override
public SingleResponse<Boolean> loadInBackground() {
return isAllFollowing();
}
@Override
protected void onStartLoading() {
forceLoad();
}
private SingleResponse<Boolean> isAllFollowing() {
if (status == null) return SingleResponse.getInstance();
if (status.user_id == status.account_id) return SingleResponse.getInstance(true);
final Twitter twitter = getTwitterInstance(context, status.account_id, false);
if (twitter == null) return SingleResponse.getInstance();
try {
final Relationship result = twitter.showFriendship(status.account_id, status.user_id);
if (!result.isSourceFollowingTarget()) {
SingleResponse.getInstance(false);
}
} catch (final TwitterException e) {
return SingleResponse.getInstance(e);
}
return SingleResponse.getInstance();
}
}
static class LoadConversationTask extends AsyncTask<ParcelableStatus, Void, SingleResponse<Boolean>> {
final Handler handler;
final Context context;
final StatusFragment fragment;
LoadConversationTask(final StatusFragment fragment) {
context = fragment.getActivity();
this.fragment = fragment;
handler = new Handler();
}
@Override
protected SingleResponse<Boolean> doInBackground(final ParcelableStatus... params) {
if (params == null || params.length != 1)
return new SingleResponse<>(false, null);
try {
final long account_id = params[0].account_id;
ParcelableStatus status = params[0];
while (status != null && status.in_reply_to_status_id > 0 && !isCancelled()) {
status = findStatus(context, account_id, status.in_reply_to_status_id);
if (status == null) {
break;
}
handler.post(new AddStatusRunnable(status));
}
} catch (final TwitterException e) {
return new SingleResponse<>(false, e);
}
return new SingleResponse<>(true, null);
}
@Override
protected void onCancelled() {
fragment.setProgressBarIndeterminateVisibility(false);
fragment.updateConversationInfo();
}
@Override
protected void onPostExecute(final SingleResponse<Boolean> data) {
fragment.setProgressBarIndeterminateVisibility(false);
fragment.updateConversationInfo();
if (data.getData() == null || !data.getData()) {
showErrorMessage(context, context.getString(R.string.action_getting_status), data.getException(), true);
}
}
@Override
protected void onPreExecute() {
fragment.setProgressBarIndeterminateVisibility(true);
fragment.updateConversationInfo();
}
class AddStatusRunnable implements Runnable {
final ParcelableStatus status;
AddStatusRunnable(final ParcelableStatus status) {
this.status = status;
}
@Override
public void run() {
fragment.addConversationStatus(status);
}
}
}
static class LocationInfoLoader extends AsyncTaskLoader<String> {
private final Context context;
private final ParcelableLocation location;
public LocationInfoLoader(final Context context, final ParcelableLocation location) {
super(context);
this.context = context;
this.location = location;
}
@Override
public String loadInBackground() {
if (location == null) return null;
try {
final Geocoder coder = new Geocoder(context);
final List<Address> addresses = coder.getFromLocation(location.latitude, location.longitude, 1);
if (addresses.size() == 1) {
final Address address = addresses.get(0);
final StringBuilder builder = new StringBuilder();
for (int i = 0, max_idx = address.getMaxAddressLineIndex(); i < max_idx; i++) {
builder.append(address.getAddressLine(i));
if (i != max_idx - 1) {
builder.append(", ");
}
}
return builder.toString();
}
} catch (final IOException e) {
e.printStackTrace();
}
return null;
}
@Override
protected void onStartLoading() {
forceLoad();
}
}
static class ParcelableStatusLoader extends AsyncTaskLoader<SingleResponse<ParcelableStatus>> {
private final boolean mOmitIntentExtra;
private final Bundle mExtras;
private final long mAccountId, mStatusId;
public ParcelableStatusLoader(final Context context, final boolean omitIntentExtra, final Bundle extras,
final long accountId, final long statusId) {
super(context);
mOmitIntentExtra = omitIntentExtra;
mExtras = extras;
mAccountId = accountId;
mStatusId = statusId;
}
@Override
public SingleResponse<ParcelableStatus> loadInBackground() {
if (!mOmitIntentExtra && mExtras != null) {
final ParcelableStatus cache = mExtras.getParcelable(EXTRA_STATUS);
if (cache != null) return SingleResponse.getInstance(cache);
}
try {
return SingleResponse.getInstance(findStatus(getContext(), mAccountId, mStatusId));
} catch (final TwitterException e) {
return SingleResponse.getInstance(e);
}
}
@Override
protected void onStartLoading() {
forceLoad();
}
}
}