Update recycler plugin
This commit is contained in:
parent
50d331bc87
commit
1531588453
@ -138,8 +138,7 @@ public class TableEntry extends MarkwonAdapter.Entry<TableBlock, TableEntry.Hold
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void bindHolder(@NonNull Markwon markwon, @NonNull Holder holder, @NonNull TableBlock node) {
|
public void bindHolder(@NonNull Markwon markwon, @NonNull Holder holder, @NonNull TableBlock node, int depth) {
|
||||||
|
|
||||||
Table table = map.get(node);
|
Table table = map.get(node);
|
||||||
if (table == null) {
|
if (table == null) {
|
||||||
table = Table.parse(markwon, node);
|
table = Table.parse(markwon, node);
|
||||||
|
@ -16,6 +16,8 @@ android {
|
|||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
api project(':markwon-core')
|
api project(':markwon-core')
|
||||||
|
implementation project(path: ':markwon-round-textview')
|
||||||
|
implementation project(path: ':markwon-iframe-ext')
|
||||||
|
|
||||||
deps.with {
|
deps.with {
|
||||||
api it['x-recycler-view']
|
api it['x-recycler-view']
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
package io.noties.markwon.recycler;
|
||||||
|
|
||||||
|
import android.text.Selection;
|
||||||
|
import android.text.Spannable;
|
||||||
|
import android.text.method.LinkMovementMethod;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
public class CustomMovementMethod extends LinkMovementMethod {
|
||||||
|
@Override
|
||||||
|
public boolean canSelectArbitrarily () {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize(TextView widget, Spannable text) {
|
||||||
|
Selection.setSelection(text, text.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTakeFocus(TextView view, Spannable text, int dir) {
|
||||||
|
if ((dir & (View.FOCUS_FORWARD | View.FOCUS_DOWN)) != 0) {
|
||||||
|
if (view.getLayout() == null) {
|
||||||
|
// This shouldn't be null, but do something sensible if it is.
|
||||||
|
Selection.setSelection(text, text.length());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Selection.setSelection(text, text.length());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
|||||||
package io.noties.markwon.recycler;
|
package io.noties.markwon.recycler;
|
||||||
|
|
||||||
|
import android.graphics.Color;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@ -23,9 +24,9 @@ import io.noties.markwon.MarkwonReducer;
|
|||||||
* ability to customize rendering of blocks. For example display certain blocks in a horizontal
|
* ability to customize rendering of blocks. For example display certain blocks in a horizontal
|
||||||
* scrolling container or display tables in a specific widget designed for it ({@link Builder#include(Class, Entry)}).
|
* scrolling container or display tables in a specific widget designed for it ({@link Builder#include(Class, Entry)}).
|
||||||
*
|
*
|
||||||
* @see #builder(int, int)
|
* @see #builder(int, int, String, int, String)
|
||||||
* @see #builder(Entry)
|
* @see #builder(Entry)
|
||||||
* @see #create(int, int)
|
* @see #create(int, int, String, String)
|
||||||
* @see #create(Entry)
|
* @see #create(Entry)
|
||||||
* @see #setMarkdown(Markwon, String)
|
* @see #setMarkdown(Markwon, String)
|
||||||
* @see #setParsedMarkdown(Markwon, Node)
|
* @see #setParsedMarkdown(Markwon, Node)
|
||||||
@ -34,22 +35,31 @@ import io.noties.markwon.MarkwonReducer;
|
|||||||
*/
|
*/
|
||||||
public abstract class MarkwonAdapter extends RecyclerView.Adapter<MarkwonAdapter.Holder> {
|
public abstract class MarkwonAdapter extends RecyclerView.Adapter<MarkwonAdapter.Holder> {
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public static Builder builderTextViewIsRoot(@LayoutRes int defaultEntryLayoutResId) {
|
|
||||||
return builder(SimpleEntry.createTextViewIsRoot(defaultEntryLayoutResId));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Factory method to obtain {@link Builder} instance.
|
* Factory method to obtain {@link Builder} instance.
|
||||||
*
|
*
|
||||||
* @see Builder
|
* @see Builder
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
|
public static Builder builderTextViewIsRoot(@LayoutRes int defaultEntryLayoutResId) {
|
||||||
|
return builder(SimpleEntry.createTextViewIsRoot(defaultEntryLayoutResId));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String ENTRY_TYPE_IFRAME = "IFRAME";
|
||||||
|
private static String ENTRY_TYPE_TEXT = "TEXT";
|
||||||
|
@NonNull
|
||||||
public static Builder builder(
|
public static Builder builder(
|
||||||
@LayoutRes int defaultEntryLayoutResId,
|
@LayoutRes int defaultEntryLayoutResId,
|
||||||
@IdRes int defaultEntryTextViewResId
|
@IdRes int defaultEntryTextViewResId,
|
||||||
|
@NonNull String type,
|
||||||
|
@NonNull int textColor,
|
||||||
|
@NonNull String theme
|
||||||
) {
|
) {
|
||||||
return builder(SimpleEntry.create(defaultEntryLayoutResId, defaultEntryTextViewResId));
|
if (type.equalsIgnoreCase(ENTRY_TYPE_IFRAME)) {
|
||||||
|
return builder(SimpleEntryWebView.create(defaultEntryLayoutResId, defaultEntryTextViewResId));
|
||||||
|
} else {
|
||||||
|
return builder(SimpleEntry.create(defaultEntryLayoutResId, defaultEntryTextViewResId, textColor, theme));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@ -70,15 +80,17 @@ public abstract class MarkwonAdapter extends RecyclerView.Adapter<MarkwonAdapter
|
|||||||
* be specified explicitly.
|
* be specified explicitly.
|
||||||
*
|
*
|
||||||
* @see #create(Entry)
|
* @see #create(Entry)
|
||||||
* @see #builder(int, int)
|
* @see #builder(int, int, String, int, String)
|
||||||
* @see SimpleEntry
|
* @see SimpleEntry
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
public static MarkwonAdapter create(
|
public static MarkwonAdapter create(
|
||||||
@LayoutRes int defaultEntryLayoutResId,
|
@LayoutRes int defaultEntryLayoutResId,
|
||||||
@IdRes int defaultEntryTextViewResId
|
@IdRes int defaultEntryTextViewResId,
|
||||||
|
@NonNull String type,
|
||||||
|
@NonNull String theme
|
||||||
) {
|
) {
|
||||||
return builder(defaultEntryLayoutResId, defaultEntryTextViewResId).build();
|
return builder(defaultEntryLayoutResId, defaultEntryTextViewResId, type, Color.BLACK, theme).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -144,7 +156,7 @@ public abstract class MarkwonAdapter extends RecyclerView.Adapter<MarkwonAdapter
|
|||||||
@NonNull
|
@NonNull
|
||||||
public abstract H createHolder(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent);
|
public abstract H createHolder(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent);
|
||||||
|
|
||||||
public abstract void bindHolder(@NonNull Markwon markwon, @NonNull H holder, @NonNull N node);
|
public abstract void bindHolder(@NonNull Markwon markwon, @NonNull H holder, @NonNull N node, int depth);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Will be called when new content is available (clear internal cache if any)
|
* Will be called when new content is available (clear internal cache if any)
|
||||||
@ -164,11 +176,13 @@ public abstract class MarkwonAdapter extends RecyclerView.Adapter<MarkwonAdapter
|
|||||||
|
|
||||||
public abstract void setMarkdown(@NonNull Markwon markwon, @NonNull String markdown);
|
public abstract void setMarkdown(@NonNull Markwon markwon, @NonNull String markdown);
|
||||||
|
|
||||||
|
public abstract void setMarkdown(@NonNull Markwon markwon, @NonNull String markdown, int depth);
|
||||||
|
|
||||||
public abstract void setParsedMarkdown(@NonNull Markwon markwon, @NonNull Node document);
|
public abstract void setParsedMarkdown(@NonNull Markwon markwon, @NonNull Node document);
|
||||||
|
|
||||||
public abstract void setParsedMarkdown(@NonNull Markwon markwon, @NonNull List<Node> nodes);
|
public abstract void setParsedMarkdown(@NonNull Markwon markwon, @NonNull List<Node> nodes);
|
||||||
|
|
||||||
public abstract int getNodeViewType(@NonNull Class<? extends Node> node);
|
public abstract int getNodeViewType(Node node);
|
||||||
|
|
||||||
@SuppressWarnings("WeakerAccess")
|
@SuppressWarnings("WeakerAccess")
|
||||||
public static class Holder extends RecyclerView.ViewHolder {
|
public static class Holder extends RecyclerView.ViewHolder {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package io.noties.markwon.recycler;
|
package io.noties.markwon.recycler;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@ -13,6 +14,7 @@ import java.util.List;
|
|||||||
|
|
||||||
import io.noties.markwon.Markwon;
|
import io.noties.markwon.Markwon;
|
||||||
import io.noties.markwon.MarkwonReducer;
|
import io.noties.markwon.MarkwonReducer;
|
||||||
|
import io.noties.markwon.iframe.ext.IFrameNode;
|
||||||
|
|
||||||
class MarkwonAdapterImpl extends MarkwonAdapter {
|
class MarkwonAdapterImpl extends MarkwonAdapter {
|
||||||
|
|
||||||
@ -24,6 +26,7 @@ class MarkwonAdapterImpl extends MarkwonAdapter {
|
|||||||
|
|
||||||
private Markwon markwon;
|
private Markwon markwon;
|
||||||
private List<Node> nodes;
|
private List<Node> nodes;
|
||||||
|
private int depth = 0;
|
||||||
|
|
||||||
@SuppressWarnings("WeakerAccess")
|
@SuppressWarnings("WeakerAccess")
|
||||||
MarkwonAdapterImpl(
|
MarkwonAdapterImpl(
|
||||||
@ -42,6 +45,12 @@ class MarkwonAdapterImpl extends MarkwonAdapter {
|
|||||||
setParsedMarkdown(markwon, markwon.parse(markdown));
|
setParsedMarkdown(markwon, markwon.parse(markdown));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setMarkdown(@NonNull Markwon markwon, @NonNull String markdown, int depth) {
|
||||||
|
this.depth = depth;
|
||||||
|
setParsedMarkdown(markwon, markwon.parse(markdown));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setParsedMarkdown(@NonNull Markwon markwon, @NonNull Node document) {
|
public void setParsedMarkdown(@NonNull Markwon markwon, @NonNull Node document) {
|
||||||
setParsedMarkdown(markwon, reducer.reduce(document));
|
setParsedMarkdown(markwon, reducer.reduce(document));
|
||||||
@ -50,15 +59,16 @@ class MarkwonAdapterImpl extends MarkwonAdapter {
|
|||||||
@Override
|
@Override
|
||||||
public void setParsedMarkdown(@NonNull Markwon markwon, @NonNull List<Node> nodes) {
|
public void setParsedMarkdown(@NonNull Markwon markwon, @NonNull List<Node> nodes) {
|
||||||
// clear all entries before applying
|
// clear all entries before applying
|
||||||
|
try {
|
||||||
defaultEntry.clear();
|
defaultEntry.clear();
|
||||||
|
for (int i = 0, size = entries.size(); i < size; i++) {
|
||||||
for (int i = 0, size = entries.size(); i < size; i++) {
|
entries.valueAt(i).clear();
|
||||||
entries.valueAt(i).clear();
|
}
|
||||||
|
this.markwon = markwon;
|
||||||
|
this.nodes = nodes;
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e("Markdown issue", nodes.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
this.markwon = markwon;
|
|
||||||
this.nodes = nodes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@ -68,21 +78,22 @@ class MarkwonAdapterImpl extends MarkwonAdapter {
|
|||||||
if (layoutInflater == null) {
|
if (layoutInflater == null) {
|
||||||
layoutInflater = LayoutInflater.from(parent.getContext());
|
layoutInflater = LayoutInflater.from(parent.getContext());
|
||||||
}
|
}
|
||||||
|
|
||||||
final Entry<Node, Holder> entry = getEntry(viewType);
|
final Entry<Node, Holder> entry = getEntry(viewType);
|
||||||
|
|
||||||
return entry.createHolder(layoutInflater, parent);
|
return entry.createHolder(layoutInflater, parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull Holder holder, int position) {
|
public void onBindViewHolder(@NonNull Holder holder, int position) {
|
||||||
|
|
||||||
final Node node = nodes.get(position);
|
final Node node = nodes.get(position);
|
||||||
final int viewType = getNodeViewType(node.getClass());
|
int viewType = getNodeViewType(node);
|
||||||
|
|
||||||
final Entry<Node, Holder> entry = getEntry(viewType);
|
final Entry<Node, Holder> entry = getEntry(viewType);
|
||||||
|
if (isIFrameTypeType(node)) {
|
||||||
entry.bindHolder(markwon, holder, node);
|
if(node.getFirstChild() != null) {
|
||||||
|
entry.bindHolder(markwon, holder, node.getFirstChild().getFirstChild(), this.depth);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
entry.bindHolder(markwon, holder, node, this.depth);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -110,22 +121,37 @@ class MarkwonAdapterImpl extends MarkwonAdapter {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getItemViewType(int position) {
|
public int getItemViewType(int position) {
|
||||||
return getNodeViewType(nodes.get(position).getClass());
|
final Node node = nodes.get(position);
|
||||||
|
return getNodeViewType(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getItemId(int position) {
|
public long getItemId(int position) {
|
||||||
final Node node = nodes.get(position);
|
final Node node = nodes.get(position);
|
||||||
final int type = getNodeViewType(node.getClass());
|
int type = getNodeViewType(node);
|
||||||
final Entry<Node, Holder> entry = getEntry(type);
|
final Entry<Node, Holder> entry = getEntry(type);
|
||||||
return entry.id(node);
|
return entry.id(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isIFrameTypeType(Node node) {
|
||||||
|
if (node.getFirstChild() != null) {
|
||||||
|
if (node.getFirstChild().toString().contains("IFrameGroupNode") &&
|
||||||
|
node.getFirstChild().toString().contains("IFrameGroupNode")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getNodeViewType(@NonNull Class<? extends Node> node) {
|
public int getNodeViewType(@NonNull Node node) {
|
||||||
// if has registered -> then return it, else 0
|
// if has registered -> then return it, else 0
|
||||||
final int hash = node.hashCode();
|
if (isIFrameTypeType(node)) {
|
||||||
|
return IFrameNode.class.hashCode();
|
||||||
|
}
|
||||||
|
final int hash = node.getClass().hashCode();
|
||||||
if (entries.indexOfKey(hash) > -1) {
|
if (entries.indexOfKey(hash) > -1) {
|
||||||
|
Log.d("NodeType1", String.valueOf(hash));
|
||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package io.noties.markwon.recycler;
|
package io.noties.markwon.recycler;
|
||||||
|
|
||||||
|
import android.graphics.Color;
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@ -15,6 +16,7 @@ import org.commonmark.node.Node;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import io.noties.markdown.boundarytext.RoundedBgTextView;
|
||||||
import io.noties.markwon.Markwon;
|
import io.noties.markwon.Markwon;
|
||||||
import io.noties.markwon.utils.NoCopySpannableFactory;
|
import io.noties.markwon.utils.NoCopySpannableFactory;
|
||||||
|
|
||||||
@ -30,12 +32,12 @@ public class SimpleEntry extends MarkwonAdapter.Entry<Node, SimpleEntry.Holder>
|
|||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
public static SimpleEntry createTextViewIsRoot(@LayoutRes int layoutResId) {
|
public static SimpleEntry createTextViewIsRoot(@LayoutRes int layoutResId) {
|
||||||
return new SimpleEntry(layoutResId, 0);
|
return new SimpleEntry(layoutResId, 0, Color.BLACK, "light");
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public static SimpleEntry create(@LayoutRes int layoutResId, @IdRes int textViewIdRes) {
|
public static SimpleEntry create(@LayoutRes int layoutResId, @IdRes int textViewIdRes, @NonNull int textColor, @NonNull String theme) {
|
||||||
return new SimpleEntry(layoutResId, textViewIdRes);
|
return new SimpleEntry(layoutResId, textViewIdRes, textColor, theme);
|
||||||
}
|
}
|
||||||
|
|
||||||
// small cache for already rendered nodes
|
// small cache for already rendered nodes
|
||||||
@ -43,20 +45,24 @@ public class SimpleEntry extends MarkwonAdapter.Entry<Node, SimpleEntry.Holder>
|
|||||||
|
|
||||||
private final int layoutResId;
|
private final int layoutResId;
|
||||||
private final int textViewIdRes;
|
private final int textViewIdRes;
|
||||||
|
private final int textColor;
|
||||||
|
private String theme = "light";
|
||||||
|
|
||||||
public SimpleEntry(@LayoutRes int layoutResId, @IdRes int textViewIdRes) {
|
public SimpleEntry(@LayoutRes int layoutResId, @IdRes int textViewIdRes, @NonNull int textColor, String theme) {
|
||||||
this.layoutResId = layoutResId;
|
this.layoutResId = layoutResId;
|
||||||
this.textViewIdRes = textViewIdRes;
|
this.textViewIdRes = textViewIdRes;
|
||||||
|
this.textColor = textColor;
|
||||||
|
this.theme = theme;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Holder createHolder(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) {
|
public Holder createHolder(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) {
|
||||||
return new Holder(textViewIdRes, inflater.inflate(layoutResId, parent, false));
|
return new Holder(textViewIdRes, inflater.inflate(layoutResId, parent, false), textColor, theme);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void bindHolder(@NonNull Markwon markwon, @NonNull Holder holder, @NonNull Node node) {
|
public void bindHolder(@NonNull Markwon markwon, @NonNull Holder holder, @NonNull Node node, int depth) {
|
||||||
Spanned spanned = cache.get(node);
|
Spanned spanned = cache.get(node);
|
||||||
if (spanned == null) {
|
if (spanned == null) {
|
||||||
spanned = markwon.render(node);
|
spanned = markwon.render(node);
|
||||||
@ -72,22 +78,25 @@ public class SimpleEntry extends MarkwonAdapter.Entry<Node, SimpleEntry.Holder>
|
|||||||
|
|
||||||
public static class Holder extends MarkwonAdapter.Holder {
|
public static class Holder extends MarkwonAdapter.Holder {
|
||||||
|
|
||||||
final TextView textView;
|
final RoundedBgTextView textView;
|
||||||
|
|
||||||
protected Holder(@IdRes int textViewIdRes, @NonNull View itemView) {
|
protected Holder(@IdRes int textViewIdRes, @NonNull View itemView, @NonNull int textColor, String theme) {
|
||||||
super(itemView);
|
super(itemView);
|
||||||
|
|
||||||
final TextView textView;
|
final RoundedBgTextView textView;
|
||||||
if (textViewIdRes == 0) {
|
if (textViewIdRes == 0) {
|
||||||
if (!(itemView instanceof TextView)) {
|
if (!(itemView instanceof TextView)) {
|
||||||
throw new IllegalStateException("TextView is not root of layout " +
|
throw new IllegalStateException("TextView is not root of layout " +
|
||||||
"(specify TextView ID explicitly): " + itemView);
|
"(specify TextView ID explicitly): " + itemView);
|
||||||
}
|
}
|
||||||
textView = (TextView) itemView;
|
textView = (RoundedBgTextView) itemView;
|
||||||
} else {
|
} else {
|
||||||
textView = requireView(textViewIdRes);
|
textView = requireView(textViewIdRes);
|
||||||
}
|
}
|
||||||
|
textView.setThemeChange(theme);
|
||||||
|
textView.setTextColor(textColor);
|
||||||
this.textView = textView;
|
this.textView = textView;
|
||||||
|
this.textView.setMovementMethod(new CustomMovementMethod());
|
||||||
this.textView.setSpannableFactory(NoCopySpannableFactory.getInstance());
|
this.textView.setSpannableFactory(NoCopySpannableFactory.getInstance());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,150 @@
|
|||||||
|
package io.noties.markwon.recycler;
|
||||||
|
|
||||||
|
import static io.noties.markwon.iframe.ext.IFrameUtils.getDesmosId;
|
||||||
|
import static io.noties.markwon.iframe.ext.IFrameUtils.getVimeoVideoId;
|
||||||
|
import static io.noties.markwon.iframe.ext.IFrameUtils.getYoutubeVideoId;
|
||||||
|
|
||||||
|
import android.text.Spanned;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.webkit.WebChromeClient;
|
||||||
|
import android.webkit.WebSettings;
|
||||||
|
import android.webkit.WebView;
|
||||||
|
import android.webkit.WebViewClient;
|
||||||
|
|
||||||
|
import androidx.annotation.IdRes;
|
||||||
|
import androidx.annotation.LayoutRes;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.commonmark.node.Node;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import io.noties.markwon.Markwon;
|
||||||
|
import io.noties.markwon.iframe.ext.IFrameNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 3.0.0
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("WeakerAccess")
|
||||||
|
public class SimpleEntryWebView extends MarkwonAdapter.Entry<IFrameNode, SimpleEntryWebView.Holder> {
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static SimpleEntryWebView create(@LayoutRes int layoutResId, @IdRes int webViewIdRes) {
|
||||||
|
return new SimpleEntryWebView(layoutResId, webViewIdRes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// small cache for already rendered nodes
|
||||||
|
private final Map<Node, Spanned> cache = new HashMap<>();
|
||||||
|
|
||||||
|
private final int layoutResId;
|
||||||
|
private final int webViewIdRes;
|
||||||
|
|
||||||
|
public SimpleEntryWebView(@LayoutRes int layoutResId, @IdRes int webViewIdRes) {
|
||||||
|
this.layoutResId = layoutResId;
|
||||||
|
this.webViewIdRes = webViewIdRes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Holder createHolder(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) {
|
||||||
|
return new Holder(webViewIdRes, inflater.inflate(layoutResId, parent, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void bindHolder(@NonNull Markwon markwon, @NonNull Holder holder, @NonNull IFrameNode node, int depth) {
|
||||||
|
Spanned spanned = cache.get(node);
|
||||||
|
if (spanned == null) {
|
||||||
|
spanned = markwon.render(node);
|
||||||
|
cache.put(node, spanned);
|
||||||
|
}
|
||||||
|
renderWebView(holder, node, depth);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void renderWebView(Holder holder, IFrameNode node, int depth) {
|
||||||
|
String videoLink = "";
|
||||||
|
float deviceWidth = 1080;
|
||||||
|
float marginH = 10;
|
||||||
|
int videoWidth = (int)(deviceWidth - marginH * 2 - depth * marginH);
|
||||||
|
int videoHeight = (int)(videoWidth * 9 / 16);
|
||||||
|
if (node.link().contains("youtu")) {
|
||||||
|
String youtubeVideoId = getYoutubeVideoId(node.link());
|
||||||
|
if (!TextUtils.isEmpty(youtubeVideoId)) {
|
||||||
|
videoLink = "https://www.youtube.com/embed/" + youtubeVideoId;
|
||||||
|
}
|
||||||
|
} else if (node.link().contains("vimeo")) {
|
||||||
|
String vimeoId = getVimeoVideoId(node.link());
|
||||||
|
if (!TextUtils.isEmpty(vimeoId)) {
|
||||||
|
videoLink = "https://player.vimeo.com/video/" + vimeoId;
|
||||||
|
videoHeight = (int)(videoWidth * 3 / 4);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
String desmosId = getDesmosId(node.link());
|
||||||
|
if (!TextUtils.isEmpty(desmosId)) {
|
||||||
|
videoLink = "https://www.desmos.com/calculator/" + desmosId + "?embed";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WebSettings webSettings = holder.webView.getSettings();
|
||||||
|
webSettings.setJavaScriptEnabled(true);
|
||||||
|
|
||||||
|
ViewGroup.LayoutParams layoutParams = (ViewGroup.LayoutParams) holder.webView.getLayoutParams();
|
||||||
|
layoutParams.height = (int) videoHeight;
|
||||||
|
holder.webView.setLayoutParams(layoutParams);
|
||||||
|
holder.webView.setWebViewClient(new WebViewClient() {
|
||||||
|
@Override
|
||||||
|
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
holder.webView.setWebChromeClient(new WebChromeClient());
|
||||||
|
webSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN);
|
||||||
|
webSettings.setLoadWithOverviewMode(true);
|
||||||
|
webSettings.setUseWideViewPort(true);
|
||||||
|
webSettings.setTextZoom(100);
|
||||||
|
holder.webView.setVerticalScrollBarEnabled(false);
|
||||||
|
holder.webView.setHorizontalScrollBarEnabled(false);
|
||||||
|
holder.webView.setInitialScale(100);
|
||||||
|
if (!videoLink.isEmpty()) {
|
||||||
|
String dataURL = "<iframe id=\"player\" type=\"text/html\" width=\"" + videoWidth +"\" height=\"" + videoHeight + "\" " +
|
||||||
|
"src=\"" + videoLink + "\"frameborder=\"0\" allowfullscreen webkitallowfullscreen/>";
|
||||||
|
holder.webView.loadData(dataURL, "text/html", "utf-8");
|
||||||
|
}
|
||||||
|
holder.webView.setOnTouchListener(new View.OnTouchListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onTouch(View v, MotionEvent event) {
|
||||||
|
return (event.getAction() == MotionEvent.ACTION_MOVE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
cache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Holder extends MarkwonAdapter.Holder {
|
||||||
|
|
||||||
|
final WebView webView;
|
||||||
|
|
||||||
|
protected Holder(@IdRes int webViewIdRes, @NonNull View itemView) {
|
||||||
|
super(itemView);
|
||||||
|
|
||||||
|
final WebView webView;
|
||||||
|
if (webViewIdRes == 0) {
|
||||||
|
if (!(itemView instanceof WebView)) {
|
||||||
|
throw new IllegalStateException("WebView is not root of layout " +
|
||||||
|
"(specify TextView ID explicitly): " + itemView);
|
||||||
|
}
|
||||||
|
webView = (WebView) itemView;
|
||||||
|
} else {
|
||||||
|
webView = requireView(webViewIdRes);
|
||||||
|
}
|
||||||
|
this.webView = webView;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user