Switch to use SpannableFactory

This commit is contained in:
Dimitry Ivanov 2018-07-21 13:35:49 +03:00
parent 146ba9c575
commit 9f532df752
4 changed files with 259 additions and 44 deletions

View File

@ -31,6 +31,7 @@ public class SpannableConfiguration {
private final UrlProcessor urlProcessor;
private final SpannableHtmlParser htmlParser;
private final ImageSizeResolver imageSizeResolver;
private final SpannableFactory spannableFactory; // @since 1.1.0
private SpannableConfiguration(@NonNull Builder builder) {
this.theme = builder.theme;
@ -40,6 +41,7 @@ public class SpannableConfiguration {
this.urlProcessor = builder.urlProcessor;
this.htmlParser = builder.htmlParser;
this.imageSizeResolver = builder.imageSizeResolver;
this.spannableFactory = builder.spannableFactory;
}
@NonNull
@ -77,6 +79,11 @@ public class SpannableConfiguration {
return imageSizeResolver;
}
@NonNull
public SpannableFactory factory() {
return spannableFactory;
}
@SuppressWarnings("unused")
public static class Builder {
@ -88,6 +95,7 @@ public class SpannableConfiguration {
private UrlProcessor urlProcessor;
private SpannableHtmlParser htmlParser;
private ImageSizeResolver imageSizeResolver;
private SpannableFactory spannableFactory;
Builder(@NonNull Context context) {
this.context = context;
@ -138,6 +146,15 @@ public class SpannableConfiguration {
return this;
}
/**
* @since 1.1.0
*/
@NonNull
public Builder spannableFactory(@NonNull SpannableFactory spannableFactory) {
this.spannableFactory = spannableFactory;
return this;
}
@NonNull
public SpannableConfiguration build() {
@ -165,6 +182,11 @@ public class SpannableConfiguration {
imageSizeResolver = new ImageSizeResolverDef();
}
// @since 1.1.0
if (spannableFactory == null) {
spannableFactory = SpannableFactoryDef.create();
}
if (htmlParser == null) {
htmlParser = SpannableHtmlParser.create(theme, asyncDrawableLoader, urlProcessor, linkResolver, imageSizeResolver);
}

View File

@ -0,0 +1,73 @@
package ru.noties.markwon;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import java.util.List;
import ru.noties.markwon.renderer.ImageSize;
import ru.noties.markwon.renderer.ImageSizeResolver;
import ru.noties.markwon.spans.AsyncDrawable;
import ru.noties.markwon.spans.LinkSpan;
import ru.noties.markwon.spans.SpannableTheme;
import ru.noties.markwon.spans.TableRowSpan;
/**
* Each method can return null or a Span object or an array of spans
*
* @since 1.1.0
*/
public interface SpannableFactory {
@Nullable
Object strongEmphasis();
@Nullable
Object emphasis();
@Nullable
Object blockQuote(@NonNull SpannableTheme theme);
@Nullable
Object code(@NonNull SpannableTheme theme, boolean multiline);
@Nullable
Object orderedListItem(@NonNull SpannableTheme theme, int startNumber);
@Nullable
Object bulletListItem(@NonNull SpannableTheme theme, int level);
@Nullable
Object thematicBreak(@NonNull SpannableTheme theme);
@Nullable
Object heading(@NonNull SpannableTheme theme, int level);
@Nullable
Object strikethrough();
@Nullable
Object taskListItem(@NonNull SpannableTheme theme, int blockIndent, boolean isDone);
@Nullable
Object tableRow(
@NonNull SpannableTheme theme,
@NonNull List<TableRowSpan.Cell> cells,
boolean isHeader,
boolean isOdd);
@Nullable
Object image(
@NonNull SpannableTheme theme,
@NonNull String destination,
@NonNull AsyncDrawable.Loader loader,
@NonNull ImageSizeResolver imageSizeResolver,
@Nullable ImageSize imageSize,
boolean replacementTextIsLink);
@Nullable
Object link(
@NonNull SpannableTheme theme,
@NonNull String destination,
@NonNull LinkSpan.Resolver resolver);
}

View File

@ -0,0 +1,121 @@
package ru.noties.markwon;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.style.StrikethroughSpan;
import java.util.List;
import ru.noties.markwon.renderer.ImageSize;
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.SpannableTheme;
import ru.noties.markwon.spans.StrongEmphasisSpan;
import ru.noties.markwon.spans.TableRowSpan;
import ru.noties.markwon.spans.TaskListSpan;
import ru.noties.markwon.spans.ThematicBreakSpan;
public class SpannableFactoryDef implements SpannableFactory {
@NonNull
public static SpannableFactoryDef create() {
return new SpannableFactoryDef();
}
@Nullable
@Override
public Object strongEmphasis() {
return new StrongEmphasisSpan();
}
@Nullable
@Override
public Object emphasis() {
return new EmphasisSpan();
}
@Nullable
@Override
public Object blockQuote(@NonNull SpannableTheme theme) {
return new BlockQuoteSpan(theme);
}
@Nullable
@Override
public Object code(@NonNull SpannableTheme theme, boolean multiline) {
return new CodeSpan(theme, multiline);
}
@Nullable
@Override
public Object orderedListItem(@NonNull SpannableTheme theme, int startNumber) {
// todo| in order to provide real RTL experience there must be a way to provide this string
return new OrderedListItemSpan(theme, String.valueOf(startNumber) + "." + '\u00a0');
}
@Nullable
@Override
public Object bulletListItem(@NonNull SpannableTheme theme, int level) {
return new BulletListItemSpan(theme, level);
}
@Nullable
@Override
public Object thematicBreak(@NonNull SpannableTheme theme) {
return new ThematicBreakSpan(theme);
}
@Nullable
@Override
public Object heading(@NonNull SpannableTheme theme, int level) {
return new HeadingSpan(theme, level);
}
@Nullable
@Override
public Object strikethrough() {
return new StrikethroughSpan();
}
@Nullable
@Override
public Object taskListItem(@NonNull SpannableTheme theme, int blockIndent, boolean isDone) {
return new TaskListSpan(theme, blockIndent, isDone);
}
@Nullable
@Override
public Object tableRow(@NonNull SpannableTheme theme, @NonNull List<TableRowSpan.Cell> cells, boolean isHeader, boolean isOdd) {
return new TableRowSpan(theme, cells, isHeader, isOdd);
}
@Nullable
@Override
public Object image(@NonNull SpannableTheme theme, @NonNull String destination, @NonNull AsyncDrawable.Loader loader, @NonNull ImageSizeResolver imageSizeResolver, @Nullable ImageSize imageSize, boolean replacementTextIsLink) {
return new AsyncDrawableSpan(
theme,
new AsyncDrawable(
destination,
loader,
imageSizeResolver,
imageSize
),
AsyncDrawableSpan.ALIGN_BOTTOM,
replacementTextIsLink
);
}
@Nullable
@Override
public Object link(@NonNull SpannableTheme theme, @NonNull String destination, @NonNull LinkSpan.Resolver resolver) {
return new LinkSpan(theme, destination, resolver);
}
}

View File

@ -42,6 +42,7 @@ import java.util.List;
import ru.noties.markwon.SpannableBuilder;
import ru.noties.markwon.SpannableConfiguration;
import ru.noties.markwon.SpannableFactory;
import ru.noties.markwon.renderer.html.SpannableHtmlParser;
import ru.noties.markwon.spans.AsyncDrawable;
import ru.noties.markwon.spans.AsyncDrawableSpan;
@ -52,6 +53,7 @@ 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.SpannableTheme;
import ru.noties.markwon.spans.StrongEmphasisSpan;
import ru.noties.markwon.spans.TableRowSpan;
import ru.noties.markwon.spans.TaskListSpan;
@ -66,6 +68,9 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
private final SpannableBuilder builder;
private final Deque<HtmlInlineItem> htmlInlineItems;
private final SpannableTheme theme;
private final SpannableFactory factory;
private int blockQuoteIndent;
private int listLevel;
@ -80,6 +85,9 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
this.configuration = configuration;
this.builder = builder;
this.htmlInlineItems = new ArrayDeque<>(2);
this.theme = configuration.theme();
this.factory = configuration.factory();
}
@Override
@ -91,14 +99,14 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
public void visit(StrongEmphasis strongEmphasis) {
final int length = builder.length();
visitChildren(strongEmphasis);
setSpan(length, new StrongEmphasisSpan());
setSpan(length, factory.strongEmphasis());
}
@Override
public void visit(Emphasis emphasis) {
final int length = builder.length();
visitChildren(emphasis);
setSpan(length, new EmphasisSpan());
setSpan(length, factory.emphasis());
}
@Override
@ -115,7 +123,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
visitChildren(blockQuote);
setSpan(length, new BlockQuoteSpan(configuration.theme()));
setSpan(length, factory.blockQuote(theme));
blockQuoteIndent -= 1;
@ -136,10 +144,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
builder.append(code.getLiteral());
builder.append('\u00a0');
setSpan(length, new CodeSpan(
configuration.theme(),
false
));
setSpan(length, factory.code(theme, false));
}
@Override
@ -174,10 +179,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
);
builder.append('\u00a0').append('\n');
setSpan(length, new CodeSpan(
configuration.theme(),
true
));
setSpan(length, factory.code(theme, true));
newLine();
builder.append('\n');
@ -217,11 +219,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
visitChildren(listItem);
// todo| in order to provide real RTL experience there must be a way to provide this string
setSpan(length, new OrderedListItemSpan(
configuration.theme(),
String.valueOf(start) + "." + '\u00a0'
));
setSpan(length, factory.orderedListItem(theme, start));
// after we have visited the children increment start number
final OrderedList orderedList = (OrderedList) parent;
@ -231,10 +229,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
visitChildren(listItem);
setSpan(length, new BulletListItemSpan(
configuration.theme(),
listLevel - 1
));
setSpan(length, factory.bulletListItem(theme, listLevel - 1));
}
blockQuoteIndent -= 1;
@ -250,7 +245,8 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
final int length = builder.length();
builder.append(' '); // without space it won't render
setSpan(length, new ThematicBreakSpan(configuration.theme()));
setSpan(length, factory.thematicBreak(theme));
newLine();
builder.append('\n');
@ -263,7 +259,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
final int length = builder.length();
visitChildren(heading);
setSpan(length, new HeadingSpan(configuration.theme(), heading.getLevel()));
setSpan(length, factory.heading(theme, heading.getLevel()));
newLine();
@ -305,7 +301,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
final int length = builder.length();
visitChildren(customNode);
setSpan(length, new StrikethroughSpan());
setSpan(length, factory.strikethrough());
} else if (customNode instanceof TaskListItem) {
@ -319,11 +315,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
visitChildren(customNode);
setSpan(length, new TaskListSpan(
configuration.theme(),
blockQuoteIndent,
listItem.done()
));
setSpan(length, factory.taskListItem(theme, blockQuoteIndent, listItem.done()));
newLine();
@ -356,12 +348,11 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
// trimmed from the final result
builder.append('\u00a0');
final TableRowSpan span = new TableRowSpan(
configuration.theme(),
final Object span = factory.tableRow(
theme,
pendingTableRow,
tableRowIsHeader,
tableRows % 2 == 1
);
tableRows % 2 == 1);
tableRows = tableRowIsHeader
? 0
@ -434,15 +425,12 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
setSpan(
length,
new AsyncDrawableSpan(
configuration.theme(),
new AsyncDrawable(
destination,
configuration.asyncDrawableLoader(),
configuration.imageSizeResolver(),
null
),
AsyncDrawableSpan.ALIGN_BOTTOM,
factory.image(
theme,
destination,
configuration.asyncDrawableLoader(),
configuration.imageSizeResolver(),
null,
link
)
);
@ -504,11 +492,22 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
final int length = builder.length();
visitChildren(link);
final String destination = configuration.urlProcessor().process(link.getDestination());
setSpan(length, new LinkSpan(configuration.theme(), destination, configuration.linkResolver()));
setSpan(length, factory.link(theme, destination, configuration.linkResolver()));
}
private void setSpan(int start, @NonNull Object span) {
builder.setSpan(span, start, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
private void setSpan(int start, @Nullable Object span) {
if (span != null) {
final int length = builder.length();
if (span.getClass().isArray()) {
for (Object o: ((Object[]) span)) {
builder.setSpan(o, start, length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
} else {
builder.setSpan(span, start, length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
private void newLine() {