Show images in comments and post

Replace image links in posts and comments with actual images.
This commit is contained in:
Balazs Toldi 2023-08-03 13:50:51 +02:00 committed by Bazsalanszky
parent 926e1162f0
commit ffc2d669e5
13 changed files with 96 additions and 57 deletions

View File

@ -8,8 +8,8 @@ android {
applicationId "eu.toldi.infinityforlemmy"
minSdk 21
targetSdk 33
versionCode 126
versionName "0.0.6"
versionCode 128
versionName "0.0.8"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
javaCompileOptions {
annotationProcessorOptions {
@ -169,6 +169,7 @@ dependencies {
implementation "io.noties.markwon:recycler-table:$markwonVersion"
implementation "io.noties.markwon:simple-ext:$markwonVersion"
implementation "io.noties.markwon:inline-parser:$markwonVersion"
implementation "io.noties.markwon:image-glide:$markwonVersion"
implementation 'com.atlassian.commonmark:commonmark-ext-gfm-tables:0.14.0'
implementation 'me.saket:better-link-movement-method:2.2.0'

View File

@ -513,8 +513,8 @@ public class CommentActivity extends BaseActivity implements UploadImageEnabledA
int start = Math.max(binding.commentCommentEditText.getSelectionStart(), 0);
int end = Math.max(binding.commentCommentEditText.getSelectionEnd(), 0);
binding.commentCommentEditText.getText().replace(Math.min(start, end), Math.max(start, end),
"[" + uploadedImage.imageName + "](" + uploadedImage.imageUrl + ")",
0, "[]()".length() + uploadedImage.imageName.length() + uploadedImage.imageUrl.length());
"![" + uploadedImage.imageName + "](" + uploadedImage.imageUrl + ")",
0, "![]()".length() + uploadedImage.imageName.length() + uploadedImage.imageUrl.length());
}
@Override

View File

@ -345,7 +345,7 @@ public class EditCommentActivity extends BaseActivity implements UploadImageEnab
int start = Math.max(contentEditText.getSelectionStart(), 0);
int end = Math.max(contentEditText.getSelectionEnd(), 0);
contentEditText.getText().replace(Math.min(start, end), Math.max(start, end),
"[" + uploadedImage.imageName + "](" + uploadedImage.imageUrl + ")",
0, "[]()".length() + uploadedImage.imageName.length() + uploadedImage.imageUrl.length());
"![" + uploadedImage.imageName + "](" + uploadedImage.imageUrl + ")",
0, "![]()".length() + uploadedImage.imageName.length() + uploadedImage.imageUrl.length());
}
}

View File

@ -456,7 +456,7 @@ public class EditPostActivity extends BaseActivity implements UploadImageEnabled
int start = Math.max(contentEditText.getSelectionStart(), 0);
int end = Math.max(contentEditText.getSelectionEnd(), 0);
contentEditText.getText().replace(Math.min(start, end), Math.max(start, end),
"[" + uploadedImage.imageName + "](" + uploadedImage.imageUrl + ")",
0, "[]()".length() + uploadedImage.imageName.length() + uploadedImage.imageUrl.length());
"![" + uploadedImage.imageName + "](" + uploadedImage.imageUrl + ")",
0, "![]()".length() + uploadedImage.imageName.length() + uploadedImage.imageUrl.length());
}
}

View File

@ -792,7 +792,7 @@ public class PostImageActivity extends BaseActivity implements FlairBottomSheetF
int start = Math.max(contentEditText.getSelectionStart(), 0);
int end = Math.max(contentEditText.getSelectionEnd(), 0);
contentEditText.getText().replace(Math.min(start, end), Math.max(start, end),
"[" + uploadedImage.imageName + "](" + uploadedImage.imageUrl + ")",
0, "[]()".length() + uploadedImage.imageName.length() + uploadedImage.imageUrl.length());
"![" + uploadedImage.imageName + "](" + uploadedImage.imageUrl + ")",
0, "![]()".length() + uploadedImage.imageName.length() + uploadedImage.imageUrl.length());
}
}

View File

@ -722,7 +722,7 @@ public class PostLinkActivity extends BaseActivity implements FlairBottomSheetFr
int start = Math.max(contentEditText.getSelectionStart(), 0);
int end = Math.max(contentEditText.getSelectionEnd(), 0);
contentEditText.getText().replace(Math.min(start, end), Math.max(start, end),
"[" + uploadedImage.imageName + "](" + uploadedImage.imageUrl + ")",
0, "[]()".length() + uploadedImage.imageName.length() + uploadedImage.imageUrl.length());
"![" + uploadedImage.imageName + "](" + uploadedImage.imageUrl + ")",
0, "![]()".length() + uploadedImage.imageName.length() + uploadedImage.imageUrl.length());
}
}

View File

@ -673,8 +673,8 @@ public class PostTextActivity extends BaseActivity implements FlairBottomSheetFr
int start = Math.max(contentEditText.getSelectionStart(), 0);
int end = Math.max(contentEditText.getSelectionEnd(), 0);
contentEditText.getText().replace(Math.min(start, end), Math.max(start, end),
"[" + uploadedImage.imageName + "](" + uploadedImage.imageUrl + ")",
0, "[]()".length() + uploadedImage.imageName.length() + uploadedImage.imageUrl.length());
"![" + uploadedImage.imageName + "](" + uploadedImage.imageUrl + ")",
0, "![]()".length() + uploadedImage.imageName.length() + uploadedImage.imageUrl.length());
}
@Override

View File

@ -42,7 +42,7 @@ import io.noties.markwon.Markwon;
import io.noties.markwon.MarkwonConfiguration;
import io.noties.markwon.core.MarkwonTheme;
import io.noties.markwon.ext.strikethrough.StrikethroughPlugin;
import io.noties.markwon.inlineparser.BangInlineProcessor;
import io.noties.markwon.image.glide.GlideImagesPlugin;
import io.noties.markwon.inlineparser.HtmlInlineProcessor;
import io.noties.markwon.inlineparser.MarkwonInlineParserPlugin;
import io.noties.markwon.linkify.LinkifyPlugin;
@ -105,7 +105,6 @@ public class MessageRecyclerViewAdapter extends PagedListAdapter<CommentInteract
mMarkwon = Markwon.builder(mActivity)
.usePlugin(MarkwonInlineParserPlugin.create(plugin -> {
plugin.excludeInlineProcessor(HtmlInlineProcessor.class);
plugin.excludeInlineProcessor(BangInlineProcessor.class);
}))
.usePlugin(new AbstractMarkwonPlugin() {
@Override
@ -129,6 +128,7 @@ public class MessageRecyclerViewAdapter extends PagedListAdapter<CommentInteract
.usePlugin(StrikethroughPlugin.create())
.usePlugin(MovementMethodPlugin.create(new SpoilerAwareMovementMethod()))
.usePlugin(LinkifyPlugin.create(Linkify.WEB_URLS))
.usePlugin(GlideImagesPlugin.create(mActivity))
.build();
mAccessToken = accessToken;
if (where.equals(FetchMessage.WHERE_MESSAGES)) {

View File

@ -24,7 +24,6 @@ import java.util.TimeZone;
import java.util.concurrent.Executor;
import java.util.regex.Pattern;
import eu.toldi.infinityforlemmy.markdown.MarkdownUtils;
import eu.toldi.infinityforlemmy.utils.JSONUtils;
import eu.toldi.infinityforlemmy.utils.LemmyUtils;
@ -310,7 +309,7 @@ public class ParseComment {
e.printStackTrace();
}
}
String content = MarkdownUtils.processImageCaptions(commentObj.getString("content"), "Image");
String content = commentObj.getString("content");
String commentMarkdown = content;
String commentRawText = content;
String linkId = postObj.getString("id");

View File

@ -0,0 +1,59 @@
package eu.toldi.infinityforlemmy.markdown;
import android.content.Context;
import android.content.Intent;
import android.text.style.ClickableSpan;
import android.view.View;
import android.widget.Toast;
import androidx.annotation.NonNull;
import org.commonmark.node.Image;
import eu.toldi.infinityforlemmy.activities.ViewImageOrGifActivity;
import io.noties.markwon.AbstractMarkwonPlugin;
import io.noties.markwon.MarkwonConfiguration;
import io.noties.markwon.MarkwonSpansFactory;
import io.noties.markwon.RenderProps;
import io.noties.markwon.image.AsyncDrawableSpan;
import io.noties.markwon.image.ImageSpanFactory;
public class ClickableGlideImagesPlugin extends AbstractMarkwonPlugin {
public static Context context;
public ClickableGlideImagesPlugin(Context context) {
this.context = context;
}
public static ClickableGlideImagesPlugin create(Context context) {
return new ClickableGlideImagesPlugin(context);
}
@Override
public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) {
builder.setFactory(Image.class, new ClickableImageFactory());
}
class ClickableImageFactory extends ImageSpanFactory {
@Override
public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) {
AsyncDrawableSpan image = (AsyncDrawableSpan) super.getSpans(configuration, props);
ClickableSpan clickableSpan = new ClickableSpan() {
@Override
public void onClick(@NonNull View widget) {
Intent intent = new Intent(context, ViewImageOrGifActivity.class);
intent.putExtra(ViewImageOrGifActivity.EXTRA_IMAGE_URL_KEY, image.getDrawable().getDestination());
intent.putExtra(ViewImageOrGifActivity.EXTRA_FILE_NAME_KEY, image.getDrawable().getDestination());
context.startActivity(intent);
}
};
Object[] objects = new Object[2];
objects[0] = image;
objects[1] = clickableSpan;
return objects;
}
}
}

View File

@ -8,7 +8,6 @@ import androidx.annotation.Nullable;
import org.commonmark.ext.gfm.tables.TableBlock;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import eu.toldi.infinityforlemmy.R;
@ -16,6 +15,7 @@ import eu.toldi.infinityforlemmy.customviews.CustomMarkwonAdapter;
import io.noties.markwon.Markwon;
import io.noties.markwon.MarkwonPlugin;
import io.noties.markwon.ext.strikethrough.StrikethroughPlugin;
import io.noties.markwon.image.glide.GlideImagesPlugin;
import io.noties.markwon.inlineparser.BangInlineProcessor;
import io.noties.markwon.inlineparser.HtmlInlineProcessor;
import io.noties.markwon.inlineparser.MarkwonInlineParserPlugin;
@ -38,9 +38,9 @@ public class MarkdownUtils {
int spoilerBackgroundColor,
@Nullable BetterLinkMovementMethod.OnLinkLongClickListener onLinkLongClickListener) {
return Markwon.builder(context)
.usePlugin(GlideImagesPlugin.create(context))
.usePlugin(MarkwonInlineParserPlugin.create(plugin -> {
plugin.excludeInlineProcessor(HtmlInlineProcessor.class);
plugin.excludeInlineProcessor(BangInlineProcessor.class);
}))
.usePlugin(miscPlugin)
.usePlugin(SuperscriptPlugin.create())
@ -51,6 +51,7 @@ public class MarkdownUtils {
.setOnLinkLongClickListener(onLinkLongClickListener)))
.usePlugin(LinkifyPlugin.create(Linkify.WEB_URLS))
.usePlugin(TableEntryPlugin.create(context))
.usePlugin(ClickableGlideImagesPlugin.create(context))
.build();
}
@ -60,7 +61,6 @@ public class MarkdownUtils {
return Markwon.builder(context)
.usePlugin(MarkwonInlineParserPlugin.create(plugin -> {
plugin.excludeInlineProcessor(HtmlInlineProcessor.class);
plugin.excludeInlineProcessor(BangInlineProcessor.class);
}))
.usePlugin(miscPlugin)
.usePlugin(SuperscriptPlugin.create())
@ -70,6 +70,7 @@ public class MarkdownUtils {
.setOnLinkLongClickListener(onLinkLongClickListener)))
.usePlugin(LinkifyPlugin.create(Linkify.WEB_URLS))
.usePlugin(TableEntryPlugin.create(context))
.usePlugin(GlideImagesPlugin.create(context))
.build();
}
@ -104,6 +105,15 @@ public class MarkdownUtils {
.build();
}
@NonNull
public static MarkwonAdapter createTablesAndImagesAdapter(@NonNull Context context) {
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)))
.build();
}
/**
* Creates a CustomMarkwonAdapter configured with support for tables.
*/
@ -119,35 +129,4 @@ public class MarkdownUtils {
private static final Pattern emptyPattern = Pattern.compile("!\\[\\]\\((.*?)\\)");
private static final Pattern nonEmptyPattern = Pattern.compile("!\\[(.*?)\\]\\((.*?)\\)");
public static String processImageCaptions(String markdown, String replacementCaption) {
// Pattern for Markdown images with empty captions
// Pattern for Markdown images with non-empty captions
Matcher emptyMatcher = emptyPattern.matcher(markdown);
StringBuffer sb = new StringBuffer();
while (emptyMatcher.find()) {
// Replace the matched pattern with the same URL, but with a caption
emptyMatcher.appendReplacement(sb, "[" + replacementCaption + "](" + emptyMatcher.group(1) + ")");
}
// Append the rest of the content
emptyMatcher.appendTail(sb);
// Now process non-empty captions
Matcher nonEmptyMatcher = nonEmptyPattern.matcher(sb.toString());
StringBuffer finalSb = new StringBuffer();
while (nonEmptyMatcher.find()) {
// Replace the matched pattern with the same URL and caption, but without the "!"
nonEmptyMatcher.appendReplacement(finalSb, "[" + nonEmptyMatcher.group(1) + "](" + nonEmptyMatcher.group(2) + ")");
}
// Append the rest of the content
nonEmptyMatcher.appendTail(finalSb);
return finalSb.toString();
}
}

View File

@ -214,6 +214,7 @@ public class ParsePost {
String url = (!data.getJSONObject("post").isNull("url")) ? data.getJSONObject("post").getString("url") : "";
String communityURL = (!data.getJSONObject("community").isNull("icon")) ? data.getJSONObject("community").getString("icon") : "";
String authorAvatar = (!data.getJSONObject("creator").isNull("avatar")) ? data.getJSONObject("creator").getString("avatar") : null;
Uri uri = Uri.parse(url);
String path = uri.getPath();
@ -654,14 +655,14 @@ public class ParsePost {
post.setVoteType(data.getInt("my_vote"));
post.setScore(post.getScore() - 1);
}
if (!data.getJSONObject("post").isNull("body")) {
String body = MarkdownUtils.processImageCaptions(data.getJSONObject("post").getString("body"), "Image");
String body = data.getJSONObject("post").getString("body");
post.setSelfText(body);
post.setSelfTextPlain(body);
post.setSelfTextPlainTrimmed(body.trim());
}
post.setAuthorIconUrl(authorAvatar);
post.setSubredditIconUrl(communityURL);
return post;
}

View File

@ -382,8 +382,8 @@ public final class Utils {
int start = Math.max(editText.getSelectionStart(), 0);
int end = Math.max(editText.getSelectionEnd(), 0);
editText.getText().replace(Math.min(start, end), Math.max(start, end),
"[" + fileName + "](" + imageUrlOrError + ")",
0, "[]()".length() + fileName.length() + imageUrlOrError.length());
"![" + fileName + "](" + imageUrlOrError + ")",
0, "![]()".length() + fileName.length() + imageUrlOrError.length());
Snackbar.make(coordinatorLayout, R.string.upload_image_success, Snackbar.LENGTH_LONG).show();
} else {
Toast.makeText(context, R.string.upload_image_failed, Toast.LENGTH_LONG).show();