Extracted span generation into a separate factory

This commit is contained in:
Cyrus Bakhtiari-Haftlang 2018-07-12 14:18:48 +02:00
parent 194218df2a
commit bef7fd235f
4 changed files with 212 additions and 58 deletions

View File

@ -0,0 +1,127 @@
package ru.noties.markwon;
import android.support.annotation.NonNull;
import android.text.style.StrikethroughSpan;
import ru.noties.markwon.renderer.ImageSizeResolver;
import ru.noties.markwon.spans.AsyncDrawable;
import ru.noties.markwon.spans.AsyncDrawableSpan;
import ru.noties.markwon.spans.BlockQuoteSpan;
import ru.noties.markwon.spans.BulletListItemSpan;
import ru.noties.markwon.spans.CodeSpan;
import ru.noties.markwon.spans.EmphasisSpan;
import ru.noties.markwon.spans.HeadingSpan;
import ru.noties.markwon.spans.LinkSpan;
import ru.noties.markwon.spans.OrderedListItemSpan;
import ru.noties.markwon.spans.SpanFactory;
import ru.noties.markwon.spans.SpannableTheme;
import ru.noties.markwon.spans.StrongEmphasisSpan;
import ru.noties.markwon.spans.TaskListSpan;
import ru.noties.markwon.spans.ThematicBreakSpan;
public class SpanFactoryDef implements SpanFactory {
@NonNull
private final SpannableTheme theme;
@NonNull
private final LinkSpan.Resolver linkResolver;
@NonNull
private final AsyncDrawable.Loader drawableLoader;
@NonNull
private final ImageSizeResolver imageSizeResolver;
public SpanFactoryDef(@NonNull SpannableTheme theme,
@NonNull LinkSpan.Resolver linkResolver,
@NonNull AsyncDrawable.Loader drawableLoader,
@NonNull ImageSizeResolver imageSizeResolver) {
this.theme = theme;
this.linkResolver = linkResolver;
this.drawableLoader = drawableLoader;
this.imageSizeResolver = imageSizeResolver;
}
@NonNull
@Override
public Object createBlockQuote() {
return new BlockQuoteSpan(theme);
}
@NonNull
@Override
public Object createBulletListItem(int level) {
return new BulletListItemSpan(theme, level);
}
@NonNull
@Override
public Object createCode(boolean multiline) {
return new CodeSpan(theme, multiline);
}
@NonNull
@Override
public Object createEmphasis() {
return new EmphasisSpan();
}
@NonNull
@Override
public Object createHeading(int level) {
return new HeadingSpan(theme, level);
}
@NonNull
@Override
public Object createImage(@NonNull String destination, boolean link) {
return new AsyncDrawableSpan(
theme,
new AsyncDrawable(
destination,
drawableLoader,
imageSizeResolver,
null
),
AsyncDrawableSpan.ALIGN_BOTTOM,
link
);
}
@NonNull
@Override
public Object createLink(@NonNull String destination) {
return new LinkSpan(theme, destination, linkResolver);
}
@NonNull
@Override
public Object createOrderedListItem(int order) {
// todo| in order to provide real RTL experience there must be a way to provide this string
return new OrderedListItemSpan(theme, String.valueOf(order) + "." + '\u00a0');
}
@NonNull
@Override
public Object createStrikethrough() {
return new StrikethroughSpan();
}
@NonNull
@Override
public Object createStrongEmphasis() {
return new StrongEmphasisSpan();
}
@NonNull
@Override
public Object createTaskList(int indent, boolean done) {
return new TaskListSpan(theme, indent, done);
}
@NonNull
@Override
public Object createThematicBreak() {
return new ThematicBreakSpan(theme);
}
}

View File

@ -8,6 +8,7 @@ import ru.noties.markwon.renderer.ImageSizeResolverDef;
import ru.noties.markwon.renderer.html.SpannableHtmlParser; import ru.noties.markwon.renderer.html.SpannableHtmlParser;
import ru.noties.markwon.spans.AsyncDrawable; import ru.noties.markwon.spans.AsyncDrawable;
import ru.noties.markwon.spans.LinkSpan; import ru.noties.markwon.spans.LinkSpan;
import ru.noties.markwon.spans.SpanFactory;
import ru.noties.markwon.spans.SpannableTheme; import ru.noties.markwon.spans.SpannableTheme;
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
@ -31,6 +32,7 @@ public class SpannableConfiguration {
private final UrlProcessor urlProcessor; private final UrlProcessor urlProcessor;
private final SpannableHtmlParser htmlParser; private final SpannableHtmlParser htmlParser;
private final ImageSizeResolver imageSizeResolver; private final ImageSizeResolver imageSizeResolver;
private final SpanFactory spanFactory;
private SpannableConfiguration(@NonNull Builder builder) { private SpannableConfiguration(@NonNull Builder builder) {
this.theme = builder.theme; this.theme = builder.theme;
@ -40,6 +42,7 @@ public class SpannableConfiguration {
this.urlProcessor = builder.urlProcessor; this.urlProcessor = builder.urlProcessor;
this.htmlParser = builder.htmlParser; this.htmlParser = builder.htmlParser;
this.imageSizeResolver = builder.imageSizeResolver; this.imageSizeResolver = builder.imageSizeResolver;
this.spanFactory = builder.spanFactory;
} }
@NonNull @NonNull
@ -77,6 +80,11 @@ public class SpannableConfiguration {
return imageSizeResolver; return imageSizeResolver;
} }
@NonNull
public SpanFactory spanFactory() {
return spanFactory;
}
@SuppressWarnings("unused") @SuppressWarnings("unused")
public static class Builder { public static class Builder {
@ -88,6 +96,7 @@ public class SpannableConfiguration {
private UrlProcessor urlProcessor; private UrlProcessor urlProcessor;
private SpannableHtmlParser htmlParser; private SpannableHtmlParser htmlParser;
private ImageSizeResolver imageSizeResolver; private ImageSizeResolver imageSizeResolver;
private SpanFactory spanFactory;
Builder(@NonNull Context context) { Builder(@NonNull Context context) {
this.context = context; this.context = context;
@ -138,6 +147,12 @@ public class SpannableConfiguration {
return this; return this;
} }
@NonNull
public Builder spanFactory(@NonNull SpanFactory spanFactory) {
this.spanFactory = spanFactory;
return this;
}
@NonNull @NonNull
public SpannableConfiguration build() { public SpannableConfiguration build() {
@ -165,6 +180,10 @@ public class SpannableConfiguration {
imageSizeResolver = new ImageSizeResolverDef(); imageSizeResolver = new ImageSizeResolverDef();
} }
if (spanFactory == null) {
spanFactory = new SpanFactoryDef(theme, linkResolver, asyncDrawableLoader, imageSizeResolver);
}
if (htmlParser == null) { if (htmlParser == null) {
htmlParser = SpannableHtmlParser.create(theme, asyncDrawableLoader, urlProcessor, linkResolver, imageSizeResolver); htmlParser = SpannableHtmlParser.create(theme, asyncDrawableLoader, urlProcessor, linkResolver, imageSizeResolver);
} }

View File

@ -5,7 +5,6 @@ import android.support.annotation.Nullable;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
import android.text.Spanned; import android.text.Spanned;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.style.StrikethroughSpan;
import org.commonmark.ext.gfm.strikethrough.Strikethrough; import org.commonmark.ext.gfm.strikethrough.Strikethrough;
import org.commonmark.ext.gfm.tables.TableBody; import org.commonmark.ext.gfm.tables.TableBody;
@ -44,19 +43,8 @@ import java.util.List;
import ru.noties.markwon.ReverseSpannableStringBuilder; import ru.noties.markwon.ReverseSpannableStringBuilder;
import ru.noties.markwon.SpannableConfiguration; import ru.noties.markwon.SpannableConfiguration;
import ru.noties.markwon.renderer.html.SpannableHtmlParser; import ru.noties.markwon.renderer.html.SpannableHtmlParser;
import ru.noties.markwon.spans.AsyncDrawable; import ru.noties.markwon.spans.SpanFactory;
import ru.noties.markwon.spans.AsyncDrawableSpan;
import ru.noties.markwon.spans.BlockQuoteSpan;
import ru.noties.markwon.spans.BulletListItemSpan;
import ru.noties.markwon.spans.CodeSpan;
import ru.noties.markwon.spans.EmphasisSpan;
import ru.noties.markwon.spans.HeadingSpan;
import ru.noties.markwon.spans.LinkSpan;
import ru.noties.markwon.spans.OrderedListItemSpan;
import ru.noties.markwon.spans.StrongEmphasisSpan;
import ru.noties.markwon.spans.TableRowSpan; import ru.noties.markwon.spans.TableRowSpan;
import ru.noties.markwon.spans.TaskListSpan;
import ru.noties.markwon.spans.ThematicBreakSpan;
import ru.noties.markwon.tasklist.TaskListBlock; import ru.noties.markwon.tasklist.TaskListBlock;
import ru.noties.markwon.tasklist.TaskListItem; import ru.noties.markwon.tasklist.TaskListItem;
@ -64,6 +52,7 @@ import ru.noties.markwon.tasklist.TaskListItem;
public class SpannableMarkdownVisitor extends AbstractVisitor { public class SpannableMarkdownVisitor extends AbstractVisitor {
private final SpannableConfiguration configuration; private final SpannableConfiguration configuration;
private final SpanFactory spanFactory;
private final SpannableStringBuilder builder; private final SpannableStringBuilder builder;
private final Deque<HtmlInlineItem> htmlInlineItems; private final Deque<HtmlInlineItem> htmlInlineItems;
@ -79,6 +68,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
@NonNull SpannableStringBuilder builder @NonNull SpannableStringBuilder builder
) { ) {
this.configuration = configuration; this.configuration = configuration;
this.spanFactory = configuration.spanFactory();
this.builder = builder; this.builder = builder;
this.htmlInlineItems = new ArrayDeque<>(2); this.htmlInlineItems = new ArrayDeque<>(2);
} }
@ -92,14 +82,14 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
public void visit(StrongEmphasis strongEmphasis) { public void visit(StrongEmphasis strongEmphasis) {
final int length = builder.length(); final int length = builder.length();
visitChildren(strongEmphasis); visitChildren(strongEmphasis);
setSpan(length, new StrongEmphasisSpan()); setSpan(length, spanFactory.createStrongEmphasis());
} }
@Override @Override
public void visit(Emphasis emphasis) { public void visit(Emphasis emphasis) {
final int length = builder.length(); final int length = builder.length();
visitChildren(emphasis); visitChildren(emphasis);
setSpan(length, new EmphasisSpan()); setSpan(length, spanFactory.createEmphasis());
} }
@Override @Override
@ -116,7 +106,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
visitChildren(blockQuote); visitChildren(blockQuote);
setSpan(length, new BlockQuoteSpan(configuration.theme())); setSpan(length, spanFactory.createBlockQuote());
blockQuoteIndent -= 1; blockQuoteIndent -= 1;
@ -137,10 +127,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
builder.append(code.getLiteral()); builder.append(code.getLiteral());
builder.append('\u00a0'); builder.append('\u00a0');
setSpan(length, new CodeSpan( setSpan(length, spanFactory.createCode(false));
configuration.theme(),
false
));
} }
@Override @Override
@ -175,10 +162,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
); );
builder.append('\u00a0').append('\n'); builder.append('\u00a0').append('\n');
setSpan(length, new CodeSpan( setSpan(length, spanFactory.createCode(true));
configuration.theme(),
true
));
newLine(); newLine();
builder.append('\n'); builder.append('\n');
@ -218,11 +202,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
visitChildren(listItem); visitChildren(listItem);
// todo| in order to provide real RTL experience there must be a way to provide this string setSpan(length, spanFactory.createOrderedListItem(start));
setSpan(length, new OrderedListItemSpan(
configuration.theme(),
String.valueOf(start) + "." + '\u00a0'
));
// after we have visited the children increment start number // after we have visited the children increment start number
final OrderedList orderedList = (OrderedList) parent; final OrderedList orderedList = (OrderedList) parent;
@ -232,10 +212,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
visitChildren(listItem); visitChildren(listItem);
setSpan(length, new BulletListItemSpan( setSpan(length, spanFactory.createBulletListItem(listLevel - 1));
configuration.theme(),
listLevel - 1
));
} }
blockQuoteIndent -= 1; blockQuoteIndent -= 1;
@ -251,7 +228,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
final int length = builder.length(); final int length = builder.length();
builder.append(' '); // without space it won't render builder.append(' '); // without space it won't render
setSpan(length, new ThematicBreakSpan(configuration.theme())); setSpan(length, spanFactory.createThematicBreak());
newLine(); newLine();
builder.append('\n'); builder.append('\n');
@ -264,7 +241,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
final int length = builder.length(); final int length = builder.length();
visitChildren(heading); visitChildren(heading);
setSpan(length, new HeadingSpan(configuration.theme(), heading.getLevel())); setSpan(length, spanFactory.createHeading(heading.getLevel()));
newLine(); newLine();
@ -306,7 +283,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
final int length = builder.length(); final int length = builder.length();
visitChildren(customNode); visitChildren(customNode);
setSpan(length, new StrikethroughSpan()); setSpan(length, spanFactory.createStrikethrough());
} else if (customNode instanceof TaskListItem) { } else if (customNode instanceof TaskListItem) {
@ -320,11 +297,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
visitChildren(customNode); visitChildren(customNode);
setSpan(length, new TaskListSpan( setSpan(length, spanFactory.createTaskList(blockQuoteIndent, listItem.done()));
configuration.theme(),
blockQuoteIndent,
listItem.done()
));
newLine(); newLine();
@ -448,20 +421,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
final boolean link = parent != null && parent instanceof Link; final boolean link = parent != null && parent instanceof Link;
final String destination = configuration.urlProcessor().process(image.getDestination()); final String destination = configuration.urlProcessor().process(image.getDestination());
setSpan( setSpan(length, spanFactory.createImage(destination, link));
length,
new AsyncDrawableSpan(
configuration.theme(),
new AsyncDrawable(
destination,
configuration.asyncDrawableLoader(),
configuration.imageSizeResolver(),
null
),
AsyncDrawableSpan.ALIGN_BOTTOM,
link
)
);
// todo, maybe, if image is not inside a link, we should make it clickable, so // todo, maybe, if image is not inside a link, we should make it clickable, so
// user can open it in external viewer? // user can open it in external viewer?
@ -520,11 +480,17 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
final int length = builder.length(); final int length = builder.length();
visitChildren(link); visitChildren(link);
final String destination = configuration.urlProcessor().process(link.getDestination()); final String destination = configuration.urlProcessor().process(link.getDestination());
setSpan(length, new LinkSpan(configuration.theme(), destination, configuration.linkResolver())); setSpan(length, spanFactory.createLink(destination));
} }
private void setSpan(int start, @NonNull Object span) { private void setSpan(int start, @NonNull Object spans) {
builder.setSpan(span, start, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); if (spans instanceof Object[]) {
for (final Object span : (Object[]) spans) {
builder.setSpan(span, start, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
} else {
builder.setSpan(spans, start, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
} }
private void newLine() { private void newLine() {

View File

@ -0,0 +1,42 @@
package ru.noties.markwon.spans;
import android.support.annotation.NonNull;
public interface SpanFactory {
@NonNull
Object createBlockQuote();
@NonNull
Object createBulletListItem(int level);
@NonNull
Object createCode(boolean multiline);
@NonNull
Object createEmphasis();
@NonNull
Object createHeading(int level);
@NonNull
Object createImage(@NonNull String destination, boolean link);
@NonNull
Object createLink(@NonNull String destination);
@NonNull
Object createOrderedListItem(int order);
@NonNull
Object createStrongEmphasis();
@NonNull
Object createStrikethrough();
@NonNull
Object createTaskList(int indent, boolean done);
@NonNull
Object createThematicBreak();
}