package com.luorrak.ouroboros.thread; import android.app.FragmentManager; import android.app.FragmentTransaction; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.support.design.widget.Snackbar; import android.support.v7.app.AlertDialog; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.text.Html; import android.text.Spannable; import android.text.format.DateUtils; import android.text.method.LinkMovementMethod; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.PopupMenu; import android.widget.TextView; import com.koushikdutta.async.future.FutureCallback; import com.koushikdutta.ion.ImageViewBitmapInfo; import com.koushikdutta.ion.Ion; import com.koushikdutta.ion.Response; import com.luorrak.ouroboros.R; import com.luorrak.ouroboros.api.CommentParser; import com.luorrak.ouroboros.deepzoom.DeepZoomActivity; import com.luorrak.ouroboros.reply.ReplyCommentActivity; import com.luorrak.ouroboros.util.ChanUrls; import com.luorrak.ouroboros.util.CursorRecyclerAdapter; import com.luorrak.ouroboros.util.DbContract; import com.luorrak.ouroboros.util.InfiniteDbHelper; import com.luorrak.ouroboros.util.Media; import com.luorrak.ouroboros.util.SettingsHelper; import com.luorrak.ouroboros.util.Util; import java.util.ArrayList; /** * Ouroboros - An 8chan browser * Copyright (C) 2015 Luorrak * * 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/>. */ public class ThreadAdapter extends CursorRecyclerAdapter { private final String LOG_TAG = ThreadAdapter.class.getSimpleName(); private CommentParser commentParser = new CommentParser(); private FragmentManager fragmentManager; private String boardName; private Context context; private InfiniteDbHelper infiniteDbHelper; private int parentWidth; private int parentHeight; public ThreadAdapter(Cursor cursor, FragmentManager fragmentManager, String boardName, Context context, InfiniteDbHelper infiniteDbHelper, int parentWidth, int parentHeight) { super(cursor); this.fragmentManager = fragmentManager; this.boardName = boardName; this.context = context; this.infiniteDbHelper = infiniteDbHelper; this.parentWidth = parentWidth; this.parentHeight = parentHeight; } @Override public void onBindViewHolderCursor(RecyclerView.ViewHolder holder, final Cursor cursor) { final ThreadViewHolder threadViewHolder = (ThreadViewHolder)holder; resetThreadViewHolder(threadViewHolder); createThreadObject(threadViewHolder, cursor); if (infiniteDbHelper.isNoUserPost(boardName, threadViewHolder.threadObject.no)){ threadViewHolder.threadObject.name = "(You) " + threadViewHolder.threadObject.name; } threadViewHolder.threadName.setText(threadViewHolder.threadObject.name); threadViewHolder.threadName.setVisibility(View.VISIBLE); threadViewHolder.threadTripcode.setText(threadViewHolder.threadObject.tripcode); threadViewHolder.threadTripcode.setVisibility(View.VISIBLE); threadViewHolder.threadNo.setText(threadViewHolder.threadObject.no); threadViewHolder.threadNo.setVisibility(View.VISIBLE); if (threadViewHolder.threadObject.id != null){ threadViewHolder.id.setText(commentParser.parseId(threadViewHolder.threadObject.id)); threadViewHolder.id.setVisibility(View.VISIBLE); } if(threadViewHolder.threadObject.sub != null){ threadViewHolder.threadObject.sub = Html.fromHtml(threadViewHolder.threadObject.sub).toString(); threadViewHolder.threadSub.setText(threadViewHolder.threadObject.sub); threadViewHolder.threadSub.setVisibility(View.VISIBLE); } if (threadViewHolder.threadObject.email != null && threadViewHolder.threadObject.email.equals("sage")){ threadViewHolder.threadEmail.setVisibility(View.VISIBLE); } threadViewHolder.threadTime.setText( DateUtils.getRelativeTimeSpanString( threadViewHolder.threadObject.threadTime * 1000, System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS )); threadViewHolder.threadTime.setVisibility(View.VISIBLE); // MediaView /////////////////////////////////////////////////////////////////////////////// if (threadViewHolder.threadObject.serializedMediaList != null) { threadViewHolder.threadMediaItemRecycler.setVisibility(View.VISIBLE); if (threadViewHolder.threadObject.mediaArrayList.size() > 1){ threadViewHolder.threadMediaItemRecycler.setScrollbarFadingEnabled(false); } } else { threadViewHolder.threadMediaItemRecycler.setVisibility(View.GONE); // Youtube ///////////////////////////////////////////////////////////////////////////// if (threadViewHolder.threadObject.embed != null){ createEmbedObject(threadViewHolder); if (threadViewHolder.embedObject.dataUrl != null){ threadViewHolder.threadEmbed.setVisibility(View.VISIBLE); threadViewHolder.threadEmbedPlayButton.setVisibility(View.VISIBLE); String imageUrl = "https://" + threadViewHolder.embedObject.imageUrl; Ion.with(threadViewHolder.threadEmbed) .load(imageUrl) .withBitmapInfo() .setCallback(new FutureCallback<ImageViewBitmapInfo>() { @Override public void onCompleted(Exception e, ImageViewBitmapInfo result) { if (e != null || result.getException() != null){ return; } if (result.getBitmapInfo().bitmap == null){ return; } if (getItemViewType(cursor.getPosition()) != Util.THREAD_LAYOUT_HORIZONTAL){ Util.setSwatch(threadViewHolder.mediaHolder, result); } } }); final String youtubeVideoUrl = threadViewHolder.embedObject.dataUrl; threadViewHolder.threadEmbedPlayButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(youtubeVideoUrl)); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); } }); } } // End Youtube ///////////////////////////////////////////////////////////////////////// } if (!threadViewHolder.threadObject.replyCount.equals("0")){ threadViewHolder.threadReplies.setVisibility(View.VISIBLE); threadViewHolder.threadObject.replyCount = threadViewHolder.threadObject.replyCount + " Replies"; threadViewHolder.threadReplies.setText(threadViewHolder.threadObject.replyCount); } threadViewHolder.threadMediaItemRecycler.setAdapter(new MediaAdapter(threadViewHolder.threadObject.mediaArrayList, boardName, threadViewHolder.threadObject.resto, context, parentWidth, parentHeight)); // END MediaView /////////////////////////////////////////////////////////////////////////// //Does comment exist if (threadViewHolder.threadObject.com != null){ threadViewHolder.threadObject.parsedCom = commentParser.parseCom( threadViewHolder.threadObject.com, CommentParser.THREAD_VIEW, boardName, threadViewHolder.threadObject.resto, fragmentManager, infiniteDbHelper ); threadViewHolder.threadCom.setVisibility(View.VISIBLE); threadViewHolder.threadCom.setText(threadViewHolder.threadObject.parsedCom); } // OnClick ///////////////////////////////////////////////////////////////////////////////// threadViewHolder.threadCom.setMovementMethod(LinkMovementMethod.getInstance()); threadViewHolder.threadReplies.setMovementMethod(LinkMovementMethod.getInstance()); threadViewHolder.threadReplyButton.setMovementMethod(LinkMovementMethod.getInstance()); } @Override public int getItemViewType(int position) { return SettingsHelper.getThreadView(context); } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { switch (viewType) { default: case Util.THREAD_LAYOUT_VERTICAL: { ThreadViewHolder threadViewHolder = new ThreadViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.card_thread, parent, false)); SnappyLinearLayoutManager layoutManager = new SnappyLinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false); threadViewHolder.threadMediaItemRecycler.setLayoutManager(layoutManager); return threadViewHolder; } case Util.THREAD_LAYOUT_HORIZONTAL: { ThreadViewHolder threadViewHolder = new ThreadViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.card_thread_horizontal, parent, false)); SnappyLinearLayoutManager layoutManager = new SnappyLinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false); threadViewHolder.threadMediaItemRecycler.setLayoutManager(layoutManager); return threadViewHolder; } } } // TextHandling //////////////////////////////////////////////////////////////////////////////// // Utility ///////////////////////////////////////////////////////////////////////////////////// private void resetThreadViewHolder(ThreadViewHolder threadViewHolder){ threadViewHolder.threadName.setVisibility(View.GONE); threadViewHolder.threadTripcode.setVisibility(View.GONE); threadViewHolder.threadNo.setVisibility(View.GONE); threadViewHolder.id.setVisibility(View.GONE); threadViewHolder.threadSub.setVisibility(View.GONE); threadViewHolder.threadCom.setVisibility(View.GONE); threadViewHolder.threadEmail.setVisibility(View.GONE); threadViewHolder.threadTime.setVisibility(View.GONE); threadViewHolder.threadReplies.setVisibility(View.GONE); threadViewHolder.threadEmbed.setVisibility(View.GONE); threadViewHolder.threadEmbedPlayButton.setVisibility(View.GONE); } private String getReplyCount(String no){ Cursor repliesCursor = infiniteDbHelper.getReplies(no); String replies = String.valueOf(repliesCursor.getCount()); repliesCursor.close(); return replies; } private void createThreadObject(ThreadViewHolder threadViewHolder, Cursor cursor){ threadViewHolder.threadObject.name = cursor.getString(cursor.getColumnIndex(DbContract.ThreadEntry.COLUMN_THREAD_NAME)); threadViewHolder.threadObject.tripcode = cursor.getString(cursor.getColumnIndex(DbContract.ThreadEntry.COLUMN_THREAD_TRIP)); threadViewHolder.threadObject.no = cursor.getString(cursor.getColumnIndex(DbContract.ThreadEntry.COLUMN_THREAD_NO)); threadViewHolder.threadObject.sub = cursor.getString(cursor.getColumnIndex(DbContract.ThreadEntry.COLUMN_THREAD_SUB)); threadViewHolder.threadObject.com = cursor.getString(cursor.getColumnIndex(DbContract.ThreadEntry.COLUMN_THREAD_COM)); threadViewHolder.threadObject.email = cursor.getString(cursor.getColumnIndex(DbContract.ThreadEntry.COLUMN_THREAD_EMAIL)); threadViewHolder.threadObject.threadTime = cursor.getLong(cursor.getColumnIndex(DbContract.ThreadEntry.COLUMN_THREAD_TIME)); threadViewHolder.threadObject.embed = cursor.getString(cursor.getColumnIndex(DbContract.ThreadEntry.COLUMN_THREAD_EMBED)); threadViewHolder.threadObject.resto = cursor.getString(cursor.getColumnIndex(DbContract.ThreadEntry.COLUMN_THREAD_RESTO)); threadViewHolder.threadObject.id = cursor.getString(cursor.getColumnIndex(DbContract.ThreadEntry.COLUMN_THREAD_ID)); threadViewHolder.threadObject.serializedMediaList = cursor.getBlob(cursor.getColumnIndex(DbContract.ThreadEntry.COLUMN_THREAD_MEDIA_FILES)); threadViewHolder.threadObject.replyCount = getReplyCount(threadViewHolder.threadObject.no); threadViewHolder.threadObject.mediaArrayList = (threadViewHolder.threadObject.serializedMediaList != null) ? (ArrayList<Media>) Util.deserializeObject(threadViewHolder.threadObject.serializedMediaList) : new ArrayList<Media>(); } //This only works for YouTube currently private void createEmbedObject(ThreadViewHolder threadViewHolder){ String[] youtubeData = Util.parseYoutube(threadViewHolder.threadObject.embed); threadViewHolder.embedObject.dataUrl = youtubeData[0]; threadViewHolder.embedObject.imageUrl = youtubeData[1]; } public void showDeletePostDialog(final View v, final ThreadObject threadObject) { new AlertDialog.Builder(context) .setTitle("Delete Post") .setMessage("Are you sure you want to delete this post?") .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { ((ThreadActivity) context).setProgressBarStatus(true); Ion.with(context) .load(ChanUrls.getDeletePostUrl()) .setBodyParameter("board", boardName) .setBodyParameter("delete_" + threadObject.no, "on") .setBodyParameter("password", SettingsHelper.getPostPassword(context)) .setBodyParameter("delete", "Delete") .setBodyParameter("reason", "") .asString() .withResponse() .setCallback(new FutureCallback<Response<String>>() { @Override public void onCompleted(Exception e, Response<String> result) { ((ThreadActivity) context).setProgressBarStatus(false); if (e != null) { Snackbar.make(v, "Error: " + e.toString(), Snackbar.LENGTH_LONG).show(); return; } if (result.getHeaders().code() == 303) { Snackbar.make(v, "Post Deleted", Snackbar.LENGTH_LONG).show(); } else { Snackbar.make(v, "Something Went Wrong", Snackbar.LENGTH_LONG).show(); } } }); } }) .setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { } }) .show(); } // View Holder ///////////////////////////////////////////////////////////////////////////////// class ThreadViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { public TextView threadName; public TextView threadTripcode; public TextView threadNo; public TextView id; public TextView threadSub; public TextView threadCom; public TextView threadEmail; public TextView threadTime; public Button threadReplies; public Button threadReplyButton; public Button threadMoreMenuButton; public ImageView threadEmbed; public ImageView threadEmbedPlayButton; public FrameLayout mediaHolder; public SnappyRecyclerView threadMediaItemRecycler; public ThreadObject threadObject; public EmbedObject embedObject; public ThreadViewHolder(View itemView) { super(itemView); threadName = (TextView) itemView.findViewById(R.id.thread_name); threadTripcode = (TextView) itemView.findViewById(R.id.thread_tripcode); threadNo = (TextView) itemView.findViewById(R.id.thread_post_no); id = (TextView) itemView.findViewById(R.id.thread_id); threadSub = (TextView) itemView.findViewById(R.id.thread_sub_text); threadCom = (TextView) itemView.findViewById(R.id.thread_com_text); threadEmail = (TextView) itemView.findViewById(R.id.thread_email); threadTime = (TextView) itemView.findViewById(R.id.thread_time); threadReplies = (Button) itemView.findViewById(R.id.thread_view_replies_button); threadReplyButton = (Button) itemView.findViewById(R.id.thread_submit_reply_button); threadMoreMenuButton = (Button) itemView.findViewById(R.id.thread_more_option_menu); threadEmbed = (ImageView) itemView.findViewById(R.id.thread_embed); threadEmbedPlayButton = (ImageView) itemView.findViewById(R.id.thread_embed_play_button); threadMediaItemRecycler = (SnappyRecyclerView) itemView.findViewById(R.id.thread_media_recycler); mediaHolder = (FrameLayout) itemView.findViewById(R.id.thread_image_holder); threadObject = new ThreadObject(); embedObject = new EmbedObject(); threadReplies.setOnClickListener(this); threadReplyButton.setOnClickListener(this); threadMoreMenuButton.setOnClickListener(this); } @Override public void onClick(final View v) { switch (v.getId()){ case R.id.thread_view_replies_button:{ FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); CardDialogFragment cardDialogFragment = CardDialogFragment.showReplies(threadNo.getText().toString(), boardName); fragmentTransaction.add(R.id.placeholder_card, cardDialogFragment) .addToBackStack("threadDialog") .commit(); break; } case R.id.thread_submit_reply_button:{ Intent intent = new Intent(context, ReplyCommentActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.putExtra(Util.INTENT_THREAD_NO, threadObject.resto); intent.putExtra(Util.INTENT_REPLY_NO, threadObject.no); intent.putExtra(Util.INTENT_BOARD_NAME, boardName); context.startActivity(intent); break; } //Adapted from Chanobol - // FIXME: 10/17/2015 to menu layout case R.id.thread_more_option_menu: { PopupMenu popupMenu = new PopupMenu(context, v); Menu m = popupMenu.getMenu(); final int COPY = 0; final int DELETE = 1; final int OPEN_MEDIA_ITEM = 2; m.add(Menu.NONE, COPY, Menu.NONE, "Copy Text"); m.add(Menu.NONE, DELETE, Menu.NONE, "Delete Post"); if (threadObject.serializedMediaList != null){ m.add(Menu.NONE, OPEN_MEDIA_ITEM, Menu.NONE, "Open Media"); } popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { switch (item.getItemId()) { case COPY: { Util.copyToClipboard(context, threadObject.parsedCom.toString()); Snackbar.make(v, "Text Copied to Clipboard", Snackbar.LENGTH_LONG).show(); break; } case DELETE: { showDeletePostDialog(v, threadObject); break; } case OPEN_MEDIA_ITEM: { Intent intent = new Intent(context, DeepZoomActivity.class); intent.putExtra(Util.TIM, threadObject.mediaArrayList.get(0).fileName); intent.putExtra(Util.INTENT_THREAD_NO, threadObject.resto); intent.putExtra(Util.INTENT_BOARD_NAME, boardName); context.startActivity(intent); break; } } return false; } }); popupMenu.show(); break; } } } } //Cursor object //////////////////////////////////////////////////////////////////////////////// class ThreadObject { public String name; public String tripcode; public String no; public String sub; public String com; public Spannable parsedCom; public String email; public long threadTime; public String embed; public String resto; public String id; public byte[] serializedMediaList; public String replyCount; public ArrayList<Media> mediaArrayList; } //Embed object ///////////////////////////////////////////////////////////////////////////////// class EmbedObject { public String imageUrl; public String dataUrl; } }