package ml.docilealligator.infinityforreddit.markdown; 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 androidx.annotation.Nullable; 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; import ml.docilealligator.infinityforreddit.activities.BaseActivity; import ml.docilealligator.infinityforreddit.customviews.SpoilerOnClickTextView; public class CustomMarkwonAdapter extends MarkwonAdapter { private BaseActivity activity; private final SparseArray> entries; private final Entry defaultEntry; private final MarkwonReducer reducer; private LayoutInflater layoutInflater; private Markwon markwon; private List nodes; @Nullable private View.OnClickListener onClickListener; @Nullable private View.OnLongClickListener onLongClickListener; @SuppressWarnings("WeakerAccess") CustomMarkwonAdapter( @NonNull BaseActivity activity, @NonNull SparseArray> entries, @NonNull Entry defaultEntry, @NonNull MarkwonReducer reducer) { this.activity = activity; this.entries = entries; this.defaultEntry = defaultEntry; this.reducer = reducer; setHasStableIds(true); } public void setOnClickListener(@Nullable View.OnClickListener onClickListener) { this.onClickListener = onClickListener; } public void setOnLongClickListener(@Nullable View.OnLongClickListener onLongClickListener) { this.onLongClickListener = onLongClickListener; } @NonNull public static CustomBuilderImpl builder( @NonNull BaseActivity activity, @LayoutRes int defaultEntryLayoutResId, @IdRes int defaultEntryTextViewResId ) { return builder(activity, SimpleEntry.create(defaultEntryLayoutResId, defaultEntryTextViewResId)); } @NonNull public static CustomBuilderImpl builder(@NonNull BaseActivity activity, @NonNull Entry defaultEntry) { //noinspection unchecked return new CustomBuilderImpl(activity, (Entry) 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 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 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 entry = getEntry(viewType); entry.bindHolder(markwon, holder, node); if (holder.itemView instanceof SpoilerOnClickTextView) { SpoilerOnClickTextView textView = (SpoilerOnClickTextView) holder.itemView; holder.itemView.setOnClickListener(view -> { if (onClickListener != null && textView.getSelectionStart() == -1 && textView.getSelectionEnd() == -1) { onClickListener.onClick(view); } }); holder.itemView.setOnLongClickListener(view -> { if (onLongClickListener != null && textView.getSelectionStart() == -1 && textView.getSelectionEnd() == -1) { return onLongClickListener.onLongClick(view); } return false; }); } 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) { TextView textView = (TextView) tableRow.getChildAt(j); tableRow.getChildAt(j).setOnClickListener(view -> { if (onClickListener != null && textView.getSelectionStart() == -1 && textView.getSelectionEnd() == -1) { onClickListener.onClick(view); } }); tableRow.getChildAt(j).setOnLongClickListener(view -> { if (onLongClickListener != null && textView.getSelectionStart() == -1 && textView.getSelectionEnd() == -1) { onLongClickListener.onLongClick(view); return true; } return false; }); } } } } } } if (node instanceof ImageAndGifBlock) { if (onClickListener != null) { holder.itemView.setOnClickListener(onClickListener); } if (onLongClickListener != null) { holder.itemView.setOnLongClickListener(onLongClickListener); } if (holder instanceof ImageAndGifEntry.Holder) { ((ImageAndGifEntry.Holder) holder).binding.captionTextViewMarkdownImageAndGifBlock.setOnClickListener(view -> { if (onClickListener != null && ((ImageAndGifEntry.Holder) holder).binding.captionTextViewMarkdownImageAndGifBlock.getSelectionStart() == -1 && ((ImageAndGifEntry.Holder) holder).binding.captionTextViewMarkdownImageAndGifBlock.getSelectionEnd() == -1) { onClickListener.onClick(view); } }); ((ImageAndGifEntry.Holder) holder).binding.captionTextViewMarkdownImageAndGifBlock.setOnLongClickListener(view -> { if (onLongClickListener != null && ((ImageAndGifEntry.Holder) holder).binding.captionTextViewMarkdownImageAndGifBlock.getSelectionStart() == -1 && ((ImageAndGifEntry.Holder) holder).binding.captionTextViewMarkdownImageAndGifBlock.getSelectionEnd() == -1) { return onLongClickListener.onLongClick(view); } return false; }); } } } @Override public int getItemCount() { return nodes != null ? nodes.size() : 0; } @Override public void onViewRecycled(@NonNull Holder holder) { super.onViewRecycled(holder); final Entry entry = getEntry(holder.getItemViewType()); entry.onViewRecycled(holder); } @SuppressWarnings("unused") @NonNull public List getItems() { return nodes != null ? Collections.unmodifiableList(nodes) : Collections.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 entry = getEntry(type); return entry.id(node); } @Override public int getNodeViewType(@NonNull Class 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 getEntry(int viewType) { return viewType == 0 ? defaultEntry : entries.get(viewType); } public static class CustomBuilderImpl implements Builder { private final BaseActivity activity; private final SparseArray> entries = new SparseArray<>(3); private final Entry defaultEntry; private MarkwonReducer reducer; CustomBuilderImpl(@NonNull BaseActivity activity, @NonNull Entry defaultEntry) { this.activity = activity; this.defaultEntry = defaultEntry; } @NonNull @Override public CustomBuilderImpl include( @NonNull Class node, @NonNull Entry entry) { //noinspection unchecked entries.append(node.hashCode(), (Entry) 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(activity, entries, defaultEntry, reducer); } } }