diff options
Diffstat (limited to 'app')
5 files changed, 314 insertions, 31 deletions
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/CommentsRecyclerViewAdapter.java b/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/CommentsRecyclerViewAdapter.java index 19fad657..6a0d3c53 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/CommentsRecyclerViewAdapter.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/CommentsRecyclerViewAdapter.java @@ -9,6 +9,7 @@ import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; import android.os.Handler; +import android.text.Spanned; import android.text.util.Linkify; import android.view.LayoutInflater; import android.view.View; @@ -33,6 +34,8 @@ import com.bumptech.glide.RequestManager; import com.bumptech.glide.request.RequestOptions; import com.lsjwzh.widget.materialloadingprogressbar.CircleProgressBar; +import org.commonmark.ext.gfm.tables.TableBlock; + import java.util.ArrayList; import java.util.Locale; import java.util.concurrent.Executor; @@ -52,6 +55,8 @@ import io.noties.markwon.inlineparser.HtmlInlineProcessor; import io.noties.markwon.inlineparser.MarkwonInlineParserPlugin; import io.noties.markwon.linkify.LinkifyPlugin; import io.noties.markwon.movement.MovementMethodPlugin; +import io.noties.markwon.recycler.table.TableEntry; +import io.noties.markwon.recycler.table.TableEntryPlugin; import jp.wasabeef.glide.transformations.RoundedCornersTransformation; import me.saket.bettermovementmethod.BetterLinkMovementMethod; import ml.docilealligator.infinityforreddit.R; @@ -68,6 +73,9 @@ import ml.docilealligator.infinityforreddit.comment.Comment; import ml.docilealligator.infinityforreddit.comment.FetchComment; import ml.docilealligator.infinityforreddit.customtheme.CustomThemeWrapper; import ml.docilealligator.infinityforreddit.customviews.CommentIndentationView; +import ml.docilealligator.infinityforreddit.customviews.CustomMarkwonAdapter; +import ml.docilealligator.infinityforreddit.customviews.LinearLayoutManagerBugFixed; +import ml.docilealligator.infinityforreddit.customviews.MarkwonLinearLayoutManager; import ml.docilealligator.infinityforreddit.customviews.SpoilerOnClickTextView; import ml.docilealligator.infinityforreddit.fragments.ViewPostDetailFragment; import ml.docilealligator.infinityforreddit.markdown.SpoilerParserPlugin; @@ -101,6 +109,7 @@ public class CommentsRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerVi private ArrayList<Comment> mVisibleComments; private Locale mLocale; private RequestManager mGlide; + private RecyclerView.RecycledViewPool recycledViewPool; private String mSingleCommentId; private boolean mIsSingleCommentThreadMode; private boolean mVoteButtonsOnTheRight; @@ -190,6 +199,15 @@ public class CommentsRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerVi } @Override + public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) { + if (mActivity.contentTypeface != null) { + textView.setTypeface(mActivity.contentTypeface); + } + textView.setTextColor(mCommentTextColor); + textView.setHighlightColor(Color.TRANSPARENT); + } + + @Override public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) { builder.linkResolver((view, link) -> { Intent intent = new Intent(mActivity, LinkResolverActivity.class); @@ -207,7 +225,7 @@ public class CommentsRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerVi }) .usePlugin(SpoilerParserPlugin.create(mCommentTextColor, commentSpoilerBackgroundColor)) .usePlugin(StrikethroughPlugin.create()) - .usePlugin(MovementMethodPlugin.create(BetterLinkMovementMethod.newInstance().setOnLinkLongClickListener((textView, url) -> { + .usePlugin(MovementMethodPlugin.create(BetterLinkMovementMethod.linkify(Linkify.WEB_URLS).setOnLinkLongClickListener((textView, url) -> { if (!activity.isDestroyed() && !activity.isFinishing()) { UrlMenuBottomSheetFragment urlMenuBottomSheetFragment = new UrlMenuBottomSheetFragment(); Bundle bundle = new Bundle(); @@ -218,7 +236,9 @@ public class CommentsRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerVi return true; }))) .usePlugin(LinkifyPlugin.create(Linkify.WEB_URLS)) + .usePlugin(TableEntryPlugin.create(mActivity)) .build(); + recycledViewPool = new RecyclerView.RecycledViewPool(); mAccessToken = accessToken; mAccountName = accountName; mPost = post; @@ -447,7 +467,8 @@ public class CommentsRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerVi Utils.setHTMLWithImageToTextView(((CommentViewHolder) holder).awardsTextView, comment.getAwards(), true); } - mCommentMarkwon.setMarkdown(((CommentViewHolder) holder).commentMarkdownView, comment.getCommentMarkdown()); + ((CommentViewHolder) holder).mMarkwonAdapter.setMarkdown(mCommentMarkwon, comment.getCommentMarkdown()); + ((CommentViewHolder) holder).mMarkwonAdapter.notifyDataSetChanged(); ((CommentViewHolder) holder).scoreTextView.setText(Utils.getNVotes(mShowAbsoluteNumberOfVotes, comment.getScore() + comment.getVoteType())); ((CommentViewHolder) holder).topScoreTextView.setText(mActivity.getString(R.string.top_score, @@ -1143,7 +1164,7 @@ public class CommentsRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerVi @BindView(R.id.awards_text_view_item_comment) TextView awardsTextView; @BindView(R.id.comment_markdown_view_item_post_comment) - SpoilerOnClickTextView commentMarkdownView; + RecyclerView commentMarkdownView; @BindView(R.id.bottom_constraint_layout_item_post_comment) ConstraintLayout bottomConstraintLayout; @BindView(R.id.up_vote_button_item_post_comment) @@ -1166,6 +1187,7 @@ public class CommentsRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerVi CommentIndentationView commentIndentationView; @BindView(R.id.divider_item_comment) View commentDivider; + CustomMarkwonAdapter mMarkwonAdapter; CommentViewHolder(View itemView) { super(itemView); @@ -1225,9 +1247,6 @@ public class CommentsRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerVi scoreTextView.setTypeface(mActivity.typeface); expandButton.setTypeface(mActivity.typeface); } - if (mActivity.contentTypeface != null) { - commentMarkdownView.setTypeface(mActivity.contentTypeface); - } if (mShowAuthorAvatar) { authorIconImageView.setVisibility(View.VISIBLE); @@ -1236,10 +1255,29 @@ public class CommentsRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerVi ((ConstraintLayout.LayoutParams) authorFlairTextView.getLayoutParams()).leftMargin = 0; } + commentMarkdownView.setRecycledViewPool(recycledViewPool); + LinearLayoutManagerBugFixed linearLayoutManager = new MarkwonLinearLayoutManager(mActivity, new MarkwonLinearLayoutManager.HorizontalScrollViewScrolledListener() { + @Override + public void onScrolledLeft() { + ((ViewPostDetailActivity) mActivity).lockSwipeRightToGoBack(); + } + + @Override + public void onScrolledRight() { + ((ViewPostDetailActivity) mActivity).unlockSwipeRightToGoBack(); + } + }); + commentMarkdownView.setLayoutManager(linearLayoutManager); + mMarkwonAdapter = CustomMarkwonAdapter.builder(R.layout.adapter_default_entry, R.id.text) + .include(TableBlock.class, TableEntry.create(builder -> builder + .tableLayout(R.layout.adapter_table_block, R.id.table_layout) + .textLayoutIsRoot(R.layout.view_table_entry_cell))) + .build(); + commentMarkdownView.setAdapter(mMarkwonAdapter); + itemView.setBackgroundColor(mCommentBackgroundColor); authorTextView.setTextColor(mUsernameColor); commentTimeTextView.setTextColor(mSecondaryTextColor); - commentMarkdownView.setTextColor(mCommentTextColor); authorFlairTextView.setTextColor(mAuthorFlairTextColor); topScoreTextView.setTextColor(mSecondaryTextColor); awardsTextView.setTextColor(mSecondaryTextColor); @@ -1572,23 +1610,34 @@ public class CommentsRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerVi if (mCommentToolbarHideOnClick) { View.OnLongClickListener hideToolbarOnLongClickListener = view -> hideToolbar(); itemView.setOnLongClickListener(hideToolbarOnLongClickListener); - commentMarkdownView.setOnLongClickListener(hideToolbarOnLongClickListener); commentTimeTextView.setOnLongClickListener(hideToolbarOnLongClickListener); + mMarkwonAdapter.setOnLongClickListener(v -> { + if (v instanceof TextView) { + if (((TextView) v).getSelectionStart() == -1 && ((TextView) v).getSelectionEnd() == -1) { + hideToolbar(); + } + } + return true; + }); } - commentMarkdownView.setOnClickListener(view -> { - if (commentMarkdownView.isSpoilerOnClick()) { - commentMarkdownView.setSpoilerOnClick(false); - return; + mMarkwonAdapter.setOnClickListener(v -> { + if (v instanceof SpoilerOnClickTextView) { + if (((SpoilerOnClickTextView) v).isSpoilerOnClick()) { + ((SpoilerOnClickTextView) v).setSpoilerOnClick(false); + return; + } } expandComments(); }); itemView.setOnClickListener(view -> expandComments()); } else { if (mCommentToolbarHideOnClick) { - commentMarkdownView.setOnClickListener(view -> { - if (commentMarkdownView.isSpoilerOnClick()) { - commentMarkdownView.setSpoilerOnClick(false); - return; + mMarkwonAdapter.setOnClickListener(view -> { + if (view instanceof SpoilerOnClickTextView) { + if (((SpoilerOnClickTextView) view).isSpoilerOnClick()) { + ((SpoilerOnClickTextView) view).setSpoilerOnClick(false); + return; + } } hideToolbar(); }); @@ -1596,9 +1645,11 @@ public class CommentsRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerVi itemView.setOnClickListener(hideToolbarOnClickListener); commentTimeTextView.setOnClickListener(hideToolbarOnClickListener); } - commentMarkdownView.setOnLongClickListener(view -> { - if (commentMarkdownView.getSelectionStart() == -1 && commentMarkdownView.getSelectionEnd() == -1) { - expandComments(); + mMarkwonAdapter.setOnLongClickListener(view -> { + if (view instanceof TextView) { + if (((TextView) view).getSelectionStart() == -1 && ((TextView) view).getSelectionEnd() == -1) { + expandComments(); + } } return true; }); @@ -1607,7 +1658,6 @@ public class CommentsRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerVi return true; }); } - commentMarkdownView.setHighlightColor(Color.TRANSPARENT); } private boolean expandComments() { diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/PostDetailRecyclerViewAdapter.java b/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/PostDetailRecyclerViewAdapter.java index 983c887a..ba59a7fd 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/PostDetailRecyclerViewAdapter.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/PostDetailRecyclerViewAdapter.java @@ -259,7 +259,7 @@ public class PostDetailRecyclerViewAdapter extends RecyclerView.Adapter<Recycler @Override public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) { if (mActivity.contentTypeface != null) { - textView.setTypeface(mActivity.typeface); + textView.setTypeface(mActivity.contentTypeface); } textView.setTextColor(markdownColor); textView.setOnLongClickListener(view -> { diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/customviews/CustomMarkwonAdapter.java b/app/src/main/java/ml/docilealligator/infinityforreddit/customviews/CustomMarkwonAdapter.java new file mode 100644 index 00000000..fb10d036 --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/customviews/CustomMarkwonAdapter.java @@ -0,0 +1,235 @@ +package ml.docilealligator.infinityforreddit.customviews; + +import android.util.SparseArray; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.HorizontalScrollView; +import android.widget.TableLayout; +import android.widget.TableRow; +import android.widget.TextView; + +import androidx.annotation.IdRes; +import androidx.annotation.LayoutRes; +import androidx.annotation.NonNull; + +import org.commonmark.node.Node; + +import java.util.Collections; +import java.util.List; + +import io.noties.markwon.Markwon; +import io.noties.markwon.MarkwonReducer; +import io.noties.markwon.recycler.MarkwonAdapter; +import io.noties.markwon.recycler.SimpleEntry; +import ml.docilealligator.infinityforreddit.R; + +public class CustomMarkwonAdapter extends MarkwonAdapter { + private final SparseArray<Entry<Node, Holder>> entries; + private final Entry<Node, Holder> defaultEntry; + private final MarkwonReducer reducer; + + private LayoutInflater layoutInflater; + + private Markwon markwon; + private List<Node> nodes; + + private View.OnClickListener onClickListener; + private View.OnLongClickListener onLongClickListener; + + @SuppressWarnings("WeakerAccess") + CustomMarkwonAdapter( + @NonNull SparseArray<Entry<Node, Holder>> entries, + @NonNull Entry<Node, Holder> defaultEntry, + @NonNull MarkwonReducer reducer) { + this.entries = entries; + this.defaultEntry = defaultEntry; + this.reducer = reducer; + + setHasStableIds(true); + } + + public void setOnClickListener(View.OnClickListener onClickListener) { + this.onClickListener = onClickListener; + } + + public void setOnLongClickListener(View.OnLongClickListener onLongClickListener) { + this.onLongClickListener = onLongClickListener; + } + + @NonNull + public static CustomBuilderImpl builder( + @LayoutRes int defaultEntryLayoutResId, + @IdRes int defaultEntryTextViewResId + ) { + return builder(SimpleEntry.create(defaultEntryLayoutResId, defaultEntryTextViewResId)); + } + + @NonNull + public static CustomBuilderImpl builder(@NonNull Entry<? extends Node, ? extends Holder> defaultEntry) { + //noinspection unchecked + return new CustomBuilderImpl((Entry<Node, Holder>) defaultEntry); + } + + @Override + public void setMarkdown(@NonNull Markwon markwon, @NonNull String markdown) { + setParsedMarkdown(markwon, markwon.parse(markdown)); + } + + @Override + public void setParsedMarkdown(@NonNull Markwon markwon, @NonNull Node document) { + setParsedMarkdown(markwon, reducer.reduce(document)); + } + + @Override + public void setParsedMarkdown(@NonNull Markwon markwon, @NonNull List<Node> nodes) { + // clear all entries before applying + + defaultEntry.clear(); + + for (int i = 0, size = entries.size(); i < size; i++) { + entries.valueAt(i).clear(); + } + + this.markwon = markwon; + this.nodes = nodes; + } + + @NonNull + @Override + public Holder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + + if (layoutInflater == null) { + layoutInflater = LayoutInflater.from(parent.getContext()); + } + + final Entry<Node, Holder> entry = getEntry(viewType); + + return entry.createHolder(layoutInflater, parent); + } + + @Override + public void onBindViewHolder(@NonNull Holder holder, int position) { + + final Node node = nodes.get(position); + final int viewType = getNodeViewType(node.getClass()); + + final Entry<Node, Holder> entry = getEntry(viewType); + + entry.bindHolder(markwon, holder, node); + + if (holder.itemView instanceof SpoilerOnClickTextView) { + holder.itemView.setOnClickListener(onClickListener); + holder.itemView.setOnLongClickListener(onLongClickListener); + } else if (holder.itemView instanceof HorizontalScrollView) { + TableLayout tableLayout = holder.itemView.findViewById(R.id.table_layout); + if (tableLayout != null) { + for (int i = 0; i < tableLayout.getChildCount(); i++) { + if (tableLayout.getChildAt(i) instanceof TableRow) { + TableRow tableRow = ((TableRow) tableLayout.getChildAt(i)); + for (int j = 0; j < tableRow.getChildCount(); j++) { + if (tableRow.getChildAt(j) instanceof TextView) { + tableRow.getChildAt(j).setOnClickListener(onClickListener); + tableRow.getChildAt(j).setOnLongClickListener(onLongClickListener); + } + } + } + } + } + } + } + + @Override + public int getItemCount() { + return nodes != null + ? nodes.size() + : 0; + } + + @Override + public void onViewRecycled(@NonNull Holder holder) { + super.onViewRecycled(holder); + + final Entry<Node, Holder> entry = getEntry(holder.getItemViewType()); + entry.onViewRecycled(holder); + } + + @SuppressWarnings("unused") + @NonNull + public List<Node> getItems() { + return nodes != null + ? Collections.unmodifiableList(nodes) + : Collections.<Node>emptyList(); + } + + @Override + public int getItemViewType(int position) { + return getNodeViewType(nodes.get(position).getClass()); + } + + @Override + public long getItemId(int position) { + final Node node = nodes.get(position); + final int type = getNodeViewType(node.getClass()); + final Entry<Node, Holder> entry = getEntry(type); + return entry.id(node); + } + + @Override + public int getNodeViewType(@NonNull Class<? extends Node> node) { + // if has registered -> then return it, else 0 + final int hash = node.hashCode(); + if (entries.indexOfKey(hash) > -1) { + return hash; + } + return 0; + } + + @NonNull + private Entry<Node, Holder> getEntry(int viewType) { + return viewType == 0 + ? defaultEntry + : entries.get(viewType); + } + + public static class CustomBuilderImpl implements Builder { + + private final SparseArray<Entry<Node, Holder>> entries = new SparseArray<>(3); + + private final Entry<Node, Holder> defaultEntry; + + private MarkwonReducer reducer; + + CustomBuilderImpl(@NonNull Entry<Node, Holder> defaultEntry) { + this.defaultEntry = defaultEntry; + } + + @NonNull + @Override + public <N extends Node> CustomBuilderImpl include( + @NonNull Class<N> node, + @NonNull Entry<? super N, ? extends Holder> entry) { + //noinspection unchecked + entries.append(node.hashCode(), (Entry<Node, Holder>) entry); + return this; + } + + @NonNull + @Override + public CustomBuilderImpl reducer(@NonNull MarkwonReducer reducer) { + this.reducer = reducer; + return this; + } + + @NonNull + @Override + public CustomMarkwonAdapter build() { + + if (reducer == null) { + reducer = MarkwonReducer.directChildren(); + } + + return new CustomMarkwonAdapter(entries, defaultEntry, reducer); + } + } +} diff --git a/app/src/main/res/layout/adapter_default_entry.xml b/app/src/main/res/layout/adapter_default_entry.xml index a02d03f0..b011fbf5 100644 --- a/app/src/main/res/layout/adapter_default_entry.xml +++ b/app/src/main/res/layout/adapter_default_entry.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<TextView xmlns:android="http://schemas.android.com/apk/res/android" +<ml.docilealligator.infinityforreddit.customviews.SpoilerOnClickTextView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/text" android:layout_width="match_parent" android:layout_height="wrap_content" diff --git a/app/src/main/res/layout/item_comment.xml b/app/src/main/res/layout/item_comment.xml index b62106f8..06ac8c02 100644 --- a/app/src/main/res/layout/item_comment.xml +++ b/app/src/main/res/layout/item_comment.xml @@ -119,16 +119,14 @@ android:fontFamily="?attr/font_family" tools:visibility="visible" /> - <ml.docilealligator.infinityforreddit.customviews.SpoilerOnClickTextView + <androidx.recyclerview.widget.RecyclerView android:id="@+id/comment_markdown_view_item_post_comment" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_margin="8dp" - android:paddingStart="8dp" - android:paddingEnd="8dp" - android:textColor="?attr/primaryTextColor" - android:textSize="?attr/content_font_default" - android:fontFamily="?attr/content_font_family" /> + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_marginTop="8dp" + android:layout_marginStart="8dp" + android:layout_marginEnd="8dp" + android:nestedScrollingEnabled="false" /> <androidx.constraintlayout.widget.ConstraintLayout android:id="@+id/bottom_constraint_layout_item_post_comment" |