From adb60910debc552ab665e7685ee288dd0c0ee93a Mon Sep 17 00:00:00 2001 From: Docile-Alligator <25734209+Docile-Alligator@users.noreply.github.com> Date: Fri, 17 Nov 2023 08:58:33 -0500 Subject: Start showing images from preview.redd.it in posts. --- .../activities/CommentActivity.java | 8 +- .../activities/FullMarkdownActivity.java | 3 +- .../infinityforreddit/activities/WikiActivity.java | 2 +- .../adapters/PostDetailRecyclerViewAdapter.java | 2 +- .../adapters/RulesRecyclerViewAdapter.java | 4 +- .../fragments/SidebarFragment.java | 4 +- .../markdown/ImageAndGifBlock.java | 6 + .../markdown/ImageAndGifBlockParser.java | 50 ++++++ .../markdown/ImageAndGifEntry.java | 198 +++++++++++++++++++++ .../markdown/ImageAndGifPlugin.java | 23 +++ .../infinityforreddit/markdown/MarkdownUtils.java | 7 +- .../infinityforreddit/post/ParsePost.java | 8 +- .../infinityforreddit/utils/Utils.java | 27 ++- .../res/layout/markdown_image_and_gif_block.xml | 31 ++++ 14 files changed, 355 insertions(+), 18 deletions(-) create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/markdown/ImageAndGifBlock.java create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/markdown/ImageAndGifBlockParser.java create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/markdown/ImageAndGifEntry.java create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/markdown/ImageAndGifPlugin.java create mode 100644 app/src/main/res/layout/markdown_image_and_gif_block.xml (limited to 'app/src/main') 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 8a50549f..2a03a058 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/activities/CommentActivity.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/activities/CommentActivity.java @@ -1,7 +1,6 @@ package ml.docilealligator.infinityforreddit.activities; import android.content.ActivityNotFoundException; -import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.net.Uri; @@ -15,7 +14,6 @@ import android.text.TextUtils; import android.view.Menu; import android.view.MenuItem; import android.view.View; -import android.view.inputmethod.InputMethodManager; import android.widget.TextView; import android.widget.Toast; @@ -153,6 +151,8 @@ public class CommentActivity extends BaseActivity implements UploadImageEnabledA addOnOffsetChangedListener(binding.commentAppbarLayout); } + mGlide = Glide.with(this); + mAccessToken = mCurrentAccountSharedPreferences.getString(SharedPreferencesUtils.ACCESS_TOKEN, null); if (mAccessToken == null) { finish(); @@ -209,7 +209,7 @@ public class CommentActivity extends BaseActivity implements UploadImageEnabledA }; Markwon postBodyMarkwon = MarkdownUtils.createFullRedditMarkwon(this, miscPlugin, parentTextColor, parentSpoilerBackgroundColor, null); - MarkwonAdapter markwonAdapter = MarkdownUtils.createTablesAdapter(); + MarkwonAdapter markwonAdapter = MarkdownUtils.createTablesAdapter(this, mGlide); binding.commentContentMarkdownView.setLayoutManager(new LinearLayoutManagerBugFixed(this)); binding.commentContentMarkdownView.setAdapter(markwonAdapter); markwonAdapter.setMarkdown(postBodyMarkwon, parentBodyMarkdown); @@ -225,8 +225,6 @@ public class CommentActivity extends BaseActivity implements UploadImageEnabledA setSupportActionBar(binding.commentToolbar); - mGlide = Glide.with(this); - if (savedInstanceState != null) { selectedAccount = savedInstanceState.getParcelable(SELECTED_ACCOUNT_STATE); uploadedImages = savedInstanceState.getParcelableArrayList(UPLOADED_IMAGES_STATE); 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 380ef30a..aac1cc82 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/activities/FullMarkdownActivity.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/activities/FullMarkdownActivity.java @@ -18,6 +18,7 @@ import androidx.appcompat.widget.Toolbar; import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.recyclerview.widget.RecyclerView; +import com.bumptech.glide.Glide; import com.google.android.material.appbar.AppBarLayout; import com.google.android.material.appbar.CollapsingToolbarLayout; @@ -141,7 +142,7 @@ public class FullMarkdownActivity extends BaseActivity { Markwon markwon = MarkdownUtils.createFullRedditMarkwon(this, miscPlugin, markdownColor, spoilerBackgroundColor, null); - MarkwonAdapter markwonAdapter = MarkdownUtils.createTablesAdapter(); + MarkwonAdapter markwonAdapter = MarkdownUtils.createTablesAdapter(this, Glide.with(this)); LinearLayoutManagerBugFixed linearLayoutManager = new SwipeLockLinearLayoutManager(this, new SwipeLockInterface() { @Override public void lockSwipe() { 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 d7dd20da..78b957d3 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/activities/WikiActivity.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/activities/WikiActivity.java @@ -178,7 +178,7 @@ public class WikiActivity extends BaseActivity { markwon = MarkdownUtils.createFullRedditMarkwon(this, miscPlugin, markdownColor, spoilerBackgroundColor, onLinkLongClickListener); - markwonAdapter = MarkdownUtils.createTablesAdapter(); + markwonAdapter = MarkdownUtils.createTablesAdapter(this, mGlide); LinearLayoutManagerBugFixed linearLayoutManager = new SwipeLockLinearLayoutManager(this, new SwipeLockInterface() { @Override public void lockSwipe() { 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 9137aece..0b90112e 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/PostDetailRecyclerViewAdapter.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/PostDetailRecyclerViewAdapter.java @@ -289,7 +289,7 @@ public class PostDetailRecyclerViewAdapter extends RecyclerView.Adapter { + private BaseActivity baseActivity; + private RequestManager glide; + + public ImageAndGifEntry(BaseActivity baseActivity, RequestManager glide) { + this.baseActivity = baseActivity; + this.glide = glide; + } + + @NonNull + @Override + public Holder createHolder(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) { + return new Holder(MarkdownImageAndGifBlockBinding.inflate(inflater, parent, false)); + } + + @Override + public void bindHolder(@NonNull Markwon markwon, @NonNull Holder holder, @NonNull ImageAndGifBlock node) { + holder.binding.progressBarMarkdownImageAndGifBlock.setVisibility(View.VISIBLE); + + RequestBuilder imageRequestBuilder = glide.load("https://upload.wikimedia.org/wikipedia/commons/thumb/b/b6/Image_created_with_a_mobile_phone.png/1280px-Image_created_with_a_mobile_phone.png").listener(holder.requestListener); + boolean blurImage = false; + if (blurImage) { + imageRequestBuilder.apply(RequestOptions.bitmapTransform(new BlurTransformation(50, 10))) + .into(holder.binding.imageViewMarkdownImageAndGifBlock); + } else { + imageRequestBuilder.centerInside().into(holder.binding.imageViewMarkdownImageAndGifBlock); + } + } + + @Override + public void onViewRecycled(@NonNull Holder holder) { + super.onViewRecycled(holder); + glide.clear(holder.binding.imageViewMarkdownImageAndGifBlock); + holder.binding.progressBarMarkdownImageAndGifBlock.setVisibility(View.GONE); + holder.binding.loadImageErrorTextViewMarkdownImageAndGifBlock.setVisibility(View.GONE); + } + + public class Holder extends MarkwonAdapter.Holder { + MarkdownImageAndGifBlockBinding binding; + RequestListener requestListener; + + public Holder(@NonNull MarkdownImageAndGifBlockBinding binding) { + super(binding.getRoot()); + this.binding = binding; + + requestListener = new RequestListener<>() { + @Override + public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) { + binding.progressBarMarkdownImageAndGifBlock.setVisibility(View.GONE); + binding.loadImageErrorTextViewMarkdownImageAndGifBlock.setVisibility(View.VISIBLE); + return false; + } + + @Override + public boolean onResourceReady(Drawable resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) { + binding.progressBarMarkdownImageAndGifBlock.setVisibility(View.GONE); + return false; + } + }; + } + + /*private final RequestListener requestListener = new RequestListener() { + @Override + public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) { + binding.progressBar.setVisibility(View.GONE); + return false; + } + + @Override + public boolean onResourceReady(Drawable resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) { + binding.progressBar.setVisibility(View.GONE); + return false; + } + };*/ + + /*public Holder(@NonNull AdapterGifEntryBinding binding) { + super(binding.getRoot()); + this.binding = binding; + + binding.progressBar.setIndeterminateTintList(ColorStateList.valueOf(customThemeWrapper.getColorAccent())); + binding.giphyWatermark.setTextColor(customThemeWrapper.getCommentColor()); + + binding.gifLink.setTextColor(customThemeWrapper.getLinkColor()); + + }*/ + + /*void bindImage(ImageMetadata image) { + binding.gifLink.setVisibility(View.GONE); + binding.iv.setVisibility(View.VISIBLE); + binding.progressBar.setVisibility(View.VISIBLE); + binding.giphyWatermark.setVisibility(View.GONE); + + ViewGroup.LayoutParams params = binding.iv.getLayoutParams(); + if (image.x > image.y) { + params.height = dpToPx(160); + params.width = params.height * image.x / image.y; + } else { + params.width = dpToPx(160); + params.height = params.width * image.y / image.x; + } + binding.iv.setLayoutParams(params); + + // todo: check if waitForLayout is necessary here since we explicitly set width/height in LP + Target target = new DrawableImageViewTarget(binding.iv) + .waitForLayout(); + glide.load(image.getUrl()) + .addListener(requestListener) + .error(R.drawable.ic_error_outline_black_24dp) + .into(target); + } + + void bindGif(GiphyGifMetadata gif) { + if (!canLoadGif()) { + // video autoplay is disabled, don't load gif + binding.gifLink.setVisibility(View.VISIBLE); + binding.iv.setVisibility(View.GONE); + binding.progressBar.setVisibility(View.GONE); + binding.giphyWatermark.setVisibility(View.GONE); + + binding.gifLink.setOnClickListener(v -> { + onClickListener.accept(Uri.parse(gif.getGifUrl())); + }); + return; + } + binding.gifLink.setVisibility(View.GONE); + binding.iv.setVisibility(View.VISIBLE); + binding.progressBar.setVisibility(View.VISIBLE); + binding.giphyWatermark.setVisibility(View.VISIBLE); + + ViewGroup.LayoutParams params = binding.iv.getLayoutParams(); + if (gif.x > gif.y) { + params.height = dpToPx(160); + params.width = params.height * gif.x / gif.y; + } else { + params.width = dpToPx(160); + params.height = params.width * gif.y / gif.x; + } + binding.iv.setLayoutParams(params); + + // todo: check if waitForLayout is necessary here since we explicitly set width/height in LP + Target target = new DrawableImageViewTarget(binding.iv) + .waitForLayout(); + glide.load(gif.getGifUrl()) + .addListener(requestListener) + .error(R.drawable.ic_error_outline_black_24dp) + .into(target); + } + + void recycle() { + glide.clear(binding.iv); + } + + @SuppressWarnings("SameParameterValue") + private int dpToPx(int dp) { + float density = itemView.getContext().getResources().getDisplayMetrics().density; + return (int) (dp * density); + } + + private boolean canLoadGif() { + // ideally this would be injected, but it is a bit unpleasant to do + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(itemView.getContext()); + String dataSavingMode = sharedPreferences.getString(SharedPreferencesUtils.DATA_SAVING_MODE, SharedPreferencesUtils.DATA_SAVING_MODE_OFF); + Log.i("GifEntry", "datasaving=" + dataSavingMode); + if (dataSavingMode.equals(SharedPreferencesUtils.DATA_SAVING_MODE_ALWAYS)) { + return false; + } else if (dataSavingMode.equals(SharedPreferencesUtils.DATA_SAVING_MODE_ONLY_ON_CELLULAR_DATA)) { + int networkType = Utils.getConnectedNetwork(itemView.getContext()); + return networkType != Utils.NETWORK_TYPE_CELLULAR; + } else { + return true; + } + }*/ + } +} diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/ImageAndGifPlugin.java b/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/ImageAndGifPlugin.java new file mode 100644 index 00000000..04e95b23 --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/ImageAndGifPlugin.java @@ -0,0 +1,23 @@ +package ml.docilealligator.infinityforreddit.markdown; + +import android.util.Log; + +import androidx.annotation.NonNull; + +import org.commonmark.parser.Parser; + +import io.noties.markwon.AbstractMarkwonPlugin; + +public class ImageAndGifPlugin extends AbstractMarkwonPlugin { + @NonNull + @Override + public String processMarkdown(@NonNull String markdown) { + Log.i("asdfa", "imageandgifplugin " + markdown + "fuck"); + return super.processMarkdown(markdown); + } + + @Override + public void configureParser(@NonNull Parser.Builder builder) { + builder.customBlockParserFactory(new ImageAndGifBlockParser.Factory()); + } +} 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 2c56fc9c..aaaef7c8 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/MarkdownUtils.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/MarkdownUtils.java @@ -6,6 +6,8 @@ import android.text.util.Linkify; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.bumptech.glide.RequestManager; + import org.commonmark.ext.gfm.tables.TableBlock; import io.noties.markwon.Markwon; @@ -21,6 +23,7 @@ import io.noties.markwon.recycler.table.TableEntry; import io.noties.markwon.recycler.table.TableEntryPlugin; import me.saket.bettermovementmethod.BetterLinkMovementMethod; import ml.docilealligator.infinityforreddit.R; +import ml.docilealligator.infinityforreddit.activities.BaseActivity; import ml.docilealligator.infinityforreddit.customviews.CustomMarkwonAdapter; public class MarkdownUtils { @@ -47,6 +50,7 @@ public class MarkdownUtils { .usePlugin(MovementMethodPlugin.create(new SpoilerAwareMovementMethod() .setOnLinkLongClickListener(onLinkLongClickListener))) .usePlugin(LinkifyPlugin.create(Linkify.WEB_URLS)) + .usePlugin(new ImageAndGifPlugin()) .usePlugin(TableEntryPlugin.create(context)) .build(); } @@ -93,11 +97,12 @@ public class MarkdownUtils { * Creates a MarkwonAdapter configured with support for tables. */ @NonNull - public static MarkwonAdapter createTablesAdapter() { + public static MarkwonAdapter createTablesAdapter(BaseActivity baseActivity, RequestManager glide) { return MarkwonAdapter.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))) + .include(ImageAndGifBlock.class, new ImageAndGifEntry(baseActivity, glide)) .build(); } diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/post/ParsePost.java b/app/src/main/java/ml/docilealligator/infinityforreddit/post/ParsePost.java index ffdc4328..14fc4187 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/post/ParsePost.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/post/ParsePost.java @@ -319,7 +319,7 @@ public class ParsePost { if (data.isNull(JSONUtils.SELFTEXT_KEY)) { post.setSelfText(""); } else { - post.setSelfText(Utils.modifyMarkdown(Utils.trimTrailingWhitespace(data.getString(JSONUtils.SELFTEXT_KEY)))); + post.setSelfText(Utils.modifyMarkdown(Utils.parseInlineRedditImages(Utils.trimTrailingWhitespace(data.getString(JSONUtils.SELFTEXT_KEY))))); } String authority = uri.getAuthority(); @@ -505,7 +505,7 @@ public class ParsePost { if (data.isNull(JSONUtils.SELFTEXT_KEY)) { post.setSelfText(""); } else { - post.setSelfText(Utils.modifyMarkdown(Utils.trimTrailingWhitespace(data.getString(JSONUtils.SELFTEXT_KEY)))); + post.setSelfText(Utils.modifyMarkdown(Utils.parseInlineRedditImages(Utils.trimTrailingWhitespace(data.getString(JSONUtils.SELFTEXT_KEY))))); } post.setPreviews(previews); @@ -580,7 +580,7 @@ public class ParsePost { if (data.isNull(JSONUtils.SELFTEXT_KEY)) { post.setSelfText(""); } else { - post.setSelfText(Utils.modifyMarkdown(Utils.trimTrailingWhitespace(data.getString(JSONUtils.SELFTEXT_KEY)))); + post.setSelfText(Utils.modifyMarkdown(Utils.parseInlineRedditImages(Utils.trimTrailingWhitespace(data.getString(JSONUtils.SELFTEXT_KEY))))); } String authority = uri.getAuthority(); @@ -724,7 +724,7 @@ public class ParsePost { if (data.isNull(JSONUtils.SELFTEXT_KEY)) { post.setSelfText(""); } else { - String selfText = Utils.modifyMarkdown(Utils.trimTrailingWhitespace(data.getString(JSONUtils.SELFTEXT_KEY))); + String selfText = Utils.modifyMarkdown(Utils.parseInlineRedditImages(Utils.trimTrailingWhitespace(data.getString(JSONUtils.SELFTEXT_KEY)))); post.setSelfText(selfText); if (data.isNull(JSONUtils.SELFTEXT_HTML_KEY)) { post.setSelfTextPlainTrimmed(""); 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 40bc8cd1..9ddd9643 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/utils/Utils.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/utils/Utils.java @@ -19,6 +19,7 @@ import android.text.Spannable; import android.text.SpannableStringBuilder; import android.text.style.TypefaceSpan; import android.util.DisplayMetrics; +import android.util.Log; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; @@ -76,6 +77,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()@:%_+.~#?&/=]*)|)") }; public static String modifyMarkdown(String markdown) { @@ -91,14 +93,14 @@ public final class Utils { Pattern inlineGifPattern = REGEX_PATTERNS[3]; Matcher matcher = inlineGifPattern.matcher(markdownStringBuilder); while (matcher.find()) { - markdownStringBuilder.replace(matcher.start(), matcher.end(), "[gif](https://i.giphy.com/media/" + markdownStringBuilder.substring(matcher.start() + "![gif](giphy|".length(), matcher.end() - 1) + "/giphy.mp4)"); + markdownStringBuilder.replace(matcher.start(), matcher.end(), "![gif](https://i.giphy.com/media/" + markdownStringBuilder.substring(matcher.start() + "![gif](giphy|".length(), matcher.end() - 1) + "/giphy.mp4)"); matcher = inlineGifPattern.matcher(markdownStringBuilder); } Pattern inlineGifPattern2 = REGEX_PATTERNS[4]; Matcher matcher2 = inlineGifPattern2.matcher(markdownStringBuilder); while (matcher2.find()) { - markdownStringBuilder.replace(matcher2.start(), matcher2.end(), "[gif](https://i.giphy.com/media/" + markdownStringBuilder.substring(matcher2.start() + "![gif](giphy|".length(), matcher2.end() - "|downsized\\)".length() + 1) + "/giphy.mp4)"); + markdownStringBuilder.replace(matcher2.start(), matcher2.end(), "![gif](https://i.giphy.com/media/" + markdownStringBuilder.substring(matcher2.start() + "![gif](giphy|".length(), matcher2.end() - "|downsized\\)".length() + 1) + "/giphy.mp4)"); matcher2 = inlineGifPattern2.matcher(markdownStringBuilder); } @@ -106,7 +108,7 @@ public final class Utils { Matcher matcher3 = inlineGifPattern3.matcher(markdownStringBuilder); while (matcher3.find()) { markdownStringBuilder.replace(matcher3.start(), matcher3.end(), - "[gif](https://reddit-meta-production.s3.amazonaws.com/public/fortnitebr/emotes/snoomoji_emotes/" + "![gif](https://reddit-meta-production.s3.amazonaws.com/public/fortnitebr/emotes/snoomoji_emotes/" + markdownStringBuilder.substring( matcher3.start() + "![gif](emote|".length(), matcher3.end() - 1).replace('|', '/') + ".gif)"); matcher3 = inlineGifPattern3.matcher(markdownStringBuilder); @@ -148,6 +150,25 @@ public final class Utils { return markdown; } + public static String parseInlineRedditImages(String markdown) { + StringBuilder markdownStringBuilder = new StringBuilder(markdown); + Pattern inlineRedditImagePattern = REGEX_PATTERNS[6]; + Matcher matcher = inlineRedditImagePattern.matcher(markdownStringBuilder); + int start = 0; + while (matcher.find(start)) { + int fileNameStartIndex = "https://preview.redd.it/".length(); + String replacingText = "![" + /*markdownStringBuilder.substring(fileNameStartIndex, + markdownStringBuilder.indexOf(".", fileNameStartIndex))*/"img" + + "](" + markdownStringBuilder.substring(matcher.start(), matcher.end()) + ")"; + markdownStringBuilder.replace(matcher.start(), matcher.end(), replacingText); + start = replacingText.length() + matcher.start(); + matcher = inlineRedditImagePattern.matcher(markdownStringBuilder); + Log.i("asdf", "S " + start + markdownStringBuilder.length()); + } + + return markdownStringBuilder.toString(); + } + public static String trimTrailingWhitespace(String source) { if (source == null) { diff --git a/app/src/main/res/layout/markdown_image_and_gif_block.xml b/app/src/main/res/layout/markdown_image_and_gif_block.xml new file mode 100644 index 00000000..95746933 --- /dev/null +++ b/app/src/main/res/layout/markdown_image_and_gif_block.xml @@ -0,0 +1,31 @@ + + + + + + + + + + \ No newline at end of file -- cgit v1.2.3