/* * Copyright 2015 Hippo Seven * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.hippo.nimingban.ui.fragment; import android.annotation.SuppressLint; import android.app.Dialog; import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.net.Uri; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v7.app.AlertDialog; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; import android.text.Spannable; import android.text.SpannableString; import android.text.Spanned; import android.text.TextUtils; import android.text.style.ClickableSpan; import android.text.style.ForegroundColorSpan; import android.text.style.StyleSpan; import android.text.style.URLSpan; import android.util.Log; import android.util.Pair; import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import android.widget.Toast; import com.hippo.easyrecyclerview.EasyRecyclerView; import com.hippo.effect.ViewTransition; import com.hippo.nimingban.Constants; import com.hippo.nimingban.NMBApplication; import com.hippo.nimingban.R; import com.hippo.nimingban.client.NMBClient; import com.hippo.nimingban.client.NMBRequest; import com.hippo.nimingban.client.NMBUrl; import com.hippo.nimingban.client.ReferenceSpan; import com.hippo.nimingban.client.ac.NMBUriParser; import com.hippo.nimingban.client.ac.data.ACReference; import com.hippo.nimingban.client.data.Post; import com.hippo.nimingban.client.data.Reply; import com.hippo.nimingban.client.data.Site; import com.hippo.nimingban.ui.GalleryActivity2; import com.hippo.nimingban.ui.PostActivity; import com.hippo.nimingban.util.OpenUrlHelper; import com.hippo.nimingban.util.ReadableTime; import com.hippo.nimingban.util.Settings; import com.hippo.nimingban.widget.ContentLayout; import com.hippo.nimingban.widget.LinkifyTextView; import com.hippo.nimingban.widget.LoadImageView; import com.hippo.ripple.Ripple; import com.hippo.util.ActivityHelper; import com.hippo.util.DrawableManager; import com.hippo.util.ExceptionUtils; import com.hippo.util.TextUtils2; import com.hippo.widget.Slider; import com.hippo.yorozuya.LayoutUtils; import com.hippo.yorozuya.MathUtils; import com.hippo.yorozuya.Messenger; import com.hippo.yorozuya.ResourcesUtils; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.LinkedList; import java.util.List; public class PostFragment extends BaseFragment implements Messenger.Receiver, EasyRecyclerView.OnItemClickListener, EasyRecyclerView.OnItemLongClickListener, Toolbar.OnMenuItemClickListener { public static final String ACTION_POST = "com.hippo.nimingban.ui.PostActivity.action.POST"; public static final String ACTION_SITE_ID = "com.hippo.nimingban.ui.PostActivity.action.SITE_ID"; public static final String ACTION_SITE_REPLY_ID = "com.hippo.nimingban.ui.PostActivity.action.SITE_REPLY_ID"; public static final String KEY_ACTION = "action"; public static final String KEY_POST = "post"; public static final String KEY_SITE = "site"; public static final String KEY_ID = "id"; public static final String KEY_DATA = "data"; /** * Intent.ACTION_PROCESS_TEXT */ public static final String ACTION_PROCESS_TEXT = "android.intent.action.PROCESS_TEXT"; /** * Intent.EXTRA_PROCESS_TEXT */ public static final String EXTRA_PROCESS_TEXT = "android.intent.extra.PROCESS_TEXT"; /** * Intent.EXTRA_PROCESS_TEXT_READONLY */ public static final String EXTRA_PROCESS_TEXT_READONLY = "android.intent.extra.PROCESS_TEXT_READONLY"; private NMBClient mNMBClient; private Toolbar mToolbar; private ContentLayout mContentLayout; private EasyRecyclerView mRecyclerView; private ReplyHelper mReplyHelper; private ReplyAdapter mReplyAdapter; private RecyclerView.OnScrollListener mOnScrollListener; private NMBRequest mNMBRequest; private Site mSite; private String mId; private String mReplyId; private CharSequence mPostUser; private int mOpColor; private int mPageSize = -1; private List<WeakReference<ReplyHolder>> mHolderList = new LinkedList<>(); private Callback mCallback; public void setCallback(Callback callback) { mCallback = callback; } private boolean handleArgs(Bundle args) { if (args == null) { return false; } String action = args.getString(KEY_ACTION); if (ACTION_POST.equals(action)) { Post post = args.getParcelable(KEY_POST); if (post != null) { mSite = post.getNMBSite(); mId = post.getNMBId(); mPostUser = post.getNMBDisplayUsername(); return true; } } else if (ACTION_SITE_ID.equals(action)) { int site = args.getInt(KEY_SITE, -1); String id = args.getString(KEY_ID); if (Site.isValid(site) && id != null) { mSite = Site.fromId(site); mId = id; return true; } } else if (ACTION_SITE_REPLY_ID.equals(action)) { int site = args.getInt(KEY_SITE, -1); String id = args.getString(KEY_ID); if (Site.isValid(site) && id != null) { mSite = Site.fromId(site); mReplyId = id; return true; } } else if (Intent.ACTION_VIEW.equals(action)) { NMBUriParser.PostResult result = NMBUriParser.parsePostUri((Uri) args.getParcelable(KEY_DATA)); mSite = result.site; mId = result.id; if (mSite != null && mId != null) { return true; } } return false; } @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); mNMBClient = NMBApplication.getNMBClient(getContext()); if (!handleArgs(getArguments())) { getFragmentHost().finishFragment(this); return; } } @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { ViewGroup view = (ViewGroup) inflater.inflate(R.layout.activity_toolbar, container, false); ViewGroup contentPanel = (ViewGroup) view.findViewById(R.id.content_panel); ViewGroup contentView = (ViewGroup) inflater.inflate(R.layout.fragment_post, contentPanel, true); mToolbar = (Toolbar) view.findViewById(R.id.toolbar); if (mId != null) { mToolbar.setTitle(mSite.getPostTitle(getContext(), mId)); } else { mToolbar.setTitle(getString(R.string.thread)); } mToolbar.setNavigationIcon(DrawableManager.getDrawable(getContext(), R.drawable.v_arrow_left_dark_x24)); mToolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { getFragmentHost().finishFragment(PostFragment.this); } }); mToolbar.inflateMenu(R.menu.activity_post); mToolbar.setOnMenuItemClickListener(this); mContentLayout = (ContentLayout) contentView.findViewById(R.id.content_layout); mRecyclerView = mContentLayout.getRecyclerView(); mReplyHelper = new ReplyHelper(); mReplyHelper.setEmptyString(getString(R.string.not_found)); mContentLayout.setHelper(mReplyHelper); if (Settings.getFastScroller()) { mContentLayout.showFastScroll(); } else { mContentLayout.hideFastScroll(); } mReplyAdapter = new ReplyAdapter(); mRecyclerView.setAdapter(mReplyAdapter); mRecyclerView.setSelector(Ripple.generateRippleDrawable( getContext(), ResourcesUtils.getAttrBoolean(getContext(), R.attr.dark))); mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); mRecyclerView.setOnItemClickListener(this); mRecyclerView.setOnItemLongClickListener(this); mRecyclerView.hasFixedSize(); mOnScrollListener = new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { if (RecyclerView.SCROLL_STATE_DRAGGING == newState) { pauseHolders(); } else if (RecyclerView.SCROLL_STATE_IDLE == newState) { resumeHolders(); } } }; mRecyclerView.addOnScrollListener(mOnScrollListener); mOpColor = getResources().getColor(R.color.colorAccent); // Refresh mReplyHelper.firstRefresh(); Messenger.getInstance().register(Constants.MESSENGER_ID_REPLY, this); Messenger.getInstance().register(Constants.MESSENGER_ID_FAST_SCROLLER, this); return view; } @Override public void onDestroyView() { super.onDestroyView(); Messenger.getInstance().unregister(Constants.MESSENGER_ID_REPLY, this); Messenger.getInstance().unregister(Constants.MESSENGER_ID_FAST_SCROLLER, this); if (mNMBRequest != null) { mNMBRequest.cancel(); mNMBRequest = null; } mRecyclerView.removeOnScrollListener(mOnScrollListener); for (WeakReference<ReplyHolder> ref : mHolderList) { ReplyHolder holder = ref.get(); if (holder != null) { holder.thumb.unload(); } } mHolderList.clear(); } private void resumeHolders() { Iterator<WeakReference<ReplyHolder>> iterator = mHolderList.iterator(); while (iterator.hasNext()) { ReplyHolder holder = iterator.next().get(); if (holder != null) { // Only resume attached view holder if (holder.itemView.getParent() != null) { holder.thumb.start(); } } else { iterator.remove(); } } } @Override public void onResume() { super.onResume(); resumeHolders(); } private void pauseHolders() { Iterator<WeakReference<ReplyHolder>> iterator = mHolderList.iterator(); while (iterator.hasNext()) { ReplyHolder holder = iterator.next().get(); if (holder != null) { holder.thumb.stop(); } else { iterator.remove(); } } } @Override public void onPause() { super.onPause(); pauseHolders(); } @Override public void onReceive(int id, Object obj) { if (Constants.MESSENGER_ID_REPLY == id) { if (mId.equals(obj)) { int currentPage = mReplyHelper.getPageForBottom(); int pages = mReplyHelper.getPages(); if (currentPage >= 0 && currentPage + 1 == pages) { // It is the last page, refresh it mReplyHelper.doGetData(ContentLayout.ContentHelper.TYPE_REFRESH_PAGE, currentPage, ContentLayout.ContentHelper.REFRESH_TYPE_FOOTER); } } } else if (Constants.MESSENGER_ID_FAST_SCROLLER == id) { if (obj instanceof Boolean) { if ((Boolean) obj) { mContentLayout.showFastScroll(); } else { mContentLayout.hideFastScroll(); } } } } @Override public boolean onMenuItemClick(MenuItem item) { switch (item.getItemId()) { case R.id.action_go_to: int pages = mReplyHelper.getPages(); if (pages > 0 && mReplyHelper.canGoTo()) { GoToDialogHelper helper = new GoToDialogHelper(pages, mReplyHelper.getPageForTop()); AlertDialog dialog = new AlertDialog.Builder(getContext()).setTitle(R.string.go_to) .setView(helper.getView()) .setPositiveButton(android.R.string.ok, null) .create(); dialog.show(); helper.setPositiveButtonClickListener(dialog); } return true; case R.id.action_reply: mCallback.reply(mSite, mId, null, false); return true; case R.id.action_add_feed: NMBRequest request1 = new NMBRequest(); request1.setSite(mSite); request1.setMethod(NMBClient.METHOD_ADD_FEED); request1.setArgs(mSite.getUserId(getContext()), mId); request1.setCallback(new FeedListener(getContext(), true)); mNMBClient.execute(request1); return true; case R.id.action_remove_feed: NMBRequest request2 = new NMBRequest(); request2.setSite(mSite); request2.setMethod(NMBClient.METHOD_DEL_FEED); request2.setArgs(mSite.getUserId(getContext()), mId); request2.setCallback(new FeedListener(getContext(), false)); mNMBClient.execute(request2); return true; case R.id.action_share: ActivityHelper.share(getActivity(), NMBUrl.getBrowsablePostUrl(mSite, mId, 0)); return true; case R.id.action_open_in_other_app: OpenUrlHelper.openUrl(getActivity(), NMBUrl.getBrowsablePostUrl(mSite, mId, 0), false); default: return super.onOptionsItemSelected(item); } } private class GoToDialogHelper implements View.OnClickListener, DialogInterface.OnDismissListener { private int mPages; private View mView; private Slider mSlider; private Dialog mDialog; @SuppressLint("InflateParams") private GoToDialogHelper(int pages, int currentPage) { mPages = pages; mView = getActivity().getLayoutInflater().inflate(R.layout.dialog_go_to, null); ((TextView) mView.findViewById(R.id.start)).setText("1"); ((TextView) mView.findViewById(R.id.end)).setText(Integer.toString(pages)); mSlider = (Slider) mView.findViewById(R.id.slider); mSlider.setRange(1, pages); mSlider.setProgress(currentPage + 1); } public View getView() { return mView; } public void setPositiveButtonClickListener(AlertDialog dialog) { mDialog = dialog; dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(this); dialog.setOnDismissListener(this); } @Override public void onClick(View v) { int page = mSlider.getProgress() - 1; if (page >= 0 && page < mPages) { mReplyHelper.goTo(page); if (mDialog != null) { mDialog.dismiss(); mDialog = null; } } else { Toast.makeText(getContext(), R.string.go_to_error_out_of_range, Toast.LENGTH_SHORT).show(); } } @Override public void onDismiss(DialogInterface dialog) { mDialog = null; } } private void handleURLSpan(URLSpan urlSpan) { OpenUrlHelper.openUrl(getContext(), urlSpan.getURL(), true); } private final class ReferenceDialogHelper implements AlertDialog.OnDismissListener, NMBClient.Callback<ACReference>, View.OnClickListener { private Site mSite; private String mId; private View mView; private ViewTransition mViewTransition; public TextView mLeftText; public TextView mCenterText; public TextView mRightText; private LinkifyTextView mContent; private LoadImageView mThumb; private View mButton; private AlertDialog mDialog; private NMBRequest mRequest; private Reply mReply; @SuppressLint("InflateParams") public ReferenceDialogHelper(Site site, String id) { mSite = site; mId = id; mView = getActivity().getLayoutInflater().inflate(R.layout.dialog_reference, null); View progress = mView.findViewById(R.id.progress_view); View reference = mView.findViewById(R.id.reference); mViewTransition = new ViewTransition(progress, reference); mLeftText = (TextView) reference.findViewById(R.id.left_text); mCenterText = (TextView) reference.findViewById(R.id.center_text); mRightText = (TextView) reference.findViewById(R.id.right_text); mContent = (LinkifyTextView) reference.findViewById(R.id.content); mThumb = (LoadImageView) reference.findViewById(R.id.thumb); mButton = reference.findViewById(R.id.button); mContent.setOnClickListener(this); mThumb.setOnClickListener(this); Ripple.addRipple(mButton, ResourcesUtils.getAttrBoolean(getContext(), R.attr.dark)); } @Override public void onClick(View v) { if (v == mContent) { ClickableSpan span = mContent.getCurrentSpan(); mContent.clearCurrentSpan(); if (span instanceof URLSpan) { handleURLSpan((URLSpan) span); } else if (span instanceof ReferenceSpan) { handleReferenceSpan((ReferenceSpan) span); } } else if (v == mThumb) { String key; String image; if (mReply != null && !TextUtils.isEmpty(key = mReply.getNMBImageKey()) && !TextUtils.isEmpty(image = mReply.getNMBImageUrl())) { Intent intent = new Intent(getActivity(), GalleryActivity2.class); intent.setAction(GalleryActivity2.ACTION_SINGLE_IMAGE); intent.putExtra(GalleryActivity2.KEY_SITE, mSite.getId()); intent.putExtra(GalleryActivity2.KEY_ID, mReply.getNMBId()); intent.putExtra(GalleryActivity2.KEY_KEY, key); intent.putExtra(GalleryActivity2.KEY_IMAGE, image); startActivity(intent); } } } public void setDialog(AlertDialog dialog) { mDialog = dialog; } public View getView() { return mView; } public void request() { // Try to find in data list first ReplyHelper replyHelper = mReplyHelper; for (int i = 0, n = replyHelper.size(); i < n; i++) { Reply reply = replyHelper.getDataAt(i); if (mId.equals(reply.getNMBId())) { onGetReference(reply, false); return; } } NMBRequest request = new NMBRequest(); mRequest = request; request.setSite(mSite); request.setMethod(NMBClient.METHOD_GET_REFERENCE); request.setArgs(mId); request.setCallback(this); mNMBClient.execute(request); } private void onGetReference(final Reply reply, boolean animation) { mReply = reply; mLeftText.setText(highlightOp(reply)); mCenterText.setText("No." + reply.getNMBId()); mRightText.setText(ReadableTime.getDisplayTime(reply.getNMBTime())); mContent.setText(reply.getNMBDisplayContent()); String thumbKey = reply.getNMBThumbKey(); String thumbUrl = reply.getNMBThumbUrl(); boolean showImage; boolean loadFromNetwork; int ils = Settings.getImageLoadingStrategy(); if (ils == Settings.IMAGE_LOADING_STRATEGY_ALL || (ils == Settings.IMAGE_LOADING_STRATEGY_WIFI && NMBApplication.isConnectedWifi(getContext()))) { showImage = true; loadFromNetwork = true; } else { showImage = Settings.getImageLoadingStrategy2(); loadFromNetwork = false; } if (!TextUtils.isEmpty(thumbKey) && !TextUtils.isEmpty(thumbUrl) && showImage) { mThumb.setVisibility(View.VISIBLE); mThumb.unload(); mThumb.load(thumbKey, thumbUrl, loadFromNetwork); } else { mThumb.setVisibility(View.GONE); mThumb.unload(); } mContent.setTextSize(Settings.getFontSize()); mContent.setLineSpacing(LayoutUtils.dp2pix(getContext(), Settings.getLineSpacing()), 1.0f); if (Settings.getFixEmojiDisplay()) { mContent.useCustomTypeface(); } else { mContent.useOriginalTypeface(); } mViewTransition.showView(1, animation); String postId = reply.getNMBPostId(); if (postId != null && !postId.equals(PostFragment.this.mId) && mDialog != null && mDialog.isShowing()) { mButton.setVisibility(View.VISIBLE); mButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(getActivity(), PostActivity.class); intent.setAction(ACTION_SITE_ID); intent.putExtra(KEY_SITE, mSite.getId()); intent.putExtra(KEY_ID, reply.getNMBPostId()); startActivity(intent); } }); } mRequest = null; mDialog = null; } @Override public void onSuccess(ACReference result) { onGetReference(result, true); } @Override public void onFailure(Exception e) { mLeftText.setVisibility(View.GONE); mCenterText.setVisibility(View.GONE); mRightText.setVisibility(View.GONE); mContent.setText(R.string.cant_get_the_reference); mThumb.setVisibility(View.GONE); mViewTransition.showView(1, true); mRequest = null; mDialog = null; } @Override public void onCancel() { mRequest = null; if (mDialog != null) { mDialog.dismiss(); mDialog = null; } } @Override public void onDismiss(DialogInterface dialog) { mDialog = null; if (mRequest != null) { mRequest.cancel(); mRequest = null; } mThumb.unload(); } } private CharSequence highlightOp(Reply reply) { CharSequence user = reply.getNMBDisplayUsername(); if (2 == Settings.getChaosLevel()) { // Absolutely chaotic return user; } if (!TextUtils.isEmpty(user) && TextUtils2.contentEquals(user, mPostUser)) { Spannable spannable; if (user instanceof Spannable) { spannable = (Spannable) user; } else { spannable = new SpannableString(user); } int length = user.length(); if (spannable.getSpans(0, length, Object.class).length == 0) { StyleSpan styleSpan = new StyleSpan(android.graphics.Typeface.BOLD); ForegroundColorSpan colorSpan = new ForegroundColorSpan(mOpColor); spannable.setSpan(styleSpan, 0, length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); spannable.setSpan(colorSpan, 0, length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } return spannable; } else { return user; } } private void handleReferenceSpan(ReferenceSpan referenceSpan) { ReferenceDialogHelper helper = new ReferenceDialogHelper(referenceSpan.getSite(), referenceSpan.getId()); AlertDialog dialog = new AlertDialog.Builder(getContext()).setView(helper.getView()) .setOnDismissListener(helper).create(); helper.setDialog(dialog); dialog.show(); helper.request(); } private class ReplyDailogHelper implements DialogInterface.OnClickListener { private Reply mReply; private List<ResolveInfo> mResolveInfoList; public ReplyDailogHelper(Reply reply, List<ResolveInfo> resolveInfoList) { mReply = reply; mResolveInfoList = resolveInfoList; } @Override public void onClick(DialogInterface dialog, int which) { switch (which) { case 0: mCallback.reply(mSite, mId, ">>No." + mReply.getNMBId() + "\n", false); // TODO Let site decides it break; case 1: // Copy Context context = getContext(); if (context != null) { ClipboardManager cbm = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE); cbm.setPrimaryClip(ClipData.newPlainText(null, mReply.getNMBDisplayContent())); Toast.makeText(getContext(), R.string.comment_copied_clipboard, Toast.LENGTH_SHORT).show(); } break; case 2: // Send ActivityHelper.share(getActivity(), mReply.getNMBDisplayContent().toString()); break; case 3: { // Report mCallback.reply(mSite, mSite.getReportForumId(), ">>No." + mReply.getNMBId() + "\n", true); // TODO Let site decides it break; } default: { if (mResolveInfoList == null) { break; } int index = which - 4; if (index < mResolveInfoList.size() && index >= 0) { ResolveInfo info = mResolveInfoList.get(index); Intent intent = new Intent() .setClassName(info.activityInfo.packageName, info.activityInfo.name) .setAction(ACTION_PROCESS_TEXT) .setType("text/plain") .putExtra(EXTRA_PROCESS_TEXT_READONLY, true) .putExtra(EXTRA_PROCESS_TEXT, mReply.getNMBDisplayContent().toString()); startActivity(intent); } break; } } } } private void showReplyDialog(int position) { List<CharSequence> itemList = new ArrayList<>(); String[] items = getResources().getStringArray(R.array.reply_dialog); Collections.addAll(itemList, items); PackageManager pm = getContext().getPackageManager(); List<ResolveInfo> resolveInfos = pm.queryIntentActivities(new Intent() .setAction(ACTION_PROCESS_TEXT) .setType("text/plain"), 0); for (ResolveInfo info : resolveInfos) { itemList.add(info.loadLabel(pm)); } ReplyDailogHelper helper = new ReplyDailogHelper(mReplyHelper.getDataAt(position), resolveInfos); new AlertDialog.Builder(getContext()).setItems(itemList.toArray(new CharSequence[itemList.size()]), helper).show(); } @Override public boolean onItemLongClick(EasyRecyclerView parent, View view, int position, long id) { showReplyDialog(position); return true; } @Override public boolean onItemClick(EasyRecyclerView parent, View view, int position, long id) { RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(view); if (holder instanceof ReplyHolder) { ReplyHolder replyHolder = (ReplyHolder) holder; ClickableSpan span = replyHolder.content.getCurrentSpan(); replyHolder.content.clearCurrentSpan(); if (span instanceof URLSpan) { handleURLSpan((URLSpan) span); return true; } else if (span instanceof ReferenceSpan) { handleReferenceSpan((ReferenceSpan) span); return true; } } return false; } private class ReplyHolder extends RecyclerView.ViewHolder implements View.OnClickListener { public TextView leftText; public TextView centerText; public TextView rightText; public LinkifyTextView content; public LoadImageView thumb; public ReplyHolder(View itemView) { super(itemView); leftText = (TextView) itemView.findViewById(R.id.left_text); centerText = (TextView) itemView.findViewById(R.id.center_text); rightText = (TextView) itemView.findViewById(R.id.right_text); content = (LinkifyTextView) itemView.findViewById(R.id.content); thumb = (LoadImageView) itemView.findViewById(R.id.thumb); thumb.setOnClickListener(this); } @Override public void onClick(View v) { int position = getAdapterPosition(); if (position >= 0 && position < mReplyHelper.size()) { Reply reply = mReplyHelper.getDataAt(position); String key = reply.getNMBImageKey(); String image = reply.getNMBImageUrl(); if (!TextUtils.isEmpty(key) && !TextUtils.isEmpty(image)) { Intent intent = new Intent(getActivity(), GalleryActivity2.class); intent.setAction(GalleryActivity2.ACTION_SINGLE_IMAGE); intent.putExtra(GalleryActivity2.KEY_SITE, reply.getNMBSite().getId()); intent.putExtra(GalleryActivity2.KEY_ID, reply.getNMBId()); intent.putExtra(GalleryActivity2.KEY_KEY, key); intent.putExtra(GalleryActivity2.KEY_IMAGE, image); startActivity(intent); } } } } private class ReplyAdapter extends RecyclerView.Adapter<ReplyHolder> { @Override public ReplyHolder onCreateViewHolder(ViewGroup parent, int viewType) { ReplyHolder holder = new ReplyHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_post, parent, false)); mHolderList.add(new WeakReference<>(holder)); return holder; } @Override public void onBindViewHolder(ReplyHolder holder, int position) { Reply reply = mReplyHelper.getDataAt(position); holder.leftText.setText(highlightOp(reply)); holder.centerText.setText("No." + reply.getNMBId()); holder.rightText.setText(ReadableTime.getDisplayTime(reply.getNMBTime())); holder.content.setText(reply.getNMBDisplayContent()); String thumbKey = reply.getNMBThumbKey(); String thumbUrl = reply.getNMBThumbUrl(); boolean showImage; boolean loadFromNetwork; int ils = Settings.getImageLoadingStrategy(); if (ils == Settings.IMAGE_LOADING_STRATEGY_ALL || (ils == Settings.IMAGE_LOADING_STRATEGY_WIFI && getContext() != null && NMBApplication.isConnectedWifi(getContext()))) { showImage = true; loadFromNetwork = true; } else { showImage = Settings.getImageLoadingStrategy2(); loadFromNetwork = false; } if (!TextUtils.isEmpty(thumbKey) && !TextUtils.isEmpty(thumbUrl) && showImage) { holder.thumb.setVisibility(View.VISIBLE); holder.thumb.unload(); holder.thumb.load(thumbKey, thumbUrl, loadFromNetwork); } else { holder.thumb.setVisibility(View.GONE); holder.thumb.unload(); } holder.content.setTextSize(Settings.getFontSize()); // NOTE getContext() may return null holder.content.setLineSpacing(LayoutUtils.dp2pix(holder.content.getContext(), Settings.getLineSpacing()), 1.0f); if (Settings.getFixEmojiDisplay()) { holder.content.useCustomTypeface(); } else { holder.content.useOriginalTypeface(); } } @Override public int getItemCount() { return mReplyHelper.size(); } @Override public void onViewAttachedToWindow(ReplyHolder holder) { holder.thumb.start(); } @Override public void onViewDetachedFromWindow(ReplyHolder holder) { holder.thumb.stop(); } } private class ReplyHelper extends ContentLayout.ContentHelper<Reply> { @Override protected Context getContext() { return PostFragment.this.getContext(); } @Override protected void notifyDataSetChanged() { mReplyAdapter.notifyDataSetChanged(); } @Override protected void notifyItemRangeRemoved(int positionStart, int itemCount) { mReplyAdapter.notifyItemRangeRemoved(positionStart, itemCount); } @Override protected void notifyItemRangeInserted(int positionStart, int itemCount) { mReplyAdapter.notifyItemRangeInserted(positionStart, itemCount); } @Override protected void getPageData(int taskId, int type, int page) { if (mNMBRequest != null) { mNMBRequest.cancel(); mNMBRequest = null; } if (mReplyId != null) { mReplyHelper.showProgressBar(); NMBRequest request = new NMBRequest(); mNMBRequest = request; request.setSite(mSite); request.setMethod(NMBClient.METHOD_GET_REFERENCE); request.setArgs(mReplyId); request.setCallback(new GetPostIdFromReferenceListener()); mNMBClient.execute(request); } else { NMBRequest request = new NMBRequest(); mNMBRequest = request; request.setSite(mSite); request.setMethod(NMBClient.METHOD_GET_POST); request.setArgs(mId, page); request.setCallback(new PostListener(taskId, type, page, request)); mNMBClient.execute(request); } } } private class GetPostIdFromReferenceListener implements NMBClient.Callback<ACReference> { @Override public void onSuccess(ACReference result) { mReplyId = null; mId = result.postId; mToolbar.setTitle(mSite.getPostTitle(getContext(), mId)); mReplyHelper.refresh(); } @Override public void onFailure(Exception e) { mReplyHelper.showText(ExceptionUtils.getReadableString(getContext(), e)); } @Override public void onCancel() { } } private class PostListener implements NMBClient.Callback<Pair<Post, List<Reply>>> { private int mTaskId; private int mTaskType; private int mPage; private NMBRequest mRequest; public PostListener(int taskId, int type, int page, NMBRequest request) { mTaskId = taskId; mTaskType = type; mPage = page; mRequest = request; } @Override public void onSuccess(Pair<Post, List<Reply>> result) { if (mNMBRequest == mRequest) { // It is current request // Clear mNMBRequest = null; Post post = result.first; mPostUser = post.getNMBDisplayUsername(); List<Reply> replies = result.second; if (mPage == 0) { mPageSize = replies.size(); replies.add(0, post); } boolean empty; if (replies.isEmpty()) { empty = true; mReplyHelper.onGetEmptyData(mTaskId); } else { empty = false; mReplyHelper.onGetPageData(mTaskId, replies); } if (!empty && (mTaskType == ContentLayout.ContentHelper.TYPE_NEXT_PAGE || mTaskType == ContentLayout.ContentHelper.TYPE_NEXT_PAGE_KEEP_POS || mTaskType == ContentLayout.ContentHelper.TYPE_REFRESH || mTaskType == ContentLayout.ContentHelper.TYPE_SOMEWHERE) && mReplyHelper.size() == post.getNMBReplyCount() + 1) { // post is in data, so +1 mReplyHelper.setPages(mPage + 1); // this is the last page } else if (mPageSize == 0) { mReplyHelper.setPages(1); // Only post, no reply } else if (empty && (mTaskType == ContentLayout.ContentHelper.TYPE_NEXT_PAGE || mTaskType == ContentLayout.ContentHelper.TYPE_NEXT_PAGE_KEEP_POS)) { mReplyHelper.setPages(mPage); // previous page is the last page } else if (mPageSize != -1) { mReplyHelper.setPages(MathUtils.ceilDivide(post.getNMBReplyCount(), mPageSize)); // Guess2 } else if (mTaskType == ContentLayout.ContentHelper.TYPE_REFRESH_PAGE || mTaskType == ContentLayout.ContentHelper.TYPE_PRE_PAGE || mTaskType == ContentLayout.ContentHelper.TYPE_PRE_PAGE_KEEP_POS || mTaskType == ContentLayout.ContentHelper.TYPE_SOMEWHERE) { // Keep the pages } else { int pages = mReplyHelper.getPages(); if (pages != -1 && pages != Integer.MAX_VALUE) { // Keep it } else if (empty) { mReplyHelper.setPages(1); // At least we get post } else { mReplyHelper.setPages(Integer.MAX_VALUE); // Keep going } } } // Clear mRequest = null; } @Override public void onFailure(Exception e) { if (mNMBRequest == mRequest) { // It is current request // Clear mNMBRequest = null; mReplyHelper.onGetExpection(mTaskId, e); } // Clear mRequest = null; } @Override public void onCancel() { if (mNMBRequest == mRequest) { // It is current request // Clear mNMBRequest = null; } // Clear mRequest = null; } } private static class FeedListener implements NMBClient.Callback<Void> { private Context mContext; private boolean mAdd; public FeedListener(Context context, boolean add) { mContext = context.getApplicationContext(); mAdd = add; } @Override public void onSuccess(Void result) { Toast.makeText(mContext, mAdd ? R.string.add_feed_successfully : R.string.remove_feed_successfully, Toast.LENGTH_SHORT).show(); } @Override public void onFailure(Exception e) { Toast.makeText(mContext, mContext.getString(mAdd ? R.string.add_feed_failed : R.string.remove_feed_failed) + "\n" + ExceptionUtils.getReadableString(mContext, e), Toast.LENGTH_SHORT).show(); } @Override public void onCancel() { Log.d("TAG", "FeedListener onCancel"); } } public interface Callback { void reply(Site site, String id, String presetText, boolean report); } }