2015-01-03 16:16:50 +01:00
|
|
|
/*
|
2015-11-30 06:32:05 +01:00
|
|
|
* Twidere - Twitter client for Android
|
2015-01-03 16:16:50 +01:00
|
|
|
*
|
|
|
|
* Copyright (C) 2012-2015 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.content.Context;
|
2015-11-30 06:32:05 +01:00
|
|
|
import android.content.Intent;
|
2015-01-03 16:16:50 +01:00
|
|
|
import android.content.SharedPreferences;
|
2015-11-30 06:32:05 +01:00
|
|
|
import android.graphics.Rect;
|
2015-01-03 16:16:50 +01:00
|
|
|
import android.os.Bundle;
|
2015-05-12 06:27:54 +02:00
|
|
|
import android.support.annotation.NonNull;
|
2015-01-03 16:16:50 +01:00
|
|
|
import android.support.annotation.Nullable;
|
2015-11-30 06:32:05 +01:00
|
|
|
import android.support.v4.app.FragmentActivity;
|
2015-01-03 16:16:50 +01:00
|
|
|
import android.support.v4.app.LoaderManager.LoaderCallbacks;
|
|
|
|
import android.support.v4.content.Loader;
|
2015-11-30 06:32:05 +01:00
|
|
|
import android.support.v7.widget.LinearLayoutManager;
|
|
|
|
import android.support.v7.widget.RecyclerView;
|
|
|
|
import android.support.v7.widget.RecyclerView.OnScrollListener;
|
2016-02-16 16:44:50 +01:00
|
|
|
import android.view.ContextMenu;
|
2015-05-12 06:27:54 +02:00
|
|
|
import android.view.KeyEvent;
|
2016-02-16 16:44:50 +01:00
|
|
|
import android.view.MenuInflater;
|
|
|
|
import android.view.MenuItem;
|
2015-01-03 16:16:50 +01:00
|
|
|
import android.view.View;
|
2015-11-30 06:32:05 +01:00
|
|
|
|
|
|
|
import com.squareup.otto.Subscribe;
|
2015-01-03 16:16:50 +01:00
|
|
|
|
|
|
|
import org.mariotaku.twidere.R;
|
|
|
|
import org.mariotaku.twidere.adapter.AbsActivitiesAdapter;
|
2015-12-14 03:20:55 +01:00
|
|
|
import org.mariotaku.twidere.adapter.decorator.DividerItemDecoration;
|
2016-02-03 20:27:43 +01:00
|
|
|
import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter.IndicatorPosition;
|
2016-01-28 11:50:39 +01:00
|
|
|
import org.mariotaku.twidere.annotation.ReadPositionTag;
|
2016-02-18 17:21:04 +01:00
|
|
|
import org.mariotaku.twidere.fragment.support.AbsStatusesFragment.DefaultOnLikedListener;
|
2015-11-30 06:32:05 +01:00
|
|
|
import org.mariotaku.twidere.loader.iface.IExtendedLoader;
|
2015-01-15 06:43:12 +01:00
|
|
|
import org.mariotaku.twidere.model.ParcelableActivity;
|
2015-12-14 03:20:55 +01:00
|
|
|
import org.mariotaku.twidere.model.ParcelableMedia;
|
2015-11-30 06:32:05 +01:00
|
|
|
import org.mariotaku.twidere.model.ParcelableStatus;
|
2016-02-16 16:44:50 +01:00
|
|
|
import org.mariotaku.twidere.model.message.StatusListChangedEvent;
|
2016-01-02 11:27:45 +01:00
|
|
|
import org.mariotaku.twidere.model.util.ParcelableActivityUtils;
|
2016-02-24 15:16:43 +01:00
|
|
|
import org.mariotaku.twidere.task.AbstractTask;
|
|
|
|
import org.mariotaku.twidere.task.util.TaskStarter;
|
2015-01-03 16:16:50 +01:00
|
|
|
import org.mariotaku.twidere.util.AsyncTwitterWrapper;
|
2016-02-09 09:02:08 +01:00
|
|
|
import org.mariotaku.twidere.util.IntentUtils;
|
2015-05-12 06:27:54 +02:00
|
|
|
import org.mariotaku.twidere.util.KeyboardShortcutsHandler;
|
2015-11-30 06:32:05 +01:00
|
|
|
import org.mariotaku.twidere.util.KeyboardShortcutsHandler.KeyboardShortcutCallback;
|
2016-02-16 16:44:50 +01:00
|
|
|
import org.mariotaku.twidere.util.LinkCreator;
|
2016-02-25 05:21:06 +01:00
|
|
|
import org.mariotaku.twidere.util.MenuUtils;
|
2015-05-12 06:27:54 +02:00
|
|
|
import org.mariotaku.twidere.util.RecyclerViewNavigationHelper;
|
2015-11-30 06:32:05 +01:00
|
|
|
import org.mariotaku.twidere.util.RecyclerViewUtils;
|
|
|
|
import org.mariotaku.twidere.util.Utils;
|
|
|
|
import org.mariotaku.twidere.util.imageloader.PauseRecyclerViewOnScrollListener;
|
2016-02-16 16:44:50 +01:00
|
|
|
import org.mariotaku.twidere.view.ExtendedRecyclerView;
|
2015-10-06 17:18:59 +02:00
|
|
|
import org.mariotaku.twidere.view.holder.ActivityTitleSummaryViewHolder;
|
2015-01-15 06:43:12 +01:00
|
|
|
import org.mariotaku.twidere.view.holder.GapViewHolder;
|
2015-11-30 06:32:05 +01:00
|
|
|
import org.mariotaku.twidere.view.holder.iface.IStatusViewHolder;
|
|
|
|
|
|
|
|
import java.io.File;
|
2015-12-29 13:25:16 +01:00
|
|
|
import java.util.ArrayList;
|
2015-12-13 13:17:26 +01:00
|
|
|
import java.util.Arrays;
|
2015-11-30 06:32:05 +01:00
|
|
|
import java.util.List;
|
2015-12-29 13:25:16 +01:00
|
|
|
import java.util.TimeZone;
|
2015-11-30 06:32:05 +01:00
|
|
|
|
|
|
|
import edu.tsinghua.hotmobi.HotMobiLogger;
|
2015-12-14 03:20:55 +01:00
|
|
|
import edu.tsinghua.hotmobi.model.MediaEvent;
|
2015-11-30 06:32:05 +01:00
|
|
|
import edu.tsinghua.hotmobi.model.ScrollRecord;
|
2015-12-14 03:20:55 +01:00
|
|
|
import edu.tsinghua.hotmobi.model.TimelineType;
|
2015-01-03 16:16:50 +01:00
|
|
|
|
2015-10-26 09:10:37 +01:00
|
|
|
public abstract class AbsActivitiesFragment<Data> extends AbsContentListRecyclerViewFragment<AbsActivitiesAdapter<Data>>
|
2015-11-30 06:32:05 +01:00
|
|
|
implements LoaderCallbacks<Data>, AbsActivitiesAdapter.ActivityAdapterListener, KeyboardShortcutCallback {
|
2015-01-03 16:16:50 +01:00
|
|
|
|
|
|
|
private final Object mStatusesBusCallback;
|
2015-11-30 06:32:05 +01:00
|
|
|
private final OnScrollListener mHotMobiScrollTracker = new OnScrollListener() {
|
|
|
|
|
|
|
|
public List<ScrollRecord> mRecords;
|
2015-12-29 13:25:16 +01:00
|
|
|
private long mFirstVisibleTimestamp = -1, mFirstVisibleAccountId = -1;
|
2015-11-30 06:32:05 +01:00
|
|
|
private int mFirstVisiblePosition = -1;
|
|
|
|
private int mScrollState;
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
|
|
|
|
final LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
|
|
|
|
final int firstVisiblePosition = layoutManager.findFirstVisibleItemPosition();
|
|
|
|
if (firstVisiblePosition != mFirstVisiblePosition && firstVisiblePosition >= 0) {
|
|
|
|
//noinspection unchecked
|
|
|
|
final AbsActivitiesAdapter<Data> adapter = (AbsActivitiesAdapter<Data>) recyclerView.getAdapter();
|
|
|
|
final ParcelableActivity activity = adapter.getActivity(firstVisiblePosition);
|
|
|
|
if (activity != null) {
|
2015-12-29 13:25:16 +01:00
|
|
|
final long timestamp = activity.timestamp, accountId = activity.account_id;
|
|
|
|
if (timestamp != mFirstVisibleTimestamp || accountId != mFirstVisibleAccountId) {
|
|
|
|
if (mRecords == null) mRecords = new ArrayList<>();
|
|
|
|
final long time = System.currentTimeMillis();
|
|
|
|
mRecords.add(ScrollRecord.create(timestamp, accountId, time,
|
|
|
|
TimeZone.getDefault().getOffset(time), mScrollState));
|
|
|
|
}
|
|
|
|
mFirstVisibleTimestamp = timestamp;
|
|
|
|
mFirstVisibleAccountId = accountId;
|
2015-11-30 06:32:05 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
mFirstVisiblePosition = firstVisiblePosition;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
|
|
|
|
mScrollState = newState;
|
|
|
|
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
|
|
|
|
if (mRecords != null) {
|
|
|
|
HotMobiLogger.getInstance(getActivity()).logList(mRecords, HotMobiLogger.ACCOUNT_ID_NOT_NEEDED, "scroll");
|
|
|
|
}
|
|
|
|
mRecords = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2016-01-31 14:34:49 +01:00
|
|
|
|
2015-11-30 06:32:05 +01:00
|
|
|
private final OnScrollListener mOnScrollListener = new OnScrollListener() {
|
|
|
|
@Override
|
|
|
|
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
|
|
|
|
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
|
|
|
|
final LinearLayoutManager layoutManager = getLayoutManager();
|
|
|
|
saveReadPosition(layoutManager.findFirstVisibleItemPosition());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2015-05-12 06:27:54 +02:00
|
|
|
private RecyclerViewNavigationHelper mNavigationHelper;
|
2015-11-30 06:32:05 +01:00
|
|
|
private OnScrollListener mPauseOnScrollListener;
|
|
|
|
private OnScrollListener mActiveHotMobiScrollTracker;
|
2015-01-03 16:16:50 +01:00
|
|
|
|
|
|
|
protected AbsActivitiesFragment() {
|
|
|
|
mStatusesBusCallback = createMessageBusCallback();
|
|
|
|
}
|
|
|
|
|
2015-11-30 06:32:05 +01:00
|
|
|
public abstract boolean getActivities(long[] accountIds, long[] maxIds, long[] sinceIds);
|
|
|
|
|
2015-01-15 06:43:12 +01:00
|
|
|
@Override
|
2015-11-30 06:32:05 +01:00
|
|
|
public boolean handleKeyboardShortcutSingle(@NonNull KeyboardShortcutsHandler handler, int keyCode, @NonNull KeyEvent event, int metaState) {
|
|
|
|
String action = handler.getKeyAction(CONTEXT_TAG_NAVIGATION, keyCode, event, metaState);
|
|
|
|
if (ACTION_NAVIGATION_REFRESH.equals(action)) {
|
|
|
|
triggerRefresh();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
final RecyclerView recyclerView = getRecyclerView();
|
|
|
|
final LinearLayoutManager layoutManager = getLayoutManager();
|
|
|
|
if (recyclerView == null || layoutManager == null) return false;
|
|
|
|
final View focusedChild = RecyclerViewUtils.findRecyclerViewChild(recyclerView,
|
|
|
|
layoutManager.getFocusedChild());
|
2016-02-18 17:21:04 +01:00
|
|
|
int position = RecyclerView.NO_POSITION;
|
2015-11-30 06:32:05 +01:00
|
|
|
if (focusedChild != null && focusedChild.getParent() == recyclerView) {
|
|
|
|
position = recyclerView.getChildLayoutPosition(focusedChild);
|
|
|
|
}
|
2016-02-18 17:21:04 +01:00
|
|
|
if (position != RecyclerView.NO_POSITION) {
|
2015-11-30 06:32:05 +01:00
|
|
|
final ParcelableActivity activity = getAdapter().getActivity(position);
|
|
|
|
if (activity == null) return false;
|
|
|
|
if (keyCode == KeyEvent.KEYCODE_ENTER) {
|
|
|
|
openActivity(activity);
|
|
|
|
return true;
|
|
|
|
}
|
2015-12-17 15:20:26 +01:00
|
|
|
final ParcelableStatus status = ParcelableActivity.getActivityStatus(activity);
|
2015-11-30 06:32:05 +01:00
|
|
|
if (status == null) return false;
|
|
|
|
if (action == null) {
|
|
|
|
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(activity.account_id, status.id);
|
|
|
|
} else {
|
2016-02-18 17:21:04 +01:00
|
|
|
final IStatusViewHolder holder = (IStatusViewHolder)
|
|
|
|
recyclerView.findViewHolderForLayoutPosition(position);
|
|
|
|
holder.playLikeAnimation(new DefaultOnLikedListener(twitter, status));
|
2015-11-30 06:32:05 +01:00
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return mNavigationHelper.handleKeyboardShortcutSingle(handler, keyCode, event, metaState);
|
2015-01-15 06:43:12 +01:00
|
|
|
}
|
|
|
|
|
2015-11-30 06:32:05 +01:00
|
|
|
private void openActivity(ParcelableActivity activity) {
|
2015-12-17 15:20:26 +01:00
|
|
|
final ParcelableStatus status = ParcelableActivity.getActivityStatus(activity);
|
2015-11-30 06:32:05 +01:00
|
|
|
if (status != null) {
|
|
|
|
Utils.openStatus(getContext(), status, null);
|
|
|
|
} else {
|
2015-10-06 17:18:59 +02:00
|
|
|
|
2015-11-30 06:32:05 +01:00
|
|
|
}
|
2015-10-06 17:18:59 +02:00
|
|
|
}
|
|
|
|
|
2015-05-12 06:27:54 +02:00
|
|
|
@Override
|
2015-11-30 06:32:05 +01:00
|
|
|
public boolean isKeyboardShortcutHandled(@NonNull KeyboardShortcutsHandler handler, int keyCode, @NonNull KeyEvent event, int metaState) {
|
|
|
|
String action = handler.getKeyAction(CONTEXT_TAG_NAVIGATION, keyCode, event, metaState);
|
|
|
|
if (ACTION_NAVIGATION_REFRESH.equals(action)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (action == null) {
|
|
|
|
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);
|
2015-05-12 06:27:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean handleKeyboardShortcutRepeat(@NonNull KeyboardShortcutsHandler handler, final int keyCode, final int repeatCount,
|
2015-10-07 16:09:00 +02:00
|
|
|
@NonNull final KeyEvent event, int metaState) {
|
|
|
|
return mNavigationHelper.handleKeyboardShortcutRepeat(handler, keyCode, repeatCount, event, metaState);
|
2015-05-12 06:27:54 +02:00
|
|
|
}
|
2015-01-03 16:16:50 +01:00
|
|
|
|
2015-08-16 04:50:56 +02:00
|
|
|
@Override
|
2015-11-30 06:32:05 +01:00
|
|
|
public final Loader<Data> onCreateLoader(int id, Bundle args) {
|
|
|
|
final boolean fromUser = args.getBoolean(EXTRA_FROM_USER);
|
|
|
|
args.remove(EXTRA_FROM_USER);
|
|
|
|
return onCreateStatusesLoader(getActivity(), args, fromUser);
|
2015-08-16 04:50:56 +02:00
|
|
|
}
|
|
|
|
|
2015-11-30 06:32:05 +01:00
|
|
|
@Override
|
|
|
|
public void setUserVisibleHint(boolean isVisibleToUser) {
|
|
|
|
super.setUserVisibleHint(isVisibleToUser);
|
|
|
|
|
|
|
|
if (isVisibleToUser) {
|
|
|
|
final LinearLayoutManager layoutManager = getLayoutManager();
|
|
|
|
if (layoutManager != null) {
|
|
|
|
saveReadPosition(layoutManager.findFirstVisibleItemPosition());
|
|
|
|
}
|
|
|
|
}
|
2015-01-03 16:16:50 +01:00
|
|
|
}
|
|
|
|
|
2015-11-30 06:32:05 +01:00
|
|
|
@Override
|
|
|
|
public final void onLoadFinished(Loader<Data> loader, Data data) {
|
|
|
|
final AbsActivitiesAdapter<Data> adapter = getAdapter();
|
|
|
|
final boolean rememberPosition = mPreferences.getBoolean(KEY_REMEMBER_POSITION, false);
|
|
|
|
final boolean readFromBottom = mPreferences.getBoolean(KEY_READ_FROM_BOTTOM, false);
|
2016-02-25 14:11:35 +01:00
|
|
|
long lastReadId;
|
2015-11-30 06:32:05 +01:00
|
|
|
final int lastVisiblePos, lastVisibleTop;
|
|
|
|
final String tag = getCurrentReadPositionTag();
|
|
|
|
final LinearLayoutManager layoutManager = getLayoutManager();
|
|
|
|
if (readFromBottom) {
|
|
|
|
lastVisiblePos = layoutManager.findLastVisibleItemPosition();
|
|
|
|
} else {
|
|
|
|
lastVisiblePos = layoutManager.findFirstVisibleItemPosition();
|
|
|
|
}
|
|
|
|
if (lastVisiblePos != RecyclerView.NO_POSITION && lastVisiblePos < adapter.getItemCount()) {
|
2016-02-10 03:04:15 +01:00
|
|
|
final int activityStartIndex = adapter.getActivityStartIndex();
|
|
|
|
final int activityEndIndex = activityStartIndex + adapter.getActivityCount();
|
2016-02-08 13:23:26 +01:00
|
|
|
final int lastItemIndex = Math.min(activityEndIndex, lastVisiblePos);
|
|
|
|
lastReadId = adapter.getTimestamp(lastItemIndex);
|
|
|
|
final View positionView = layoutManager.findViewByPosition(lastItemIndex);
|
2015-11-30 06:32:05 +01:00
|
|
|
lastVisibleTop = positionView != null ? positionView.getTop() : 0;
|
|
|
|
} else if (rememberPosition && tag != null) {
|
|
|
|
lastReadId = mReadStateManager.getPosition(tag);
|
|
|
|
lastVisibleTop = 0;
|
|
|
|
} else {
|
|
|
|
lastReadId = -1;
|
|
|
|
lastVisibleTop = 0;
|
|
|
|
}
|
|
|
|
adapter.setData(data);
|
2016-02-10 03:04:15 +01:00
|
|
|
final int activityStartIndex = adapter.getActivityStartIndex();
|
2016-02-25 14:11:35 +01:00
|
|
|
// The last activity is activityEndExclusiveIndex - 1
|
|
|
|
final int activityEndExclusiveIndex = activityStartIndex + adapter.getActivityCount();
|
|
|
|
|
2016-02-25 17:03:02 +01:00
|
|
|
if (activityEndExclusiveIndex >= 0 && rememberPosition && tag != null) {
|
2016-02-25 14:11:35 +01:00
|
|
|
final long lastItemId = adapter.getTimestamp(activityEndExclusiveIndex);
|
|
|
|
// Activity corresponds to last read timestamp was deleted, use last item timestamp
|
|
|
|
// instead
|
|
|
|
if (lastItemId > 0 && lastReadId < lastItemId) {
|
|
|
|
lastReadId = lastItemId;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
setRefreshEnabled(true);
|
2015-11-30 06:32:05 +01:00
|
|
|
if (!(loader instanceof IExtendedLoader) || ((IExtendedLoader) loader).isFromUser()) {
|
2016-02-03 20:27:43 +01:00
|
|
|
adapter.setLoadMoreSupportedPosition(hasMoreData(data) ? IndicatorPosition.END : IndicatorPosition.NONE);
|
2015-11-30 06:32:05 +01:00
|
|
|
int pos = -1;
|
2016-02-25 14:11:35 +01:00
|
|
|
for (int i = activityStartIndex; i < activityEndExclusiveIndex; i++) {
|
|
|
|
if (lastReadId != -1 && adapter.getTimestamp(i) <= lastReadId) {
|
2015-12-29 13:25:16 +01:00
|
|
|
pos = i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (pos != -1 && adapter.isActivity(pos) && (readFromBottom || lastVisiblePos != 0)) {
|
|
|
|
if (layoutManager.getHeight() == 0) {
|
|
|
|
// RecyclerView has not currently laid out, ignore padding.
|
|
|
|
layoutManager.scrollToPositionWithOffset(pos, lastVisibleTop);
|
|
|
|
} else {
|
|
|
|
layoutManager.scrollToPositionWithOffset(pos, lastVisibleTop - layoutManager.getPaddingTop());
|
|
|
|
}
|
|
|
|
}
|
2015-11-30 06:32:05 +01:00
|
|
|
}
|
|
|
|
if (loader instanceof IExtendedLoader) {
|
|
|
|
((IExtendedLoader) loader).setFromUser(false);
|
|
|
|
}
|
|
|
|
onLoadingFinished();
|
|
|
|
}
|
2015-01-03 16:16:50 +01:00
|
|
|
|
|
|
|
@Override
|
2015-11-30 06:32:05 +01:00
|
|
|
public void onLoaderReset(Loader<Data> loader) {
|
|
|
|
if (loader instanceof IExtendedLoader) {
|
|
|
|
((IExtendedLoader) loader).setFromUser(false);
|
|
|
|
}
|
2015-01-03 16:16:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2015-11-30 06:32:05 +01:00
|
|
|
public void onGapClick(GapViewHolder holder, int position) {
|
2015-05-12 06:27:54 +02:00
|
|
|
final AbsActivitiesAdapter<Data> adapter = getAdapter();
|
2015-11-30 06:32:05 +01:00
|
|
|
final ParcelableActivity activity = adapter.getActivity(position);
|
|
|
|
final long[] accountIds = {activity.account_id};
|
|
|
|
final long[] maxIds = {activity.min_position};
|
2016-01-04 12:07:58 +01:00
|
|
|
getActivities(accountIds, maxIds, null);
|
2015-01-03 16:16:50 +01:00
|
|
|
}
|
|
|
|
|
2015-12-14 03:20:55 +01:00
|
|
|
@Override
|
|
|
|
public void onMediaClick(IStatusViewHolder holder, View view, ParcelableMedia media, int position) {
|
|
|
|
final AbsActivitiesAdapter<Data> adapter = getAdapter();
|
2015-12-17 15:20:26 +01:00
|
|
|
final ParcelableStatus status = ParcelableActivity.getActivityStatus(adapter.getActivity(position));
|
2015-12-14 03:20:55 +01:00
|
|
|
if (status == null) return;
|
2016-02-13 15:43:20 +01:00
|
|
|
IntentUtils.openMedia(getActivity(), status, media, null, true);
|
2015-12-14 03:20:55 +01:00
|
|
|
// BEGIN HotMobi
|
|
|
|
final MediaEvent event = MediaEvent.create(getActivity(), status, media,
|
|
|
|
getTimelineType(), adapter.isMediaPreviewEnabled());
|
|
|
|
HotMobiLogger.getInstance(getActivity()).log(status.account_id, event);
|
|
|
|
// END HotMobi
|
|
|
|
}
|
|
|
|
|
|
|
|
@NonNull
|
2016-01-08 09:34:28 +01:00
|
|
|
@TimelineType
|
|
|
|
protected abstract String getTimelineType();
|
2015-11-30 06:32:05 +01:00
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onStatusActionClick(IStatusViewHolder holder, int id, int position) {
|
|
|
|
final ParcelableStatus status = getActivityStatus(position);
|
|
|
|
if (status == null) return;
|
|
|
|
final FragmentActivity activity = getActivity();
|
|
|
|
switch (id) {
|
2016-03-03 12:53:02 +01:00
|
|
|
case R.id.reply: {
|
2015-11-30 06:32:05 +01:00
|
|
|
final Intent intent = new Intent(INTENT_ACTION_REPLY);
|
|
|
|
intent.setPackage(activity.getPackageName());
|
|
|
|
intent.putExtra(EXTRA_STATUS, status);
|
|
|
|
activity.startActivity(intent);
|
|
|
|
break;
|
|
|
|
}
|
2016-03-03 12:53:02 +01:00
|
|
|
case R.id.retweet: {
|
2015-11-30 06:32:05 +01:00
|
|
|
RetweetQuoteDialogFragment.show(getFragmentManager(), status);
|
|
|
|
break;
|
|
|
|
}
|
2016-03-03 12:53:02 +01:00
|
|
|
case R.id.favorite: {
|
2015-11-30 06:32:05 +01:00
|
|
|
final AsyncTwitterWrapper twitter = mTwitterWrapper;
|
|
|
|
if (twitter == null) return;
|
|
|
|
if (status.is_favorite) {
|
|
|
|
twitter.destroyFavoriteAsync(status.account_id, status.id);
|
|
|
|
} else {
|
2016-02-18 17:21:04 +01:00
|
|
|
holder.playLikeAnimation(new DefaultOnLikedListener(twitter, status));
|
2015-11-30 06:32:05 +01:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onActivityClick(ActivityTitleSummaryViewHolder holder, int position) {
|
2015-12-13 13:17:26 +01:00
|
|
|
final ParcelableActivity activity = getAdapter().getActivity(position);
|
|
|
|
if (activity == null) return;
|
2016-02-09 09:02:08 +01:00
|
|
|
IntentUtils.openUsers(getActivity(), Arrays.asList(ParcelableActivityUtils.getAfterFilteredSources(activity)));
|
2015-11-30 06:32:05 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onStatusMenuClick(IStatusViewHolder holder, View menuView, int position) {
|
2016-02-16 16:44:50 +01:00
|
|
|
if (getActivity() == null) return;
|
|
|
|
final LinearLayoutManager lm = getLayoutManager();
|
|
|
|
final View view = lm.findViewByPosition(position);
|
|
|
|
if (view == null || lm.getItemViewType(view) != AbsActivitiesAdapter.ITEM_VIEW_TYPE_STATUS) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
getRecyclerView().showContextMenuForChild(view);
|
2015-11-30 06:32:05 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onStatusClick(IStatusViewHolder holder, int position) {
|
|
|
|
final ParcelableStatus status = getActivityStatus(position);
|
|
|
|
Utils.openStatus(getContext(), status, null);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Nullable
|
|
|
|
private ParcelableStatus getActivityStatus(int position) {
|
|
|
|
final AbsActivitiesAdapter<Data> adapter = getAdapter();
|
2016-02-16 16:44:50 +01:00
|
|
|
final ParcelableActivity activity = adapter.getActivity(position);
|
|
|
|
if (activity == null) return null;
|
|
|
|
return ParcelableActivity.getActivityStatus(activity);
|
2015-11-30 06:32:05 +01:00
|
|
|
}
|
|
|
|
|
2015-01-03 16:16:50 +01:00
|
|
|
@Override
|
|
|
|
public void onStart() {
|
|
|
|
super.onStart();
|
2015-11-30 06:32:05 +01:00
|
|
|
final RecyclerView recyclerView = getRecyclerView();
|
|
|
|
recyclerView.addOnScrollListener(mOnScrollListener);
|
|
|
|
recyclerView.addOnScrollListener(mPauseOnScrollListener);
|
2016-02-24 15:16:43 +01:00
|
|
|
final AbstractTask<Object, Boolean, RecyclerView> task = new AbstractTask<Object, Boolean, RecyclerView>() {
|
2015-11-30 06:32:05 +01:00
|
|
|
@Override
|
2016-02-24 15:16:43 +01:00
|
|
|
public Boolean doLongOperation(Object params) {
|
2015-11-30 06:32:05 +01:00
|
|
|
final Context context = getContext();
|
2016-02-25 05:21:06 +01:00
|
|
|
if (context == null) return false;
|
2015-11-30 06:32:05 +01:00
|
|
|
final SharedPreferences prefs = context.getSharedPreferences(SHARED_PREFERENCES_NAME,
|
|
|
|
Context.MODE_PRIVATE);
|
|
|
|
if (!prefs.getBoolean(KEY_USAGE_STATISTICS, false)) return false;
|
|
|
|
final File logFile = HotMobiLogger.getLogFile(context, HotMobiLogger.ACCOUNT_ID_NOT_NEEDED, "scroll");
|
|
|
|
return logFile.length() < 131072;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2016-02-24 15:16:43 +01:00
|
|
|
public void afterExecute(RecyclerView recyclerView, Boolean result) {
|
2015-11-30 06:32:05 +01:00
|
|
|
if (result) {
|
|
|
|
recyclerView.addOnScrollListener(mActiveHotMobiScrollTracker = mHotMobiScrollTracker);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
task.setResultHandler(recyclerView);
|
2016-02-24 15:16:43 +01:00
|
|
|
TaskStarter.execute(task);
|
2015-10-06 18:45:39 +02:00
|
|
|
mBus.register(mStatusesBusCallback);
|
2015-01-03 16:16:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onStop() {
|
2015-10-06 18:45:39 +02:00
|
|
|
mBus.unregister(mStatusesBusCallback);
|
2015-11-30 06:32:05 +01:00
|
|
|
final RecyclerView recyclerView = getRecyclerView();
|
|
|
|
if (mActiveHotMobiScrollTracker != null) {
|
|
|
|
recyclerView.removeOnScrollListener(mActiveHotMobiScrollTracker);
|
|
|
|
}
|
|
|
|
mActiveHotMobiScrollTracker = null;
|
|
|
|
recyclerView.removeOnScrollListener(mPauseOnScrollListener);
|
|
|
|
recyclerView.removeOnScrollListener(mOnScrollListener);
|
2015-01-03 16:16:50 +01:00
|
|
|
super.onStop();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2015-11-30 06:32:05 +01:00
|
|
|
public void onDestroyView() {
|
|
|
|
super.onDestroyView();
|
2015-01-03 16:16:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2015-11-30 06:32:05 +01:00
|
|
|
public final boolean scrollToStart() {
|
|
|
|
final boolean result = super.scrollToStart();
|
|
|
|
if (result) {
|
|
|
|
saveReadPosition(0);
|
|
|
|
}
|
|
|
|
return result;
|
2015-01-03 16:16:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2015-11-30 06:32:05 +01:00
|
|
|
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
|
|
|
super.onActivityCreated(savedInstanceState);
|
|
|
|
final AbsActivitiesAdapter<Data> adapter = getAdapter();
|
|
|
|
final RecyclerView recyclerView = getRecyclerView();
|
|
|
|
final LinearLayoutManager layoutManager = getLayoutManager();
|
2016-02-16 16:44:50 +01:00
|
|
|
adapter.setListener(this);
|
|
|
|
registerForContextMenu(recyclerView);
|
2015-11-30 06:32:05 +01:00
|
|
|
mNavigationHelper = new RecyclerViewNavigationHelper(recyclerView, layoutManager,
|
|
|
|
adapter, this);
|
|
|
|
mPauseOnScrollListener = new PauseRecyclerViewOnScrollListener(adapter.getMediaLoader().getImageLoader(), false, true);
|
2015-01-03 16:16:50 +01:00
|
|
|
|
2015-11-30 06:32:05 +01:00
|
|
|
final Bundle loaderArgs = new Bundle(getArguments());
|
|
|
|
loaderArgs.putBoolean(EXTRA_FROM_USER, true);
|
|
|
|
getLoaderManager().initLoader(0, loaderArgs, this);
|
|
|
|
showProgress();
|
|
|
|
}
|
|
|
|
|
|
|
|
protected Object createMessageBusCallback() {
|
|
|
|
return new StatusesBusCallback();
|
2015-01-03 16:16:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
protected abstract long[] getAccountIds();
|
|
|
|
|
|
|
|
protected Data getAdapterData() {
|
2015-11-30 06:32:05 +01:00
|
|
|
final AbsActivitiesAdapter<Data> adapter = getAdapter();
|
|
|
|
return adapter.getData();
|
2015-01-03 16:16:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
protected void setAdapterData(Data data) {
|
2015-11-30 06:32:05 +01:00
|
|
|
final AbsActivitiesAdapter<Data> adapter = getAdapter();
|
|
|
|
adapter.setData(data);
|
2015-01-03 16:16:50 +01:00
|
|
|
}
|
|
|
|
|
2016-01-28 11:50:39 +01:00
|
|
|
@ReadPositionTag
|
|
|
|
@Nullable
|
2015-11-30 06:32:05 +01:00
|
|
|
protected String getReadPositionTag() {
|
|
|
|
return null;
|
2015-01-03 16:16:50 +01:00
|
|
|
}
|
|
|
|
|
2015-11-30 06:32:05 +01:00
|
|
|
protected abstract boolean hasMoreData(Data data);
|
2015-11-25 03:30:37 +01:00
|
|
|
|
2015-11-30 06:32:05 +01:00
|
|
|
protected abstract Loader<Data> onCreateStatusesLoader(final Context context, final Bundle args,
|
|
|
|
final boolean fromUser);
|
|
|
|
|
|
|
|
protected abstract void onLoadingFinished();
|
|
|
|
|
|
|
|
protected void saveReadPosition(int position) {
|
|
|
|
final String readPositionTag = getReadPositionTagWithAccounts();
|
|
|
|
if (readPositionTag == null) return;
|
|
|
|
if (position == RecyclerView.NO_POSITION) return;
|
|
|
|
final AbsActivitiesAdapter<Data> adapter = getAdapter();
|
2015-12-17 15:20:26 +01:00
|
|
|
final ParcelableActivity activity = adapter.getActivity(position);
|
|
|
|
if (activity == null) return;
|
2016-01-01 03:49:00 +01:00
|
|
|
if (mReadStateManager.setPosition(readPositionTag, activity.timestamp)) {
|
|
|
|
mTwitterWrapper.setActivitiesAboutMeUnreadAsync(getAccountIds(), activity.timestamp);
|
|
|
|
}
|
2015-12-17 15:20:26 +01:00
|
|
|
mReadStateManager.setPosition(getCurrentReadPositionTag(), activity.timestamp, true);
|
2015-11-25 03:30:37 +01:00
|
|
|
}
|
|
|
|
|
2015-11-30 06:32:05 +01:00
|
|
|
@NonNull
|
2015-11-25 03:30:37 +01:00
|
|
|
@Override
|
2015-11-30 06:32:05 +01:00
|
|
|
protected Rect getExtraContentPadding() {
|
|
|
|
final int paddingVertical = getResources().getDimensionPixelSize(R.dimen.element_spacing_small);
|
|
|
|
return new Rect(0, paddingVertical, 0, paddingVertical);
|
|
|
|
}
|
2015-11-25 03:30:37 +01:00
|
|
|
|
2015-12-14 03:20:55 +01:00
|
|
|
@Override
|
|
|
|
protected void setupRecyclerView(Context context, boolean compact) {
|
|
|
|
if (compact) {
|
|
|
|
super.setupRecyclerView(context, true);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
final RecyclerView recyclerView = getRecyclerView();
|
|
|
|
final AbsActivitiesAdapter<Data> adapter = getAdapter();
|
2016-01-02 16:10:53 +01:00
|
|
|
// Dividers are drawn on bottom of view
|
2015-12-14 03:20:55 +01:00
|
|
|
recyclerView.addItemDecoration(new DividerItemDecoration(context, getLayoutManager().getOrientation()) {
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected boolean isDividerEnabled(int childPos) {
|
2016-02-25 10:42:04 +01:00
|
|
|
// Don't draw for last item
|
2015-12-14 03:20:55 +01:00
|
|
|
if (childPos == RecyclerView.NO_POSITION || childPos == adapter.getItemCount() - 1) {
|
|
|
|
return false;
|
|
|
|
}
|
2016-01-02 16:10:53 +01:00
|
|
|
final int itemViewType = adapter.getItemViewType(childPos);
|
2016-02-25 10:42:04 +01:00
|
|
|
// Draw only if current item and next item is TITLE_SUMMARY
|
2016-01-02 16:10:53 +01:00
|
|
|
if (shouldUseDividerFor(itemViewType)) {
|
2015-12-17 15:20:26 +01:00
|
|
|
if (shouldUseDividerFor(adapter.getItemViewType(childPos + 1))) {
|
2015-12-14 03:20:55 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
2015-12-17 15:20:26 +01:00
|
|
|
|
|
|
|
private boolean shouldUseDividerFor(int itemViewType) {
|
2016-02-27 07:52:16 +01:00
|
|
|
switch (itemViewType) {
|
|
|
|
case AbsActivitiesAdapter.ITEM_VIEW_TYPE_TITLE_SUMMARY:
|
|
|
|
case AbsActivitiesAdapter.ITEM_VIEW_TYPE_GAP:
|
|
|
|
return true;
|
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
2015-12-17 15:20:26 +01:00
|
|
|
}
|
2015-12-14 03:20:55 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2016-02-16 16:44:50 +01:00
|
|
|
@Override
|
|
|
|
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
|
2016-02-25 05:21:06 +01:00
|
|
|
if (!getUserVisibleHint()) return;
|
2016-02-16 16:44:50 +01:00
|
|
|
final AbsActivitiesAdapter<Data> adapter = getAdapter();
|
|
|
|
final MenuInflater inflater = new MenuInflater(getContext());
|
|
|
|
final ExtendedRecyclerView.ContextMenuInfo contextMenuInfo =
|
|
|
|
(ExtendedRecyclerView.ContextMenuInfo) menuInfo;
|
|
|
|
final int position = contextMenuInfo.getPosition();
|
|
|
|
switch (adapter.getItemViewType(position)) {
|
|
|
|
case AbsActivitiesAdapter.ITEM_VIEW_TYPE_STATUS: {
|
|
|
|
final ParcelableStatus status = getActivityStatus(position);
|
|
|
|
if (status == null) return;
|
|
|
|
inflater.inflate(R.menu.action_status, menu);
|
2016-02-25 05:21:06 +01:00
|
|
|
MenuUtils.setupForStatus(getContext(), mPreferences, menu, status, mUserColorNameManager,
|
|
|
|
mTwitterWrapper);
|
2016-02-16 16:44:50 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean onContextItemSelected(MenuItem item) {
|
2016-02-25 05:21:06 +01:00
|
|
|
if (!getUserVisibleHint()) return false;
|
2016-02-16 16:44:50 +01:00
|
|
|
final AbsActivitiesAdapter<Data> adapter = getAdapter();
|
|
|
|
final ExtendedRecyclerView.ContextMenuInfo contextMenuInfo =
|
|
|
|
(ExtendedRecyclerView.ContextMenuInfo) item.getMenuInfo();
|
|
|
|
final int position = contextMenuInfo.getPosition();
|
|
|
|
|
|
|
|
switch (adapter.getItemViewType(position)) {
|
|
|
|
case AbsActivitiesAdapter.ITEM_VIEW_TYPE_STATUS: {
|
|
|
|
final ParcelableStatus status = getActivityStatus(position);
|
|
|
|
if (status == null) return false;
|
|
|
|
if (item.getItemId() == R.id.share) {
|
|
|
|
final Intent shareIntent = Utils.createStatusShareIntent(getActivity(), status);
|
|
|
|
final Intent chooser = Intent.createChooser(shareIntent, getString(R.string.share_status));
|
|
|
|
Utils.addCopyLinkIntent(getContext(), chooser, LinkCreator.getTwitterStatusLink(status));
|
|
|
|
startActivity(chooser);
|
|
|
|
return true;
|
|
|
|
}
|
2016-02-25 05:21:06 +01:00
|
|
|
return MenuUtils.handleStatusClick(getActivity(), this, getFragmentManager(),
|
2016-02-16 16:44:50 +01:00
|
|
|
mUserColorNameManager, mTwitterWrapper, status, item);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2015-11-30 06:32:05 +01:00
|
|
|
private String getCurrentReadPositionTag() {
|
|
|
|
final String tag = getReadPositionTagWithAccounts();
|
|
|
|
if (tag == null) return null;
|
|
|
|
return tag + "_current";
|
|
|
|
}
|
|
|
|
|
|
|
|
private String getReadPositionTagWithAccounts() {
|
|
|
|
return Utils.getReadPositionTagWithAccounts(getReadPositionTag(), getAccountIds());
|
2015-11-25 03:30:37 +01:00
|
|
|
}
|
|
|
|
|
2015-01-03 16:16:50 +01:00
|
|
|
protected final class StatusesBusCallback {
|
|
|
|
|
|
|
|
protected StatusesBusCallback() {
|
|
|
|
}
|
|
|
|
|
2015-11-30 06:32:05 +01:00
|
|
|
@Subscribe
|
|
|
|
public void notifyStatusListChanged(StatusListChangedEvent event) {
|
|
|
|
final AbsActivitiesAdapter<Data> adapter = getAdapter();
|
|
|
|
adapter.notifyDataSetChanged();
|
|
|
|
}
|
2015-01-03 16:16:50 +01:00
|
|
|
|
|
|
|
}
|
|
|
|
}
|