diff options
17 files changed, 559 insertions, 32 deletions
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/activities/CommentActivity.java b/app/src/main/java/ml/docilealligator/infinityforreddit/activities/CommentActivity.java index 6096f1c4..8ec61c8a 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/activities/CommentActivity.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/activities/CommentActivity.java @@ -65,6 +65,7 @@ import ml.docilealligator.infinityforreddit.customtheme.CustomThemeWrapper; import ml.docilealligator.infinityforreddit.customviews.LinearLayoutManagerBugFixed; import ml.docilealligator.infinityforreddit.databinding.ActivityCommentBinding; import ml.docilealligator.infinityforreddit.events.SwitchAccountEvent; +import ml.docilealligator.infinityforreddit.markdown.EmoteCloseBracketInlineProcessor; import ml.docilealligator.infinityforreddit.markdown.ImageAndGifEntry; import ml.docilealligator.infinityforreddit.markdown.ImageAndGifPlugin; import ml.docilealligator.infinityforreddit.markdown.MarkdownUtils; @@ -210,9 +211,11 @@ public class CommentActivity extends BaseActivity implements UploadImageEnabledA builder.linkColor(linkColor); } }; + EmoteCloseBracketInlineProcessor emoteCloseBracketInlineProcessor = new EmoteCloseBracketInlineProcessor(); ImageAndGifPlugin imageAndGifPlugin = new ImageAndGifPlugin(); Markwon postBodyMarkwon = MarkdownUtils.createFullRedditMarkwon(this, - miscPlugin, imageAndGifPlugin, parentTextColor, parentSpoilerBackgroundColor, null); + miscPlugin, emoteCloseBracketInlineProcessor, imageAndGifPlugin, parentTextColor, + parentSpoilerBackgroundColor, null); MarkwonAdapter markwonAdapter = MarkdownUtils.createTablesAdapter(new ImageAndGifEntry(this, mGlide, new ImageAndGifEntry.OnItemClickListener() { @Override public void onItemClick(MediaMetadata mediaMetadata) { diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/activities/FullMarkdownActivity.java b/app/src/main/java/ml/docilealligator/infinityforreddit/activities/FullMarkdownActivity.java index 92d6589c..07de032d 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/activities/FullMarkdownActivity.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/activities/FullMarkdownActivity.java @@ -45,6 +45,7 @@ import ml.docilealligator.infinityforreddit.customviews.SwipeLockInterface; import ml.docilealligator.infinityforreddit.customviews.SwipeLockLinearLayoutManager; import ml.docilealligator.infinityforreddit.customviews.slidr.Slidr; import ml.docilealligator.infinityforreddit.events.SwitchAccountEvent; +import ml.docilealligator.infinityforreddit.markdown.EmoteCloseBracketInlineProcessor; import ml.docilealligator.infinityforreddit.markdown.ImageAndGifEntry; import ml.docilealligator.infinityforreddit.markdown.ImageAndGifPlugin; import ml.docilealligator.infinityforreddit.markdown.MarkdownUtils; @@ -142,9 +143,11 @@ public class FullMarkdownActivity extends BaseActivity { builder.linkColor(linkColor); } }; + EmoteCloseBracketInlineProcessor emoteCloseBracketInlineProcessor = new EmoteCloseBracketInlineProcessor(); ImageAndGifPlugin imageAndGifPlugin = new ImageAndGifPlugin(); Markwon markwon = MarkdownUtils.createFullRedditMarkwon(this, - miscPlugin, imageAndGifPlugin, markdownColor, spoilerBackgroundColor, null); + miscPlugin, emoteCloseBracketInlineProcessor, imageAndGifPlugin, markdownColor, + spoilerBackgroundColor, null); MarkwonAdapter markwonAdapter = MarkdownUtils.createTablesAdapter(new ImageAndGifEntry(this, Glide.with(this), new ImageAndGifEntry.OnItemClickListener() { diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/activities/WikiActivity.java b/app/src/main/java/ml/docilealligator/infinityforreddit/activities/WikiActivity.java index 16c1c748..10cbb519 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/activities/WikiActivity.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/activities/WikiActivity.java @@ -53,6 +53,7 @@ import ml.docilealligator.infinityforreddit.customviews.SwipeLockInterface; import ml.docilealligator.infinityforreddit.customviews.SwipeLockLinearLayoutManager; import ml.docilealligator.infinityforreddit.customviews.slidr.Slidr; import ml.docilealligator.infinityforreddit.events.SwitchAccountEvent; +import ml.docilealligator.infinityforreddit.markdown.EmoteCloseBracketInlineProcessor; import ml.docilealligator.infinityforreddit.markdown.ImageAndGifEntry; import ml.docilealligator.infinityforreddit.markdown.ImageAndGifPlugin; import ml.docilealligator.infinityforreddit.markdown.MarkdownUtils; @@ -98,6 +99,7 @@ public class WikiActivity extends BaseActivity { @Inject CustomThemeWrapper mCustomThemeWrapper; private String wikiMarkdown; + private EmoteCloseBracketInlineProcessor emoteCloseBracketInlineProcessor; private ImageAndGifPlugin imageAndGifPlugin; private Markwon markwon; private MarkwonAdapter markwonAdapter; @@ -179,9 +181,10 @@ public class WikiActivity extends BaseActivity { urlMenuBottomSheetFragment.show(getSupportFragmentManager(), null); return true; }; + emoteCloseBracketInlineProcessor = new EmoteCloseBracketInlineProcessor(); imageAndGifPlugin = new ImageAndGifPlugin(); markwon = MarkdownUtils.createFullRedditMarkwon(this, - miscPlugin, imageAndGifPlugin, markdownColor, spoilerBackgroundColor, onLinkLongClickListener); + miscPlugin, emoteCloseBracketInlineProcessor, imageAndGifPlugin, markdownColor, spoilerBackgroundColor, onLinkLongClickListener); markwonAdapter = MarkdownUtils.createTablesAdapter(new ImageAndGifEntry(this, mGlide, new ImageAndGifEntry.OnItemClickListener() { diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/CommentsListingRecyclerViewAdapter.java b/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/CommentsListingRecyclerViewAdapter.java index 5abbbe8c..d947a370 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/CommentsListingRecyclerViewAdapter.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/CommentsListingRecyclerViewAdapter.java @@ -58,6 +58,7 @@ import ml.docilealligator.infinityforreddit.customviews.SpoilerOnClickTextView; import ml.docilealligator.infinityforreddit.customviews.SwipeLockInterface; import ml.docilealligator.infinityforreddit.customviews.SwipeLockLinearLayoutManager; import ml.docilealligator.infinityforreddit.databinding.ItemCommentBinding; +import ml.docilealligator.infinityforreddit.markdown.EmoteCloseBracketInlineProcessor; import ml.docilealligator.infinityforreddit.markdown.ImageAndGifEntry; import ml.docilealligator.infinityforreddit.markdown.ImageAndGifPlugin; import ml.docilealligator.infinityforreddit.markdown.MarkdownUtils; @@ -84,6 +85,7 @@ public class CommentsListingRecyclerViewAdapter extends PagedListAdapter<Comment private BaseActivity mActivity; private Retrofit mOauthRetrofit; private Locale mLocale; + private EmoteCloseBracketInlineProcessor mEmoteCloseBracketInlineProcessor; private ImageAndGifPlugin mImageAndGifPlugin; private Markwon mMarkwon; private ImageAndGifEntry mImageAndGifEntry; @@ -177,9 +179,11 @@ public class CommentsListingRecyclerViewAdapter extends PagedListAdapter<Comment } return true; }; + mEmoteCloseBracketInlineProcessor = new EmoteCloseBracketInlineProcessor(); mImageAndGifPlugin = new ImageAndGifPlugin(); mMarkwon = MarkdownUtils.createFullRedditMarkwon(mActivity, - miscPlugin, mImageAndGifPlugin, mCommentColor, commentSpoilerBackgroundColor, onLinkLongClickListener); + miscPlugin, mEmoteCloseBracketInlineProcessor, mImageAndGifPlugin, mCommentColor, + commentSpoilerBackgroundColor, onLinkLongClickListener); mImageAndGifEntry = new ImageAndGifEntry(activity, Glide.with(activity), new ImageAndGifEntry.OnItemClickListener() { @Override public void onItemClick(MediaMetadata mediaMetadata) { @@ -241,6 +245,7 @@ public class CommentsListingRecyclerViewAdapter extends PagedListAdapter<Comment Utils.setHTMLWithImageToTextView(((CommentBaseViewHolder) holder).awardsTextView, comment.getAwards(), true); } + mEmoteCloseBracketInlineProcessor.setMediaMetadataMap(comment.getMediaMetadataMap()); mImageAndGifPlugin.setMediaMetadataMap(comment.getMediaMetadataMap()); ((CommentBaseViewHolder) holder).markwonAdapter.setMarkdown(mMarkwon, comment.getCommentMarkdown()); // noinspection NotifyDataSetChanged 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 9702bfb8..5202b4a1 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/CommentsRecyclerViewAdapter.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/CommentsRecyclerViewAdapter.java @@ -69,6 +69,7 @@ import ml.docilealligator.infinityforreddit.customviews.SwipeLockInterface; import ml.docilealligator.infinityforreddit.customviews.SwipeLockLinearLayoutManager; import ml.docilealligator.infinityforreddit.databinding.ItemCommentBinding; import ml.docilealligator.infinityforreddit.fragments.ViewPostDetailFragment; +import ml.docilealligator.infinityforreddit.markdown.EmoteCloseBracketInlineProcessor; import ml.docilealligator.infinityforreddit.markdown.ImageAndGifEntry; import ml.docilealligator.infinityforreddit.markdown.ImageAndGifPlugin; import ml.docilealligator.infinityforreddit.markdown.MarkdownUtils; @@ -97,6 +98,7 @@ public class CommentsRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerVi private Executor mExecutor; private Retrofit mRetrofit; private Retrofit mOauthRetrofit; + private EmoteCloseBracketInlineProcessor mEmoteCloseBracketInlineProcessor; private ImageAndGifPlugin mImageAndGifPlugin; private Markwon mCommentMarkwon; private ImageAndGifEntry mImageAndGifEntry; @@ -211,9 +213,11 @@ public class CommentsRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerVi } return true; }; + mEmoteCloseBracketInlineProcessor = new EmoteCloseBracketInlineProcessor(); mImageAndGifPlugin = new ImageAndGifPlugin(); mCommentMarkwon = MarkdownUtils.createFullRedditMarkwon(mActivity, - miscPlugin, mImageAndGifPlugin, mCommentTextColor, commentSpoilerBackgroundColor, onLinkLongClickListener); + miscPlugin, mEmoteCloseBracketInlineProcessor, mImageAndGifPlugin, mCommentTextColor, + commentSpoilerBackgroundColor, onLinkLongClickListener); mImageAndGifEntry = new ImageAndGifEntry(activity, mGlide, new ImageAndGifEntry.OnItemClickListener() { @Override public void onItemClick(MediaMetadata mediaMetadata) { @@ -466,6 +470,7 @@ public class CommentsRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerVi Utils.setHTMLWithImageToTextView(((CommentBaseViewHolder) holder).awardsTextView, comment.getAwards(), true); } + mEmoteCloseBracketInlineProcessor.setMediaMetadataMap(comment.getMediaMetadataMap()); mImageAndGifPlugin.setMediaMetadataMap(comment.getMediaMetadataMap()); ((CommentBaseViewHolder) holder).mMarkwonAdapter.setMarkdown(mCommentMarkwon, comment.getCommentMarkdown()); // noinspection NotifyDataSetChanged 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 11b04252..f50c62c3 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/PostDetailRecyclerViewAdapter.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/PostDetailRecyclerViewAdapter.java @@ -103,6 +103,7 @@ import ml.docilealligator.infinityforreddit.databinding.ItemPostDetailVideoAndGi import ml.docilealligator.infinityforreddit.databinding.ItemPostDetailVideoAutoplayBinding; import ml.docilealligator.infinityforreddit.databinding.ItemPostDetailVideoAutoplayLegacyControllerBinding; import ml.docilealligator.infinityforreddit.fragments.ViewPostDetailFragment; +import ml.docilealligator.infinityforreddit.markdown.EmoteCloseBracketInlineProcessor; import ml.docilealligator.infinityforreddit.markdown.ImageAndGifEntry; import ml.docilealligator.infinityforreddit.markdown.ImageAndGifPlugin; import ml.docilealligator.infinityforreddit.markdown.MarkdownUtils; @@ -144,6 +145,7 @@ public class PostDetailRecyclerViewAdapter extends RecyclerView.Adapter<Recycler private SharedPreferences mCurrentAccountSharedPreferences; private RequestManager mGlide; private SaveMemoryCenterInisdeDownsampleStrategy mSaveMemoryCenterInsideDownsampleStrategy; + private EmoteCloseBracketInlineProcessor mEmoteCloseBracketInlineProcessor; private ImageAndGifPlugin mImageAndGifPlugin; private Markwon mPostDetailMarkwon; private ImageAndGifEntry mImageAndGifEntry; @@ -383,9 +385,11 @@ public class PostDetailRecyclerViewAdapter extends RecyclerView.Adapter<Recycler } return true; }; + mEmoteCloseBracketInlineProcessor = new EmoteCloseBracketInlineProcessor(); mImageAndGifPlugin = new ImageAndGifPlugin(); mPostDetailMarkwon = MarkdownUtils.createFullRedditMarkwon(mActivity, - miscPlugin, mImageAndGifPlugin, markdownColor, postSpoilerBackgroundColor, onLinkLongClickListener); + miscPlugin, mEmoteCloseBracketInlineProcessor, mImageAndGifPlugin, markdownColor, + postSpoilerBackgroundColor, onLinkLongClickListener); mImageAndGifEntry = new ImageAndGifEntry(activity, mGlide, mDataSavingMode, (post.isNSFW() && mNeedBlurNsfw && !(mDoNotBlurNsfwInNsfwSubreddits && mFragment != null && mFragment.getIsNsfwSubreddit())) || (mPost.isSpoiler() && mNeedBlurSpoiler), @@ -666,6 +670,7 @@ public class PostDetailRecyclerViewAdapter extends RecyclerView.Adapter<Recycler if (mPost.getSelfText() != null && !mPost.getSelfText().equals("")) { ((PostDetailBaseViewHolder) holder).contentMarkdownView.setVisibility(View.VISIBLE); ((PostDetailBaseViewHolder) holder).contentMarkdownView.setAdapter(mMarkwonAdapter); + mEmoteCloseBracketInlineProcessor.setMediaMetadataMap(mPost.getMediaMetadataMap()); mImageAndGifPlugin.setMediaMetadataMap(mPost.getMediaMetadataMap()); mMarkwonAdapter.setMarkdown(mPostDetailMarkwon, mPost.getSelfText()); // noinspection NotifyDataSetChanged diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/RulesRecyclerViewAdapter.java b/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/RulesRecyclerViewAdapter.java index 02b88c0f..40c4e19f 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/RulesRecyclerViewAdapter.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/RulesRecyclerViewAdapter.java @@ -35,12 +35,14 @@ import ml.docilealligator.infinityforreddit.customtheme.CustomThemeWrapper; import ml.docilealligator.infinityforreddit.customviews.SwipeLockInterface; import ml.docilealligator.infinityforreddit.customviews.SwipeLockLinearLayoutManager; import ml.docilealligator.infinityforreddit.customviews.slidr.widget.SliderPanel; +import ml.docilealligator.infinityforreddit.markdown.EmoteCloseBracketInlineProcessor; import ml.docilealligator.infinityforreddit.markdown.ImageAndGifEntry; import ml.docilealligator.infinityforreddit.markdown.ImageAndGifPlugin; import ml.docilealligator.infinityforreddit.markdown.MarkdownUtils; public class RulesRecyclerViewAdapter extends RecyclerView.Adapter<RulesRecyclerViewAdapter.RuleViewHolder> { private BaseActivity activity; + private EmoteCloseBracketInlineProcessor emoteCloseBracketInlineProcessor; private ImageAndGifPlugin imageAndGifPlugin; private Markwon markwon; @Nullable @@ -87,9 +89,11 @@ public class RulesRecyclerViewAdapter extends RecyclerView.Adapter<RulesRecycler } return true; }; + emoteCloseBracketInlineProcessor = new EmoteCloseBracketInlineProcessor(); imageAndGifPlugin = new ImageAndGifPlugin(); markwon = MarkdownUtils.createFullRedditMarkwon(activity, - miscPlugin, imageAndGifPlugin, mPrimaryTextColor, spoilerBackgroundColor, onLinkLongClickListener); + miscPlugin, emoteCloseBracketInlineProcessor, imageAndGifPlugin, mPrimaryTextColor, + spoilerBackgroundColor, onLinkLongClickListener); } @NonNull diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/comment/ParseComment.java b/app/src/main/java/ml/docilealligator/infinityforreddit/comment/ParseComment.java index 4ba92570..d08bebd1 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/comment/ParseComment.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/comment/ParseComment.java @@ -339,31 +339,40 @@ public class ParseComment { String k = it.next(); JSONObject media = mediaMetadataJSON.getJSONObject(k); String e = media.getString(JSONUtils.E_KEY); - JSONArray downscales = media.getJSONArray(JSONUtils.P_KEY); - JSONObject downscaledItemJSON; - if (downscales.length() <= 3) { - downscaledItemJSON = downscales.getJSONObject(downscales.length() - 1); - } else { - downscaledItemJSON = downscales.getJSONObject(3); - } - MediaMetadata.MediaItem downscaledItem = new MediaMetadata.MediaItem(downscaledItemJSON.getInt(JSONUtils.X_KEY), - downscaledItemJSON.getInt(JSONUtils.Y_KEY), downscaledItemJSON.getString(JSONUtils.U_KEY)); + + JSONObject originalItemJSON = media.getJSONObject(JSONUtils.S_KEY); + MediaMetadata.MediaItem originalItem; if (e.equalsIgnoreCase("Image")) { - JSONObject originalItemJSON = media.getJSONObject(JSONUtils.S_KEY); - MediaMetadata.MediaItem originalItem = new MediaMetadata.MediaItem(originalItemJSON.getInt(JSONUtils.X_KEY), + originalItem = new MediaMetadata.MediaItem(originalItemJSON.getInt(JSONUtils.X_KEY), originalItemJSON.getInt(JSONUtils.Y_KEY), originalItemJSON.getString(JSONUtils.U_KEY)); - - String id = media.getString(JSONUtils.ID_KEY); - mediaMetadataMap.put(id, new MediaMetadata(id, e, originalItem, downscaledItem)); - } else if (e.equalsIgnoreCase("AnimatedImage")) { - JSONObject originalItemJSON = media.getJSONObject(JSONUtils.S_KEY); - MediaMetadata.MediaItem originalItem = new MediaMetadata.MediaItem(originalItemJSON.getInt(JSONUtils.X_KEY), - originalItemJSON.getInt(JSONUtils.Y_KEY), originalItemJSON.getString(JSONUtils.GIF_KEY), - originalItemJSON.getString(JSONUtils.MP4_KEY)); - - String id = media.getString(JSONUtils.ID_KEY); - mediaMetadataMap.put(id, new MediaMetadata(id, e, originalItem, downscaledItem)); + } else { + if (originalItemJSON.has(JSONUtils.MP4_KEY)) { + originalItem = new MediaMetadata.MediaItem(originalItemJSON.getInt(JSONUtils.X_KEY), + originalItemJSON.getInt(JSONUtils.Y_KEY), originalItemJSON.getString(JSONUtils.GIF_KEY), + originalItemJSON.getString(JSONUtils.MP4_KEY)); + } else { + originalItem = new MediaMetadata.MediaItem(originalItemJSON.getInt(JSONUtils.X_KEY), + originalItemJSON.getInt(JSONUtils.Y_KEY), originalItemJSON.getString(JSONUtils.GIF_KEY)); + } + } + String id = media.getString(JSONUtils.ID_KEY); + + MediaMetadata.MediaItem downscaledItem; + if (media.has(JSONUtils.P_KEY)) { + JSONArray downscales = media.getJSONArray(JSONUtils.P_KEY); + JSONObject downscaledItemJSON; + if (downscales.length() <= 3) { + downscaledItemJSON = downscales.getJSONObject(downscales.length() - 1); + } else { + downscaledItemJSON = downscales.getJSONObject(3); + } + downscaledItem = new MediaMetadata.MediaItem(downscaledItemJSON.getInt(JSONUtils.X_KEY), + downscaledItemJSON.getInt(JSONUtils.Y_KEY), downscaledItemJSON.getString(JSONUtils.U_KEY)); + } else { + downscaledItem = originalItem; } + + mediaMetadataMap.put(id, new MediaMetadata(id, e, originalItem, downscaledItem)); } catch (JSONException e) { e.printStackTrace(); } diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/fragments/SidebarFragment.java b/app/src/main/java/ml/docilealligator/infinityforreddit/fragments/SidebarFragment.java index 3b42ee22..821e6926 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/fragments/SidebarFragment.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/fragments/SidebarFragment.java @@ -45,6 +45,7 @@ import ml.docilealligator.infinityforreddit.bottomsheetfragments.CopyTextBottomS import ml.docilealligator.infinityforreddit.bottomsheetfragments.UrlMenuBottomSheetFragment; import ml.docilealligator.infinityforreddit.customtheme.CustomThemeWrapper; import ml.docilealligator.infinityforreddit.customviews.LinearLayoutManagerBugFixed; +import ml.docilealligator.infinityforreddit.markdown.EmoteCloseBracketInlineProcessor; import ml.docilealligator.infinityforreddit.markdown.ImageAndGifEntry; import ml.docilealligator.infinityforreddit.markdown.ImageAndGifPlugin; import ml.docilealligator.infinityforreddit.markdown.MarkdownUtils; @@ -146,9 +147,11 @@ public class SidebarFragment extends Fragment { urlMenuBottomSheetFragment.show(getChildFragmentManager(), null); return true; }; + EmoteCloseBracketInlineProcessor emoteCloseBracketInlineProcessor = new EmoteCloseBracketInlineProcessor(); ImageAndGifPlugin imageAndGifPlugin = new ImageAndGifPlugin(); Markwon markwon = MarkdownUtils.createFullRedditMarkwon(activity, - miscPlugin, imageAndGifPlugin, markdownColor, spoilerBackgroundColor, onLinkLongClickListener); + miscPlugin, emoteCloseBracketInlineProcessor, imageAndGifPlugin, markdownColor, + spoilerBackgroundColor, onLinkLongClickListener); MarkwonAdapter markwonAdapter = MarkdownUtils.createTablesAdapter(new ImageAndGifEntry(activity, Glide.with(this), new ImageAndGifEntry.OnItemClickListener() { diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/Emote.java b/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/Emote.java new file mode 100644 index 00000000..90517682 --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/Emote.java @@ -0,0 +1,34 @@ +package ml.docilealligator.infinityforreddit.markdown; + +import org.commonmark.node.CustomNode; +import org.commonmark.node.Visitor; + +import ml.docilealligator.infinityforreddit.MediaMetadata; + +public class Emote extends CustomNode { + private final MediaMetadata mediaMetadata; + private final String title; + + public Emote(MediaMetadata mediaMetadata, String title) { + this.mediaMetadata = mediaMetadata; + this.title = title; + } + + @Override + public void accept(Visitor visitor) { + visitor.visit(this); + } + + public MediaMetadata getMediaMetadata() { + return mediaMetadata; + } + + public String getTitle() { + return title; + } + + @Override + protected String toStringAttributes() { + return "destination=" + mediaMetadata.original.url + ", title=" + title; + } +} diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/EmoteCloseBracketInlineProcessor.java b/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/EmoteCloseBracketInlineProcessor.java new file mode 100644 index 00000000..04856f3b --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/EmoteCloseBracketInlineProcessor.java @@ -0,0 +1,156 @@ +package ml.docilealligator.infinityforreddit.markdown; + +import static io.noties.markwon.inlineparser.InlineParserUtils.mergeChildTextNodes; + +import org.commonmark.internal.Bracket; +import org.commonmark.internal.util.Escaping; +import org.commonmark.node.Link; +import org.commonmark.node.LinkReferenceDefinition; +import org.commonmark.node.Node; + +import java.util.Map; +import java.util.regex.Pattern; + +import io.noties.markwon.inlineparser.InlineProcessor; +import ml.docilealligator.infinityforreddit.MediaMetadata; + +public class EmoteCloseBracketInlineProcessor extends InlineProcessor { + private static final Pattern WHITESPACE = Pattern.compile("\\s+"); + + private Map<String, MediaMetadata> mediaMetadataMap; + + @Override + public char specialCharacter() { + return ']'; + } + + @Override + protected Node parse() { + index++; + int startIndex = index; + + // Get previous `[` or `![` + Bracket opener = lastBracket(); + if (opener == null) { + // No matching opener, just return a literal. + return text("]"); + } + + if (!opener.allowed) { + // Matching opener but it's not allowed, just return a literal. + removeLastBracket(); + return text("]"); + } + + // Check to see if we have a link/image + + String dest = null; + String title = null; + boolean isLinkOrImage = false; + + // Maybe a inline link like `[foo](/uri "title")` + if (peek() == '(') { + index++; + spnl(); + if ((dest = parseLinkDestination()) != null) { + spnl(); + // title needs a whitespace before + if (WHITESPACE.matcher(input.substring(index - 1, index)).matches()) { + title = parseLinkTitle(); + spnl(); + } + if (peek() == ')') { + index++; + isLinkOrImage = true; + } else { + index = startIndex; + } + } + } + + // Maybe a reference link like `[foo][bar]`, `[foo][]` or `[foo]` + if (!isLinkOrImage) { + + // See if there's a link label like `[bar]` or `[]` + int beforeLabel = index; + parseLinkLabel(); + int labelLength = index - beforeLabel; + String ref = null; + if (labelLength > 2) { + ref = input.substring(beforeLabel, beforeLabel + labelLength); + } else if (!opener.bracketAfter) { + // If the second label is empty `[foo][]` or missing `[foo]`, then the first label is the reference. + // But it can only be a reference when there's no (unescaped) bracket in it. + // If there is, we don't even need to try to look up the reference. This is an optimization. + ref = input.substring(opener.index, startIndex); + } + + if (ref != null) { + String label = Escaping.normalizeReference(ref); + LinkReferenceDefinition definition = context.getLinkReferenceDefinition(label); + if (definition != null) { + dest = definition.getDestination(); + title = definition.getTitle(); + isLinkOrImage = true; + } + } + } + + if (isLinkOrImage) { + // If we got here, open is a potential opener + if (mediaMetadataMap == null) { + index = startIndex; + removeLastBracket(); + + return text("]"); + } + MediaMetadata mediaMetadata = mediaMetadataMap.get(dest); + if (mediaMetadata == null) { + index = startIndex; + removeLastBracket(); + + return text("]"); + } + + Node linkOrImage = opener.image ? new Emote(mediaMetadata, title) : new Link(dest, title); + + Node node = opener.node.getNext(); + while (node != null) { + Node next = node.getNext(); + linkOrImage.appendChild(node); + node = next; + } + + // Process delimiters such as emphasis inside link/image + processDelimiters(opener.previousDelimiter); + mergeChildTextNodes(linkOrImage); + // We don't need the corresponding text node anymore, we turned it into a link/image node + opener.node.unlink(); + removeLastBracket(); + + // Links within links are not allowed. We found this link, so there can be no other link around it. + if (!opener.image) { + Bracket bracket = lastBracket(); + while (bracket != null) { + if (!bracket.image) { + // Disallow link opener. It will still get matched, but will not result in a link. + bracket.allowed = false; + } + bracket = bracket.previous; + } + } + + return linkOrImage; + + } else { // no link or image + index = startIndex; + removeLastBracket(); + + return text("]"); + } + } + + public void setMediaMetadataMap(Map<String, MediaMetadata> mediaMetadataMap) { + this.mediaMetadataMap = mediaMetadataMap; + } +} diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/EmoteInlineProcessor.java b/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/EmoteInlineProcessor.java new file mode 100644 index 00000000..fcffc530 --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/EmoteInlineProcessor.java @@ -0,0 +1,32 @@ +package ml.docilealligator.infinityforreddit.markdown; + +import org.commonmark.internal.Bracket; +import org.commonmark.node.Node; +import org.commonmark.node.Text; + +import io.noties.markwon.inlineparser.InlineProcessor; + +public class EmoteInlineProcessor extends InlineProcessor { + @Override + public char specialCharacter() { + return '!'; + } + + @Override + protected Node parse() { + int startIndex = index; + index++; + if (peek() == '[') { + index++; + + Text node = text("!["); + + // Add entry to stack for this opener + addBracket(Bracket.image(node, startIndex + 1, lastBracket(), lastDelimiter())); + + return node; + } else { + return null; + } + } +} diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/EmotePlugin.java b/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/EmotePlugin.java new file mode 100644 index 00000000..24ae1ad3 --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/EmotePlugin.java @@ -0,0 +1,230 @@ +package ml.docilealligator.infinityforreddit.markdown; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.text.Spanned; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.RequestBuilder; +import com.bumptech.glide.RequestManager; +import com.bumptech.glide.request.target.CustomTarget; +import com.bumptech.glide.request.target.Target; +import com.bumptech.glide.request.transition.Transition; + +import org.commonmark.node.Link; +import org.commonmark.node.Node; + +import java.util.HashMap; +import java.util.Map; + +import io.noties.markwon.AbstractMarkwonPlugin; +import io.noties.markwon.MarkwonConfiguration; +import io.noties.markwon.MarkwonSpansFactory; +import io.noties.markwon.MarkwonVisitor; +import io.noties.markwon.RenderProps; +import io.noties.markwon.SpanFactory; +import io.noties.markwon.image.AsyncDrawable; +import io.noties.markwon.image.AsyncDrawableLoader; +import io.noties.markwon.image.AsyncDrawableScheduler; +import io.noties.markwon.image.DrawableUtils; +import io.noties.markwon.image.ImageProps; +import io.noties.markwon.image.ImageSpanFactory; +import ml.docilealligator.infinityforreddit.MediaMetadata; + +public class EmotePlugin extends AbstractMarkwonPlugin { + public interface GlideStore { + + @NonNull + RequestBuilder<Drawable> load(@NonNull AsyncDrawable drawable); + + void cancel(@NonNull Target<?> target); + } + + @NonNull + public static EmotePlugin create(@NonNull final Context context) { + // @since 4.5.0 cache RequestManager + // sometimes `cancel` would be called after activity is destroyed, + // so `Glide.with(context)` will throw an exception + return create(Glide.with(context)); + } + + @NonNull + public static EmotePlugin create(@NonNull final RequestManager requestManager) { + return create(new GlideStore() { + @NonNull + @Override + public RequestBuilder<Drawable> load(@NonNull AsyncDrawable drawable) { + return requestManager.load(drawable.getDestination()); + } + + @Override + public void cancel(@NonNull Target<?> target) { + requestManager.clear(target); + } + }); + } + + @NonNull + public static EmotePlugin create(@NonNull GlideStore glideStore) { + return new EmotePlugin(glideStore); + } + + private final GlideAsyncDrawableLoader glideAsyncDrawableLoader; + + @SuppressWarnings("WeakerAccess") + EmotePlugin(@NonNull GlideStore glideStore) { + this.glideAsyncDrawableLoader = new GlideAsyncDrawableLoader(glideStore); + } + + @Override + public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) { + builder.setFactory(Emote.class, new ImageSpanFactory()); + } + + @Override + public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) { + builder.asyncDrawableLoader(glideAsyncDrawableLoader); + } + + @Override + public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) { + builder.on(Emote.class, new MarkwonVisitor.NodeVisitor<Emote>() { + @Override + public void visit(@NonNull MarkwonVisitor visitor, @NonNull Emote emote) { + // if there is no image spanFactory, ignore + final SpanFactory spanFactory = visitor.configuration().spansFactory().get(Emote.class); + if (spanFactory == null) { + visitor.visitChildren(emote); + return; + } + + final int length = visitor.length(); + + visitor.visitChildren(emote); + + // we must check if anything _was_ added, as we need at least one char to render + if (length == visitor.length()) { + visitor.builder().append('\uFFFC'); + } + + final MarkwonConfiguration configuration = visitor.configuration(); + + final Node parent = emote.getParent(); + final boolean link = parent instanceof Link; + + final String destination = configuration + .imageDestinationProcessor() + .process(emote.getMediaMetadata().original.url); + + final RenderProps props = visitor.renderProps(); + + // apply image properties + // Please note that we explicitly set IMAGE_SIZE to null as we do not clear + // properties after we applied span (we could though) + ImageProps.DESTINATION.set(props, destination); + ImageProps.REPLACEMENT_TEXT_IS_LINK.set(props, link); + ImageProps.IMAGE_SIZE.set(props, null); + + visitor.setSpans(length, spanFactory.getSpans(configuration, props)); + } + }); + } + + @Override + public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) { + AsyncDrawableScheduler.unschedule(textView); + } + + @Override + public void afterSetText(@NonNull TextView textView) { + AsyncDrawableScheduler.schedule(textView); + } + + public void setMediaMetadataMap(Map<String, MediaMetadata> mediaMetadataMap) { + + } + + private static class GlideAsyncDrawableLoader extends AsyncDrawableLoader { + + private final GlideStore glideStore; + private final Map<AsyncDrawable, Target<?>> cache = new HashMap<>(2); + + GlideAsyncDrawableLoader(@NonNull GlideStore glideStore) { + this.glideStore = glideStore; + } + + @Override + public void load(@NonNull AsyncDrawable drawable) { + final Target<Drawable> target = new AsyncDrawableTarget(drawable); + cache.put(drawable, target); + glideStore.load(drawable) + .into(target); + } + + @Override + public void cancel(@NonNull AsyncDrawable drawable) { + final Target<?> target = cache.remove(drawable); + if (target != null) { + glideStore.cancel(target); + } + } + + @Nullable + @Override + public Drawable placeholder(@NonNull AsyncDrawable drawable) { + return null; + } + + private class AsyncDrawableTarget extends CustomTarget<Drawable> { + + private final AsyncDrawable drawable; + + AsyncDrawableTarget(@NonNull AsyncDrawable drawable) { + this.drawable = drawable; + } + + @Override + public void onResourceReady(@NonNull Drawable resource, @Nullable Transition<? super Drawable> transition) { + if (cache.remove(drawable) != null) { + if (drawable.isAttached()) { + DrawableUtils.applyIntrinsicBoundsIfEmpty(resource); + drawable.setResult(resource); + } + } + } + + @Override + public void onLoadStarted(@Nullable Drawable placeholder) { + if (placeholder != null + && drawable.isAttached()) { + DrawableUtils.applyIntrinsicBoundsIfEmpty(placeholder); + drawable.setResult(placeholder); + } + } + + @Override + public void onLoadFailed(@Nullable Drawable errorDrawable) { + if (cache.remove(drawable) != null) { + if (errorDrawable != null + && drawable.isAttached()) { + DrawableUtils.applyIntrinsicBoundsIfEmpty(errorDrawable); + drawable.setResult(errorDrawable); + } + } + } + + @Override + public void onLoadCleared(@Nullable Drawable placeholder) { + // we won't be checking if target is still present as cancellation + // must remove target anyway + if (drawable.isAttached()) { + drawable.clearResult(); + } + } + } + } +} diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/EmoteSpanFactory.java b/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/EmoteSpanFactory.java new file mode 100644 index 00000000..c70488b8 --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/EmoteSpanFactory.java @@ -0,0 +1,29 @@ +package ml.docilealligator.infinityforreddit.markdown; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import io.noties.markwon.MarkwonConfiguration; +import io.noties.markwon.RenderProps; +import io.noties.markwon.SpanFactory; +import io.noties.markwon.image.AsyncDrawable; +import io.noties.markwon.image.AsyncDrawableSpan; +import io.noties.markwon.image.ImageProps; + +public class EmoteSpanFactory implements SpanFactory { + @Nullable + @Override + public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) { + return new AsyncDrawableSpan( + configuration.theme(), + new AsyncDrawable( + ImageProps.DESTINATION.require(props), + configuration.asyncDrawableLoader(), + configuration.imageSizeResolver(), + ImageProps.IMAGE_SIZE.get(props) + ), + AsyncDrawableSpan.ALIGN_BOTTOM, + ImageProps.REPLACEMENT_TEXT_IS_LINK.get(props, false) + ); + } +} diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/ImageAndGifBlockParser.java b/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/ImageAndGifBlockParser.java index d45aa177..883e8a8d 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/ImageAndGifBlockParser.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/ImageAndGifBlockParser.java @@ -33,7 +33,7 @@ public class ImageAndGifBlockParser extends AbstractBlockParser { } public static class Factory extends AbstractBlockParserFactory { - private final Pattern redditPreviewPattern = Pattern.compile("!\\[img]\\(https://preview.redd.it/\\w+.(jpg|png)((\\?+[-a-zA-Z0-9()@:%_+.~#?&/=]*)|)\\)"); + private final Pattern redditPreviewPattern = Pattern.compile("!\\[img]\\(https://preview.redd.it/\\w+.(jpg|png|jpeg)((\\?+[-a-zA-Z0-9()@:%_+.~#?&/=]*)|)\\)"); private final Pattern gifPattern = Pattern.compile("!\\[gif]\\(giphy\\|\\w+(\\|downsized)?\\)"); private Map<String, MediaMetadata> mediaMetadataMap; private final int fromIndex = "![img](https://preview.redd.it/".length(); diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/MarkdownUtils.java b/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/MarkdownUtils.java index 5ff708ed..1b4e92b0 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/MarkdownUtils.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/MarkdownUtils.java @@ -12,6 +12,7 @@ import io.noties.markwon.Markwon; import io.noties.markwon.MarkwonPlugin; import io.noties.markwon.ext.strikethrough.StrikethroughPlugin; import io.noties.markwon.inlineparser.BangInlineProcessor; +import io.noties.markwon.inlineparser.CloseBracketInlineProcessor; import io.noties.markwon.inlineparser.HtmlInlineProcessor; import io.noties.markwon.inlineparser.MarkwonInlineParserPlugin; import io.noties.markwon.linkify.LinkifyPlugin; @@ -31,6 +32,7 @@ public class MarkdownUtils { @NonNull public static Markwon createFullRedditMarkwon(@NonNull Context context, @NonNull MarkwonPlugin miscPlugin, + @NonNull EmoteCloseBracketInlineProcessor emoteCloseBracketInlineProcessor, @NonNull ImageAndGifPlugin imageAndGifPlugin, int markdownColor, int spoilerBackgroundColor, @@ -39,6 +41,9 @@ public class MarkdownUtils { .usePlugin(MarkwonInlineParserPlugin.create(plugin -> { plugin.excludeInlineProcessor(HtmlInlineProcessor.class); plugin.excludeInlineProcessor(BangInlineProcessor.class); + plugin.excludeInlineProcessor(CloseBracketInlineProcessor.class); + plugin.addInlineProcessor(new EmoteInlineProcessor()); + plugin.addInlineProcessor(emoteCloseBracketInlineProcessor); })) .usePlugin(miscPlugin) .usePlugin(SuperscriptPlugin.create()) @@ -49,6 +54,7 @@ public class MarkdownUtils { .setOnLinkLongClickListener(onLinkLongClickListener))) .usePlugin(LinkifyPlugin.create(Linkify.WEB_URLS)) .usePlugin(imageAndGifPlugin) + .usePlugin(EmotePlugin.create(context)) .usePlugin(TableEntryPlugin.create(context)) .build(); } diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/utils/Utils.java b/app/src/main/java/ml/docilealligator/infinityforreddit/utils/Utils.java index 0efed43b..d6b3ed6c 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/utils/Utils.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/utils/Utils.java @@ -75,7 +75,7 @@ public final class Utils { Pattern.compile("!\\[gif]\\(giphy\\|\\w+\\)"), Pattern.compile("!\\[gif]\\(giphy\\|\\w+\\|downsized\\)"), Pattern.compile("!\\[gif]\\(emote\\|\\w+\\|\\w+\\)"), - Pattern.compile("https://preview.redd.it/\\w+.(jpg|png)((\\?+[-a-zA-Z0-9()@:%_+.~#?&/=]*)|)") + Pattern.compile("https://preview.redd.it/\\w+.(jpg|png|jpeg)((\\?+[-a-zA-Z0-9()@:%_+.~#?&/=]*)|)") }; public static String modifyMarkdown(String markdown) { |