Merge pull request #537 from scria1000/more-regex-fix

Refactor spoiler parsing into its own class
This commit is contained in:
Docile-Alligator 2021-11-27 20:19:44 +08:00 committed by GitHub
commit 091dd51801
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 382 additions and 299 deletions

View File

@ -72,7 +72,7 @@ import ml.docilealligator.infinityforreddit.customtheme.CustomThemeWrapper;
import ml.docilealligator.infinityforreddit.customviews.LinearLayoutManagerBugFixed; import ml.docilealligator.infinityforreddit.customviews.LinearLayoutManagerBugFixed;
import ml.docilealligator.infinityforreddit.events.SwitchAccountEvent; import ml.docilealligator.infinityforreddit.events.SwitchAccountEvent;
import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils; import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils;
import ml.docilealligator.infinityforreddit.utils.SuperscriptInlineProcessor; import ml.docilealligator.infinityforreddit.markdown.SuperscriptInlineProcessor;
import ml.docilealligator.infinityforreddit.utils.Utils; import ml.docilealligator.infinityforreddit.utils.Utils;
import retrofit2.Retrofit; import retrofit2.Retrofit;

View File

@ -132,7 +132,7 @@ public class EditCommentActivity extends BaseActivity implements UploadImageEnab
mFullName = getIntent().getStringExtra(EXTRA_FULLNAME); mFullName = getIntent().getStringExtra(EXTRA_FULLNAME);
mAccessToken = mCurrentAccountSharedPreferences.getString(SharedPreferencesUtils.ACCESS_TOKEN, null); mAccessToken = mCurrentAccountSharedPreferences.getString(SharedPreferencesUtils.ACCESS_TOKEN, null);
mCommentContent = getIntent().getStringExtra(EXTRA_CONTENT).replaceAll(">",">"); mCommentContent = getIntent().getStringExtra(EXTRA_CONTENT);
contentEditText.setText(mCommentContent); contentEditText.setText(mCommentContent);
if (savedInstanceState != null) { if (savedInstanceState != null) {

View File

@ -141,7 +141,7 @@ public class EditPostActivity extends BaseActivity implements UploadImageEnabled
mFullName = getIntent().getStringExtra(EXTRA_FULLNAME); mFullName = getIntent().getStringExtra(EXTRA_FULLNAME);
mAccessToken = mCurrentAccountSharedPreferences.getString(SharedPreferencesUtils.ACCESS_TOKEN, null); mAccessToken = mCurrentAccountSharedPreferences.getString(SharedPreferencesUtils.ACCESS_TOKEN, null);
titleTextView.setText(getIntent().getStringExtra(EXTRA_TITLE)); titleTextView.setText(getIntent().getStringExtra(EXTRA_TITLE));
mPostContent = getIntent().getStringExtra(EXTRA_CONTENT).replaceAll(">",">");; mPostContent = getIntent().getStringExtra(EXTRA_CONTENT);
contentEditText.setText(mPostContent); contentEditText.setText(mPostContent);
if (savedInstanceState != null) { if (savedInstanceState != null) {

View File

@ -61,8 +61,10 @@ import ml.docilealligator.infinityforreddit.customtheme.CustomThemeWrapper;
import ml.docilealligator.infinityforreddit.customviews.LinearLayoutManagerBugFixed; import ml.docilealligator.infinityforreddit.customviews.LinearLayoutManagerBugFixed;
import ml.docilealligator.infinityforreddit.customviews.MarkwonLinearLayoutManager; import ml.docilealligator.infinityforreddit.customviews.MarkwonLinearLayoutManager;
import ml.docilealligator.infinityforreddit.events.SwitchAccountEvent; import ml.docilealligator.infinityforreddit.events.SwitchAccountEvent;
import ml.docilealligator.infinityforreddit.markdown.SpoilerParserPlugin;
import ml.docilealligator.infinityforreddit.markdown.SpoilerSpan;
import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils; import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils;
import ml.docilealligator.infinityforreddit.utils.SuperscriptInlineProcessor; import ml.docilealligator.infinityforreddit.markdown.SuperscriptInlineProcessor;
import ml.docilealligator.infinityforreddit.utils.Utils; import ml.docilealligator.infinityforreddit.utils.Utils;
public class FullMarkdownActivity extends BaseActivity { public class FullMarkdownActivity extends BaseActivity {
@ -151,51 +153,6 @@ public class FullMarkdownActivity extends BaseActivity {
return super.processMarkdown(Utils.fixSuperScript(markdown)); return super.processMarkdown(Utils.fixSuperScript(markdown));
} }
@Override
public void afterSetText(@NonNull TextView textView) {
textView.setHighlightColor(Color.TRANSPARENT);
SpannableStringBuilder markdownStringBuilder = new SpannableStringBuilder(textView.getText());
Pattern spoilerPattern = Pattern.compile(">![\\S\\s]+?!<");
Matcher matcher = spoilerPattern.matcher(markdownStringBuilder);
int start = 0;
boolean find = false;
while (matcher.find(start)) {
if (markdownStringBuilder.length() < 4
|| matcher.start() < 0
|| matcher.end() > markdownStringBuilder.length()) {
break;
}
find = true;
markdownStringBuilder.delete(matcher.end() - 2, matcher.end());
markdownStringBuilder.delete(matcher.start(), matcher.start() + 2);
ClickableSpan clickableSpan = new ClickableSpan() {
private boolean isShowing = false;
@Override
public void updateDrawState(@NonNull TextPaint ds) {
if (isShowing) {
super.updateDrawState(ds);
ds.setColor(markdownColor);
} else {
ds.bgColor = spoilerBackgroundColor;
ds.setColor(markdownColor);
}
ds.setUnderlineText(false);
}
@Override
public void onClick(@NonNull View view) {
isShowing = !isShowing;
view.invalidate();
}
};
markdownStringBuilder.setSpan(clickableSpan, matcher.start(), matcher.end() - 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
start = matcher.end() - 4;
}
if (find) {
textView.setText(markdownStringBuilder);
}
}
@Override @Override
public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) { public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) {
textView.setTextColor(markdownColor); textView.setTextColor(markdownColor);
@ -217,6 +174,7 @@ public class FullMarkdownActivity extends BaseActivity {
builder.linkColor(linkColor); builder.linkColor(linkColor);
} }
}) })
.usePlugin(SpoilerParserPlugin.create(markdownColor, spoilerBackgroundColor))
.usePlugin(StrikethroughPlugin.create()) .usePlugin(StrikethroughPlugin.create())
.usePlugin(LinkifyPlugin.create(Linkify.WEB_URLS)) .usePlugin(LinkifyPlugin.create(Linkify.WEB_URLS))
.usePlugin(TableEntryPlugin.create(this)) .usePlugin(TableEntryPlugin.create(this))

View File

@ -67,9 +67,11 @@ import ml.docilealligator.infinityforreddit.customtheme.CustomThemeWrapper;
import ml.docilealligator.infinityforreddit.customviews.LinearLayoutManagerBugFixed; import ml.docilealligator.infinityforreddit.customviews.LinearLayoutManagerBugFixed;
import ml.docilealligator.infinityforreddit.customviews.MarkwonLinearLayoutManager; import ml.docilealligator.infinityforreddit.customviews.MarkwonLinearLayoutManager;
import ml.docilealligator.infinityforreddit.events.SwitchAccountEvent; import ml.docilealligator.infinityforreddit.events.SwitchAccountEvent;
import ml.docilealligator.infinityforreddit.markdown.SpoilerParserPlugin;
import ml.docilealligator.infinityforreddit.markdown.SpoilerSpan;
import ml.docilealligator.infinityforreddit.utils.JSONUtils; import ml.docilealligator.infinityforreddit.utils.JSONUtils;
import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils; import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils;
import ml.docilealligator.infinityforreddit.utils.SuperscriptInlineProcessor; import ml.docilealligator.infinityforreddit.markdown.SuperscriptInlineProcessor;
import ml.docilealligator.infinityforreddit.utils.Utils; import ml.docilealligator.infinityforreddit.utils.Utils;
import retrofit2.Call; import retrofit2.Call;
import retrofit2.Callback; import retrofit2.Callback;
@ -180,51 +182,6 @@ public class WikiActivity extends BaseActivity {
return super.processMarkdown(Utils.fixSuperScript(markdown)); return super.processMarkdown(Utils.fixSuperScript(markdown));
} }
@Override
public void afterSetText(@NonNull TextView textView) {
textView.setHighlightColor(Color.TRANSPARENT);
SpannableStringBuilder markdownStringBuilder = new SpannableStringBuilder(textView.getText());
Pattern spoilerPattern = Pattern.compile(">![\\S\\s]+?!<");
Matcher matcher = spoilerPattern.matcher(markdownStringBuilder);
int start = 0;
boolean find = false;
while (matcher.find(start)) {
if (markdownStringBuilder.length() < 4
|| matcher.start() < 0
|| matcher.end() > markdownStringBuilder.length()) {
break;
}
find = true;
markdownStringBuilder.delete(matcher.end() - 2, matcher.end());
markdownStringBuilder.delete(matcher.start(), matcher.start() + 2);
ClickableSpan clickableSpan = new ClickableSpan() {
private boolean isShowing = false;
@Override
public void updateDrawState(@NonNull TextPaint ds) {
if (isShowing) {
super.updateDrawState(ds);
ds.setColor(markdownColor);
} else {
ds.bgColor = spoilerBackgroundColor;
ds.setColor(markdownColor);
}
ds.setUnderlineText(false);
}
@Override
public void onClick(@NonNull View view) {
isShowing = !isShowing;
view.invalidate();
}
};
markdownStringBuilder.setSpan(clickableSpan, matcher.start(), matcher.end() - 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
start = matcher.end() - 4;
}
if (find) {
textView.setText(markdownStringBuilder);
}
}
@Override @Override
public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) { public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) {
textView.setTextColor(markdownColor); textView.setTextColor(markdownColor);
@ -245,6 +202,7 @@ public class WikiActivity extends BaseActivity {
builder.linkColor(linkColor); builder.linkColor(linkColor);
} }
}) })
.usePlugin(SpoilerParserPlugin.create(markdownColor, spoilerBackgroundColor))
.usePlugin(StrikethroughPlugin.create()) .usePlugin(StrikethroughPlugin.create())
.usePlugin(LinkifyPlugin.create(Linkify.WEB_URLS)) .usePlugin(LinkifyPlugin.create(Linkify.WEB_URLS))
.usePlugin(TableEntryPlugin.create(this)) .usePlugin(TableEntryPlugin.create(this))

View File

@ -63,9 +63,11 @@ import ml.docilealligator.infinityforreddit.comment.Comment;
import ml.docilealligator.infinityforreddit.customtheme.CustomThemeWrapper; import ml.docilealligator.infinityforreddit.customtheme.CustomThemeWrapper;
import ml.docilealligator.infinityforreddit.customviews.CommentIndentationView; import ml.docilealligator.infinityforreddit.customviews.CommentIndentationView;
import ml.docilealligator.infinityforreddit.customviews.SpoilerOnClickTextView; import ml.docilealligator.infinityforreddit.customviews.SpoilerOnClickTextView;
import ml.docilealligator.infinityforreddit.markdown.SpoilerParserPlugin;
import ml.docilealligator.infinityforreddit.markdown.SpoilerSpan;
import ml.docilealligator.infinityforreddit.utils.APIUtils; import ml.docilealligator.infinityforreddit.utils.APIUtils;
import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils; import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils;
import ml.docilealligator.infinityforreddit.utils.SuperscriptInlineProcessor; import ml.docilealligator.infinityforreddit.markdown.SuperscriptInlineProcessor;
import ml.docilealligator.infinityforreddit.utils.Utils; import ml.docilealligator.infinityforreddit.utils.Utils;
import retrofit2.Retrofit; import retrofit2.Retrofit;
@ -136,56 +138,6 @@ public class CommentsListingRecyclerViewAdapter extends PagedListAdapter<Comment
return super.processMarkdown(Utils.fixSuperScript(markdown)); return super.processMarkdown(Utils.fixSuperScript(markdown));
} }
@Override
public void afterSetText(@NonNull TextView textView) {
textView.setHighlightColor(Color.TRANSPARENT);
SpannableStringBuilder markdownStringBuilder = new SpannableStringBuilder(textView.getText());
Pattern spoilerPattern = Pattern.compile(">![\\S\\s]+?!<");
Matcher matcher = spoilerPattern.matcher(markdownStringBuilder);
int start = 0;
boolean find = false;
while (matcher.find(start)) {
if (markdownStringBuilder.length() < 4
|| matcher.start() < 0
|| matcher.end() > markdownStringBuilder.length()) {
break;
}
find = true;
markdownStringBuilder.delete(matcher.end() - 2, matcher.end());
markdownStringBuilder.delete(matcher.start(), matcher.start() + 2);
int matcherStart = matcher.start();
int matcherEnd = matcher.end();
ClickableSpan clickableSpan = new ClickableSpan() {
private boolean isShowing = false;
@Override
public void updateDrawState(@NonNull TextPaint ds) {
if (isShowing) {
super.updateDrawState(ds);
ds.setColor(mCommentColor);
} else {
ds.bgColor = commentSpoilerBackgroundColor;
ds.setColor(mCommentColor);
}
ds.setUnderlineText(false);
}
@Override
public void onClick(@NonNull View view) {
if (textView instanceof SpoilerOnClickTextView) {
((SpoilerOnClickTextView) textView).setSpoilerOnClick(true);
}
isShowing = !isShowing;
view.invalidate();
}
};
markdownStringBuilder.setSpan(clickableSpan, matcherStart, matcherEnd - 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
start = matcherEnd - 4;
}
if (find) {
textView.setText(markdownStringBuilder);
}
}
@Override @Override
public void configureTheme(@NonNull MarkwonTheme.Builder builder) { public void configureTheme(@NonNull MarkwonTheme.Builder builder) {
builder.linkColor(customThemeWrapper.getLinkColor()); builder.linkColor(customThemeWrapper.getLinkColor());
@ -202,6 +154,7 @@ public class CommentsListingRecyclerViewAdapter extends PagedListAdapter<Comment
} }
}) })
.usePlugin(SpoilerParserPlugin.create(mCommentColor, commentSpoilerBackgroundColor))
.usePlugin(LinkifyPlugin.create(Linkify.WEB_URLS)) .usePlugin(LinkifyPlugin.create(Linkify.WEB_URLS))
.usePlugin(StrikethroughPlugin.create()) .usePlugin(StrikethroughPlugin.create())
.build(); .build();

View File

@ -9,10 +9,6 @@ import android.graphics.drawable.Drawable;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextPaint;
import android.text.style.ClickableSpan;
import android.text.util.Linkify; import android.text.util.Linkify;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
@ -38,8 +34,6 @@ import com.lsjwzh.widget.materialloadingprogressbar.CircleProgressBar;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Locale; import java.util.Locale;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import butterknife.BindView; import butterknife.BindView;
import butterknife.ButterKnife; import butterknife.ButterKnife;
@ -50,6 +44,7 @@ import io.noties.markwon.core.MarkwonTheme;
import io.noties.markwon.ext.strikethrough.StrikethroughPlugin; import io.noties.markwon.ext.strikethrough.StrikethroughPlugin;
import io.noties.markwon.html.HtmlPlugin; import io.noties.markwon.html.HtmlPlugin;
import io.noties.markwon.html.tag.SuperScriptHandler; import io.noties.markwon.html.tag.SuperScriptHandler;
import io.noties.markwon.inlineparser.BackslashInlineProcessor;
import io.noties.markwon.inlineparser.BangInlineProcessor; import io.noties.markwon.inlineparser.BangInlineProcessor;
import io.noties.markwon.inlineparser.HtmlInlineProcessor; import io.noties.markwon.inlineparser.HtmlInlineProcessor;
import io.noties.markwon.inlineparser.MarkwonInlineParserPlugin; import io.noties.markwon.inlineparser.MarkwonInlineParserPlugin;
@ -71,10 +66,11 @@ import ml.docilealligator.infinityforreddit.customtheme.CustomThemeWrapper;
import ml.docilealligator.infinityforreddit.customviews.CommentIndentationView; import ml.docilealligator.infinityforreddit.customviews.CommentIndentationView;
import ml.docilealligator.infinityforreddit.customviews.SpoilerOnClickTextView; import ml.docilealligator.infinityforreddit.customviews.SpoilerOnClickTextView;
import ml.docilealligator.infinityforreddit.fragments.ViewPostDetailFragment; import ml.docilealligator.infinityforreddit.fragments.ViewPostDetailFragment;
import ml.docilealligator.infinityforreddit.markdown.SpoilerParserPlugin;
import ml.docilealligator.infinityforreddit.markdown.SuperscriptInlineProcessor;
import ml.docilealligator.infinityforreddit.post.Post; import ml.docilealligator.infinityforreddit.post.Post;
import ml.docilealligator.infinityforreddit.utils.APIUtils; import ml.docilealligator.infinityforreddit.utils.APIUtils;
import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils; import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils;
import ml.docilealligator.infinityforreddit.utils.SuperscriptInlineProcessor;
import ml.docilealligator.infinityforreddit.utils.Utils; import ml.docilealligator.infinityforreddit.utils.Utils;
import retrofit2.Retrofit; import retrofit2.Retrofit;
@ -181,52 +177,6 @@ public class CommentsRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerVi
return super.processMarkdown(Utils.fixSuperScript(markdown)); return super.processMarkdown(Utils.fixSuperScript(markdown));
} }
@Override
public void afterSetText(@NonNull TextView textView) {
SpannableStringBuilder markdownStringBuilder = new SpannableStringBuilder(textView.getText());
Pattern spoilerPattern = Pattern.compile(">![\\S\\s]+?!<");
Matcher matcher = spoilerPattern.matcher(markdownStringBuilder);
int start = 0;
boolean find = false;
while (matcher.find(start)) {
if (markdownStringBuilder.length() < 4
|| matcher.start() < 0
|| matcher.end() > markdownStringBuilder.length()) {
break;
}
find = true;
markdownStringBuilder.delete(matcher.end() - 2, matcher.end());
markdownStringBuilder.delete(matcher.start(), matcher.start() + 2);
ClickableSpan clickableSpan = new ClickableSpan() {
private boolean isShowing = false;
@Override
public void updateDrawState(@NonNull TextPaint ds) {
if (isShowing) {
super.updateDrawState(ds);
} else {
ds.bgColor = commentSpoilerBackgroundColor;
}
ds.setColor(mCommentTextColor);
ds.setUnderlineText(false);
}
@Override
public void onClick(@NonNull View view) {
if (textView instanceof SpoilerOnClickTextView) {
((SpoilerOnClickTextView) textView).setSpoilerOnClick(true);
}
isShowing = !isShowing;
view.invalidate();
}
};
markdownStringBuilder.setSpan(clickableSpan, matcher.start(), matcher.end() - 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
start = matcher.end() - 4;
}
if (find) {
textView.setText(markdownStringBuilder);
}
}
@Override @Override
public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) { public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) {
builder.linkResolver((view, link) -> { builder.linkResolver((view, link) -> {
@ -243,6 +193,7 @@ public class CommentsRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerVi
builder.linkColor(linkColor); builder.linkColor(linkColor);
} }
}) })
.usePlugin(SpoilerParserPlugin.create(mCommentTextColor, commentSpoilerBackgroundColor))
.usePlugin(StrikethroughPlugin.create()) .usePlugin(StrikethroughPlugin.create())
.usePlugin(MovementMethodPlugin.create(BetterLinkMovementMethod.newInstance().setOnLinkLongClickListener((textView, url) -> { .usePlugin(MovementMethodPlugin.create(BetterLinkMovementMethod.newInstance().setOnLinkLongClickListener((textView, url) -> {
if (!activity.isDestroyed() && !activity.isFinishing()) { if (!activity.isDestroyed() && !activity.isFinishing()) {

View File

@ -46,10 +46,12 @@ import ml.docilealligator.infinityforreddit.activities.LinkResolverActivity;
import ml.docilealligator.infinityforreddit.activities.ViewPrivateMessagesActivity; import ml.docilealligator.infinityforreddit.activities.ViewPrivateMessagesActivity;
import ml.docilealligator.infinityforreddit.activities.ViewUserDetailActivity; import ml.docilealligator.infinityforreddit.activities.ViewUserDetailActivity;
import ml.docilealligator.infinityforreddit.customtheme.CustomThemeWrapper; import ml.docilealligator.infinityforreddit.customtheme.CustomThemeWrapper;
import ml.docilealligator.infinityforreddit.markdown.SpoilerParserPlugin;
import ml.docilealligator.infinityforreddit.markdown.SpoilerSpan;
import ml.docilealligator.infinityforreddit.message.FetchMessage; import ml.docilealligator.infinityforreddit.message.FetchMessage;
import ml.docilealligator.infinityforreddit.message.Message; import ml.docilealligator.infinityforreddit.message.Message;
import ml.docilealligator.infinityforreddit.message.ReadMessage; import ml.docilealligator.infinityforreddit.message.ReadMessage;
import ml.docilealligator.infinityforreddit.utils.SuperscriptInlineProcessor; import ml.docilealligator.infinityforreddit.markdown.SuperscriptInlineProcessor;
import ml.docilealligator.infinityforreddit.utils.Utils; import ml.docilealligator.infinityforreddit.utils.Utils;
import retrofit2.Retrofit; import retrofit2.Retrofit;
@ -120,53 +122,6 @@ public class MessageRecyclerViewAdapter extends PagedListAdapter<Message, Recycl
return super.processMarkdown(Utils.fixSuperScript(markdown)); return super.processMarkdown(Utils.fixSuperScript(markdown));
} }
@Override
public void afterSetText(@NonNull TextView textView) {
textView.setHighlightColor(Color.TRANSPARENT);
SpannableStringBuilder markdownStringBuilder = new SpannableStringBuilder(textView.getText());
Pattern spoilerPattern = Pattern.compile(">![\\S\\s]+?!<");
Matcher matcher = spoilerPattern.matcher(markdownStringBuilder);
int start = 0;
boolean find = false;
while (matcher.find(start)) {
if (markdownStringBuilder.length() < 4
|| matcher.start() < 0
|| matcher.end() > markdownStringBuilder.length()) {
break;
}
find = true;
markdownStringBuilder.delete(matcher.end() - 2, matcher.end());
markdownStringBuilder.delete(matcher.start(), matcher.start() + 2);
int matcherStart = matcher.start();
int matcherEnd = matcher.end();
ClickableSpan clickableSpan = new ClickableSpan() {
private boolean isShowing = false;
@Override
public void updateDrawState(@NonNull TextPaint ds) {
if (isShowing) {
super.updateDrawState(ds);
ds.setColor(mSecondaryTextColor);
} else {
ds.bgColor = spoilerBackgroundColor;
ds.setColor(mSecondaryTextColor);
}
ds.setUnderlineText(false);
}
@Override
public void onClick(@NonNull View view) {
isShowing = !isShowing;
view.invalidate();
}
};
markdownStringBuilder.setSpan(clickableSpan, matcherStart, matcherEnd - 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
start = matcherEnd - 4;
}
if (find) {
textView.setText(markdownStringBuilder);
}
}
@Override @Override
public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) { public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) {
builder.linkResolver((view, link) -> { builder.linkResolver((view, link) -> {
@ -182,6 +137,7 @@ public class MessageRecyclerViewAdapter extends PagedListAdapter<Message, Recycl
builder.linkColor(customThemeWrapper.getLinkColor()); builder.linkColor(customThemeWrapper.getLinkColor());
} }
}) })
.usePlugin(SpoilerParserPlugin.create(mSecondaryTextColor, spoilerBackgroundColor))
.usePlugin(StrikethroughPlugin.create()) .usePlugin(StrikethroughPlugin.create())
.usePlugin(LinkifyPlugin.create(Linkify.WEB_URLS)) .usePlugin(LinkifyPlugin.create(Linkify.WEB_URLS))
.build(); .build();

View File

@ -111,11 +111,13 @@ import ml.docilealligator.infinityforreddit.customviews.AspectRatioGifImageView;
import ml.docilealligator.infinityforreddit.customviews.LinearLayoutManagerBugFixed; import ml.docilealligator.infinityforreddit.customviews.LinearLayoutManagerBugFixed;
import ml.docilealligator.infinityforreddit.customviews.MarkwonLinearLayoutManager; import ml.docilealligator.infinityforreddit.customviews.MarkwonLinearLayoutManager;
import ml.docilealligator.infinityforreddit.fragments.ViewPostDetailFragment; import ml.docilealligator.infinityforreddit.fragments.ViewPostDetailFragment;
import ml.docilealligator.infinityforreddit.markdown.SpoilerParserPlugin;
import ml.docilealligator.infinityforreddit.markdown.SpoilerSpan;
import ml.docilealligator.infinityforreddit.post.Post; import ml.docilealligator.infinityforreddit.post.Post;
import ml.docilealligator.infinityforreddit.post.PostPagingSource; import ml.docilealligator.infinityforreddit.post.PostPagingSource;
import ml.docilealligator.infinityforreddit.utils.APIUtils; import ml.docilealligator.infinityforreddit.utils.APIUtils;
import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils; import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils;
import ml.docilealligator.infinityforreddit.utils.SuperscriptInlineProcessor; import ml.docilealligator.infinityforreddit.markdown.SuperscriptInlineProcessor;
import ml.docilealligator.infinityforreddit.utils.Utils; import ml.docilealligator.infinityforreddit.utils.Utils;
import pl.droidsonroids.gif.GifImageView; import pl.droidsonroids.gif.GifImageView;
import retrofit2.Retrofit; import retrofit2.Retrofit;
@ -246,51 +248,6 @@ public class PostDetailRecyclerViewAdapter extends RecyclerView.Adapter<Recycler
return super.processMarkdown(Utils.fixSuperScript(markdown)); return super.processMarkdown(Utils.fixSuperScript(markdown));
} }
@Override
public void afterSetText(@NonNull TextView textView) {
textView.setHighlightColor(Color.TRANSPARENT);
SpannableStringBuilder markdownStringBuilder = new SpannableStringBuilder(textView.getText());
Pattern spoilerPattern = Pattern.compile(">![\\S\\s]+?!<");
Matcher matcher = spoilerPattern.matcher(markdownStringBuilder);
int start = 0;
boolean find = false;
while (matcher.find(start)) {
if (markdownStringBuilder.length() < 4
|| matcher.start() < 0
|| matcher.end() > markdownStringBuilder.length()) {
break;
}
find = true;
markdownStringBuilder.delete(matcher.end() - 2, matcher.end());
markdownStringBuilder.delete(matcher.start(), matcher.start() + 2);
ClickableSpan clickableSpan = new ClickableSpan() {
private boolean isShowing = false;
@Override
public void updateDrawState(@NonNull TextPaint ds) {
if (isShowing) {
super.updateDrawState(ds);
ds.setColor(markdownColor);
} else {
ds.bgColor = postSpoilerBackgroundColor;
ds.setColor(markdownColor);
}
ds.setUnderlineText(false);
}
@Override
public void onClick(@NonNull View view) {
isShowing = !isShowing;
view.invalidate();
}
};
markdownStringBuilder.setSpan(clickableSpan, matcher.start(), matcher.end() - 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
start = matcher.end() - 4;
}
if (find) {
textView.setText(markdownStringBuilder);
}
}
@Override @Override
public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) { public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) {
textView.setTextColor(markdownColor); textView.setTextColor(markdownColor);
@ -323,6 +280,7 @@ public class PostDetailRecyclerViewAdapter extends RecyclerView.Adapter<Recycler
builder.linkColor(linkColor); builder.linkColor(linkColor);
} }
}) })
.usePlugin(SpoilerParserPlugin.create(markdownColor, postSpoilerBackgroundColor))
.usePlugin(StrikethroughPlugin.create()) .usePlugin(StrikethroughPlugin.create())
.usePlugin(MovementMethodPlugin.create(BetterLinkMovementMethod.linkify(Linkify.WEB_URLS, activity).setOnLinkLongClickListener((textView, url) -> { .usePlugin(MovementMethodPlugin.create(BetterLinkMovementMethod.linkify(Linkify.WEB_URLS, activity).setOnLinkLongClickListener((textView, url) -> {
if (activity != null && !activity.isDestroyed() && !activity.isFinishing()) { if (activity != null && !activity.isDestroyed() && !activity.isFinishing()) {

View File

@ -41,7 +41,7 @@ import ml.docilealligator.infinityforreddit.activities.ViewUserDetailActivity;
import ml.docilealligator.infinityforreddit.customtheme.CustomThemeWrapper; import ml.docilealligator.infinityforreddit.customtheme.CustomThemeWrapper;
import ml.docilealligator.infinityforreddit.message.Message; import ml.docilealligator.infinityforreddit.message.Message;
import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils; import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils;
import ml.docilealligator.infinityforreddit.utils.SuperscriptInlineProcessor; import ml.docilealligator.infinityforreddit.markdown.SuperscriptInlineProcessor;
import ml.docilealligator.infinityforreddit.utils.Utils; import ml.docilealligator.infinityforreddit.utils.Utils;
public class PrivateMessagesDetailRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { public class PrivateMessagesDetailRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

View File

@ -35,7 +35,7 @@ import ml.docilealligator.infinityforreddit.Rule;
import ml.docilealligator.infinityforreddit.activities.LinkResolverActivity; import ml.docilealligator.infinityforreddit.activities.LinkResolverActivity;
import ml.docilealligator.infinityforreddit.bottomsheetfragments.UrlMenuBottomSheetFragment; import ml.docilealligator.infinityforreddit.bottomsheetfragments.UrlMenuBottomSheetFragment;
import ml.docilealligator.infinityforreddit.customtheme.CustomThemeWrapper; import ml.docilealligator.infinityforreddit.customtheme.CustomThemeWrapper;
import ml.docilealligator.infinityforreddit.utils.SuperscriptInlineProcessor; import ml.docilealligator.infinityforreddit.markdown.SuperscriptInlineProcessor;
import ml.docilealligator.infinityforreddit.utils.Utils; import ml.docilealligator.infinityforreddit.utils.Utils;
public class RulesRecyclerViewAdapter extends RecyclerView.Adapter<RulesRecyclerViewAdapter.RuleViewHolder> { public class RulesRecyclerViewAdapter extends RecyclerView.Adapter<RulesRecyclerViewAdapter.RuleViewHolder> {

View File

@ -74,7 +74,6 @@ public class CopyTextBottomSheetFragment extends LandscapeExpandedRoundedBottomS
if (markdownText != null) { if (markdownText != null) {
//markdownText = markdownText.replaceAll("<sup>", "^").replaceAll("</sup>", ""); //markdownText = markdownText.replaceAll("<sup>", "^").replaceAll("</sup>", "");
markdownText = markdownText.replaceAll("&gt;", ">");
copyMarkdownTextView.setOnClickListener(view -> { copyMarkdownTextView.setOnClickListener(view -> {
showCopyDialog(markdownText); showCopyDialog(markdownText);
dismiss(); dismiss();

View File

@ -58,7 +58,7 @@ import ml.docilealligator.infinityforreddit.customviews.LinearLayoutManagerBugFi
import ml.docilealligator.infinityforreddit.subreddit.FetchSubredditData; import ml.docilealligator.infinityforreddit.subreddit.FetchSubredditData;
import ml.docilealligator.infinityforreddit.subreddit.SubredditData; import ml.docilealligator.infinityforreddit.subreddit.SubredditData;
import ml.docilealligator.infinityforreddit.subreddit.SubredditViewModel; import ml.docilealligator.infinityforreddit.subreddit.SubredditViewModel;
import ml.docilealligator.infinityforreddit.utils.SuperscriptInlineProcessor; import ml.docilealligator.infinityforreddit.markdown.SuperscriptInlineProcessor;
import ml.docilealligator.infinityforreddit.utils.Utils; import ml.docilealligator.infinityforreddit.utils.Utils;
import retrofit2.Retrofit; import retrofit2.Retrofit;

View File

@ -0,0 +1,85 @@
package ml.docilealligator.infinityforreddit.markdown;
import org.commonmark.internal.util.Parsing;
import org.commonmark.node.Block;
import org.commonmark.node.BlockQuote;
import org.commonmark.parser.block.AbstractBlockParser;
import org.commonmark.parser.block.AbstractBlockParserFactory;
import org.commonmark.parser.block.BlockContinue;
import org.commonmark.parser.block.BlockStart;
import org.commonmark.parser.block.MatchedBlockParser;
import org.commonmark.parser.block.ParserState;
// Parse and consume a block quote except when it's a spoiler opening
public class BlockQuoteWithExceptionParser extends AbstractBlockParser {
private final BlockQuote block = new BlockQuote();
private static boolean isMarker(ParserState state, int index) {
CharSequence line = state.getLine();
return state.getIndent() < Parsing.CODE_BLOCK_INDENT && index < line.length() && line.charAt(index) == '>';
}
private static boolean isMarkerSpoiler(ParserState state, int index) {
CharSequence line = state.getLine();
int length = line.length();
try {
return state.getIndent() < Parsing.CODE_BLOCK_INDENT && index < length && line.charAt(index) == '>' && (index + 1) < length && line.charAt(index + 1) == '!';
} catch (IndexOutOfBoundsException e) {
e.printStackTrace();
return false;
}
}
@Override
public boolean isContainer() {
return true;
}
@Override
public boolean canContain(Block block) {
return true;
}
@Override
public BlockQuote getBlock() {
return block;
}
@Override
public BlockContinue tryContinue(ParserState state) {
int nextNonSpace = state.getNextNonSpaceIndex();
if (isMarker(state, nextNonSpace)) {
int newColumn = state.getColumn() + state.getIndent() + 1;
// optional following space or tab
if (Parsing.isSpaceOrTab(state.getLine(), nextNonSpace + 1)) {
newColumn++;
}
return BlockContinue.atColumn(newColumn);
} else {
return BlockContinue.none();
}
}
public static class Factory extends AbstractBlockParserFactory {
public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) {
int nextNonSpace = state.getNextNonSpaceIndex();
// Potential for a spoiler opening
// We don't check for spoiler closing, neither does Reddit
if (isMarkerSpoiler(state, nextNonSpace)) {
// It might be a spoiler, don't consume
return BlockStart.none();
}
// Not a spoiler then
else if (isMarker(state, nextNonSpace)) {
int newColumn = state.getColumn() + state.getIndent() + 1;
// optional following space or tab
if (Parsing.isSpaceOrTab(state.getLine(), nextNonSpace + 1)) {
newColumn++;
}
return BlockStart.of(new BlockQuoteWithExceptionParser()).atColumn(newColumn);
} else {
return BlockStart.none();
}
}
}
}

View File

@ -0,0 +1,202 @@
package ml.docilealligator.infinityforreddit.markdown;
import android.graphics.Color;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.widget.TextView;
import androidx.annotation.NonNull;
import org.commonmark.node.Block;
import org.commonmark.node.BlockQuote;
import org.commonmark.node.HtmlBlock;
import org.commonmark.node.HtmlInline;
import org.commonmark.parser.Parser;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import io.noties.markwon.AbstractMarkwonPlugin;
import io.noties.markwon.core.CorePlugin;
import io.noties.markwon.core.spans.CodeBlockSpan;
import io.noties.markwon.core.spans.CodeSpan;
public class SpoilerParserPlugin extends AbstractMarkwonPlugin {
private final int textColor;
private final int backgroundColor;
SpoilerParserPlugin(int textColor, int backgroundColor) {
this.textColor = textColor;
this.backgroundColor = backgroundColor;
}
public static SpoilerParserPlugin create(@NonNull int textColor, @NonNull int backgroundColor) {
return new SpoilerParserPlugin(textColor, backgroundColor);
}
@Override
public void configureParser(@NonNull Parser.Builder builder) {
builder.customBlockParserFactory(new BlockQuoteWithExceptionParser.Factory());
Set<Class<? extends Block>> blocks = CorePlugin.enabledBlockTypes();
blocks.remove(HtmlBlock.class);
blocks.remove(HtmlInline.class);
blocks.remove(BlockQuote.class);
builder.enabledBlockTypes(blocks);
}
@Override
public void afterSetText(@NonNull TextView textView) {
textView.setHighlightColor(Color.TRANSPARENT);
if(textView.getText().length() < 5) {
return;
}
SpannableStringBuilder markdownStringBuilder = new SpannableStringBuilder(textView.getText());
LinkedHashMap<Integer, Integer> spoilers = parse(markdownStringBuilder);
if(spoilers.size() == 0) {
return;
}
int offset = 2;
for (Map.Entry<Integer, Integer> entry : spoilers.entrySet()) {
int spoilerStart = entry.getKey() - offset;
int spoilerEnd = entry.getValue() - offset;
// Try not to set a spoiler span if it's inside a CodeSpan
CodeSpan[] codeSpans = markdownStringBuilder.getSpans(spoilerStart, spoilerEnd, CodeSpan.class);
CodeBlockSpan[] codeBlockSpans = markdownStringBuilder.getSpans(spoilerStart, spoilerEnd, CodeBlockSpan.class);
if (codeSpans.length == 0 && codeBlockSpans.length == 0) {
markdownStringBuilder.delete(spoilerStart, spoilerStart + 2);
markdownStringBuilder.delete(spoilerEnd, spoilerEnd + 2);
SpoilerSpan spoilerSpan = new SpoilerSpan(textColor, backgroundColor);
markdownStringBuilder.setSpan(spoilerSpan, spoilerStart, spoilerEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
offset += 4;
}
for (CodeSpan codeSpan : codeSpans) {
int spanBeginning = markdownStringBuilder.getSpanStart(codeSpan);
int spanEnd = markdownStringBuilder.getSpanEnd(codeSpan);
if (spoilerStart + 2 <= spanBeginning && spanEnd <= spoilerEnd + 2) {
markdownStringBuilder.delete(spoilerStart, spoilerStart + 2);
markdownStringBuilder.delete(spoilerEnd, spoilerEnd + 2);
SpoilerSpan spoilerSpan = new SpoilerSpan(textColor, backgroundColor);
markdownStringBuilder.setSpan(spoilerSpan, spoilerStart, spoilerEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
offset += 4;
} else {
break;
}
}
for (CodeBlockSpan codeBlockSpan : codeBlockSpans) {
int spanBeginning = markdownStringBuilder.getSpanStart(codeBlockSpan);
int spanEnd = markdownStringBuilder.getSpanEnd(codeBlockSpan);
if (spoilerStart + 2 <= spanBeginning && spanEnd <= spoilerEnd + 2) {
markdownStringBuilder.delete(spoilerStart, spoilerStart + 2);
markdownStringBuilder.delete(spoilerEnd, spoilerEnd + 2);
SpoilerSpan spoilerSpan = new SpoilerSpan(textColor, backgroundColor);
markdownStringBuilder.setSpan(spoilerSpan, spoilerStart, spoilerEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
offset += 4;
} else {
break;
}
}
}
if(offset > 2) {
textView.setText(markdownStringBuilder);
}
}
// Very naive implementation, needs to be improved for efficiency and edge cases
// Don't allow more than one new line after every non-blank line
// Try not to care about recursing spoilers, we just want the outermost spoiler because
// spoiler revealing-hiding breaks with recursing spoilers
private LinkedHashMap<Integer, Integer> parse(SpannableStringBuilder markdown) {
final int MAX_NEW_LINE = 1;
var openSpoilerStack = new Stack<Integer>();
var closedSpoilerMap = new LinkedHashMap<Integer, Integer>();
int variable_max_depth = calculateBalance(0, markdown) + 1;
int new_lines = 0;
int depth = 0;
for (int i = 0; i < markdown.length(); i++) {
if (markdown.charAt(i) == '\u2000' || markdown.charAt(i) == '\t') {
continue;
} else if (markdown.charAt(i) == '>' && (i + 1) < markdown.length() && markdown.charAt(i + 1) == '!') {
openSpoilerStack.push(i + 1);
depth++;
} else if (openSpoilerStack.size() > 0
&& markdown.charAt(i) == '!' && (i + 1) < markdown.length()
&& markdown.charAt(i + 1) == '<') {
var pos = i + 1;
for (int j = 0; j < depth; j++) {
if (!openSpoilerStack.isEmpty()) pos = openSpoilerStack.peek();
if (pos + 1 <= i) {
if (!openSpoilerStack.isEmpty()) pos = openSpoilerStack.peek();
break;
} else {
if (!openSpoilerStack.isEmpty()) pos = openSpoilerStack.pop();
}
}
if (depth <= variable_max_depth && pos + 1 <= i) //Spoiler content cannot be zero or less length
{
openSpoilerStack.clear();
closedSpoilerMap.put(pos + 1, i);
}
depth--;
} else if (markdown.charAt(i) == '\n') {
new_lines++;
if (openSpoilerStack.size() >= 1 && new_lines > MAX_NEW_LINE) {
openSpoilerStack.clear();
new_lines = 0;
depth = 0;
variable_max_depth = calculateBalance(i, markdown) + 1;
}
} else {
new_lines = 0;
}
if (openSpoilerStack.size() >= 32) // No
{
openSpoilerStack.clear();
closedSpoilerMap.clear();
continue;
}
}
return closedSpoilerMap;
}
private int calculateBalance(int index, SpannableStringBuilder line) {
final int MAX_NEW_LINE = 1;
int new_lines = 0;
int opening = 0;
int closing = 0;
for (int i = index; i < line.length(); i++) {
if (line.charAt(i) == '\u0020' || line.charAt(i) == '\t') {
continue;
} else if (line.charAt(i) == '>'
&& (i + 1) < line.length()
&& line.charAt(i + 1) == '!') {
opening++;
} else if (line.charAt(i) == '!' && (i + 1) < line.length()
&& line.charAt(i + 1) == '<') {
closing++;
} else if (line.charAt(i) == '\n') {
new_lines++;
if (new_lines > MAX_NEW_LINE) {
break;
}
} else {
new_lines = 0;
continue;
}
}
return Math.abs(opening - closing);
}
}

View File

@ -0,0 +1,64 @@
package ml.docilealligator.infinityforreddit.markdown;
import android.text.Layout;
import android.text.Spannable;
import android.text.TextPaint;
import android.text.style.ClickableSpan;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.transition.AutoTransition;
import androidx.transition.TransitionManager;
import ml.docilealligator.infinityforreddit.customviews.SpoilerOnClickTextView;
public class SpoilerSpan extends ClickableSpan {
final int textColor;
final int backgroundColor;
private boolean isShowing = false;
public SpoilerSpan(@NonNull int textColor, @NonNull int backgroundColor) {
this.textColor = textColor;
this.backgroundColor = backgroundColor;
}
@Override
public void onClick(@NonNull View widget) {
if (!(widget instanceof TextView)) {
return;
}
final TextView textView = (TextView) widget;
final Spannable spannable = (Spannable) textView.getText();
final int end = spannable.getSpanEnd(this);
if (end < 0) {
return;
}
final Layout layout = textView.getLayout();
if (layout == null) {
return;
}
if (widget instanceof SpoilerOnClickTextView) {
((SpoilerOnClickTextView) textView).setSpoilerOnClick(true);
}
isShowing = !isShowing;
widget.invalidate();
}
@Override
public void updateDrawState(@NonNull TextPaint ds) {
if (isShowing) {
ds.bgColor = backgroundColor & 0x0D000000; //Slightly darker background color for revealed spoiler
super.updateDrawState(ds);
} else {
ds.bgColor = backgroundColor;
}
ds.setColor(textColor);
ds.setUnderlineText(false);
}
}

View File

@ -1,4 +1,4 @@
package ml.docilealligator.infinityforreddit.utils; package ml.docilealligator.infinityforreddit.markdown;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;

View File

@ -65,7 +65,6 @@ public class Utils {
.replaceAll("((?<=[\\s])|^)/[rRuU]/[\\w-]+/{0,1}", "[$0](https://www.reddit.com$0)") .replaceAll("((?<=[\\s])|^)/[rRuU]/[\\w-]+/{0,1}", "[$0](https://www.reddit.com$0)")
.replaceAll("((?<=[\\s])|^)[rRuU]/[\\w-]+/{0,1}", "[$0](https://www.reddit.com/$0)") .replaceAll("((?<=[\\s])|^)[rRuU]/[\\w-]+/{0,1}", "[$0](https://www.reddit.com/$0)")
.replaceAll("\\^{2,}", "^") .replaceAll("\\^{2,}", "^")
.replaceAll(">!(\\n?(?:[\\S\\h]+?(?:\\n?[\\S\\h]+?)?)\\n?)!<", "&gt;!$1!<") // html entity remains escaped inside an inline block
.replaceAll("(^|^ *|\\n *)#(?!($|\\s|#))", "$0 ") .replaceAll("(^|^ *|\\n *)#(?!($|\\s|#))", "$0 ")
.replaceAll("(^|^ *|\\n *)##(?!($|\\s|#))", "$0 ") .replaceAll("(^|^ *|\\n *)##(?!($|\\s|#))", "$0 ")
.replaceAll("(^|^ *|\\n *)###(?!($|\\s|#))", "$0 ") .replaceAll("(^|^ *|\\n *)###(?!($|\\s|#))", "$0 ")