diff --git a/app/build.gradle b/app/build.gradle index 3c747a17..8078c22c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -18,5 +18,7 @@ dependencies { compile project(':library-renderer') compile 'ru.noties:debug:3.0.0@jar' compile 'com.squareup.picasso:picasso:2.5.2' + compile 'com.caverock:androidsvg:1.2.1' + compile 'pl.droidsonroids.gif:android-gif-drawable:1.2.7' compile 'com.squareup.okhttp3:okhttp:3.8.0' } diff --git a/app/src/androidTest/java/ru/noties/markwon/ExampleInstrumentedTest.java b/app/src/androidTest/java/ru/noties/markwon/ExampleInstrumentedTest.java deleted file mode 100644 index fac6b350..00000000 --- a/app/src/androidTest/java/ru/noties/markwon/ExampleInstrumentedTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package ru.noties.markwon; - -import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import static org.junit.Assert.*; - -/** - * Instrumentation test, which will execute on an Android device. - * - * @see Testing documentation - */ -@RunWith(AndroidJUnit4.class) -public class ExampleInstrumentedTest { - @Test - public void useAppContext() throws Exception { - // Context of the app under test. - Context appContext = InstrumentationRegistry.getTargetContext(); - - assertEquals("ru.noties.markwon", appContext.getPackageName()); - } -} diff --git a/app/src/main/assets/scrollable.md b/app/src/main/assets/scrollable.md index 80310183..b6b67ecb 100644 --- a/app/src/main/assets/scrollable.md +++ b/app/src/main/assets/scrollable.md @@ -1,4 +1,4 @@ -![logo](art/scrollable_big_logo.png) +![logo](https://github.com/noties/Scrollable/raw/master/art/scrollable_big_logo.png) [![Maven Central](https://img.shields.io/maven-central/v/ru.noties/scrollable.svg)](http://search.maven.org/#search|ga|1|g%3A%22ru.noties%22%20AND%20a%3A%22scrollable%22) @@ -11,7 +11,7 @@ All GIFs here are taken from `sample` application module. -colorful_sample custom_overscroll_sample dialog_sample +colorful_sample custom_overscroll_sample dialog_sample *Serving suggestion diff --git a/app/src/main/java/ru/noties/markwon/AsyncDrawableLoader.java b/app/src/main/java/ru/noties/markwon/AsyncDrawableLoader.java new file mode 100644 index 00000000..4a3f931f --- /dev/null +++ b/app/src/main/java/ru/noties/markwon/AsyncDrawableLoader.java @@ -0,0 +1,126 @@ +package ru.noties.markwon; + +import android.graphics.Picture; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.PictureDrawable; +import android.net.Uri; +import android.support.annotation.NonNull; +import android.widget.TextView; + +import com.caverock.androidsvg.SVG; +import com.squareup.picasso.Picasso; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +import okhttp3.OkHttpClient; +import pl.droidsonroids.gif.GifDrawable; +import ru.noties.debug.Debug; +import ru.noties.markwon.spans.AsyncDrawable; + +public class AsyncDrawableLoader implements AsyncDrawable.Loader { + + private final TextView view; + private final Picasso picasso; + private final OkHttpClient client; + private final ExecutorService executorService; + private final Map> requests; + + // sh*t.. + public AsyncDrawableLoader(TextView view) { + this.view = view; + this.picasso = new Picasso.Builder(view.getContext()) + .listener(new Picasso.Listener() { + @Override + public void onImageLoadFailed(Picasso picasso, Uri uri, Exception exception) { + Debug.e(exception, picasso, uri); + } + }) + .build(); + this.client = new OkHttpClient(); + this.executorService = Executors.newCachedThreadPool(); + this.requests = new HashMap<>(3); + } + + @Override + public void load(@NonNull String destination, @NonNull AsyncDrawable drawable) { + + Debug.i("destination: %s", destination); + + if (destination.endsWith(".svg")) { + // load svg + requests.put(destination, loadSvg(destination, drawable)); + } else if (destination.endsWith(".gif")) { + requests.put(destination, loadGif(destination, drawable)); + } else { + picasso + .load(destination) + .tag(destination) + .into(new TextViewTarget(view, drawable)); + } + } + + @Override + public void cancel(@NonNull String destination) { + Debug.i("destination: %s", destination); + picasso.cancelTag(destination); + + final Future future = requests.get(destination); + if (future != null) { + future.cancel(true); + } + } + + private Future loadSvg(final String destination, final AsyncDrawable asyncDrawable) { + return executorService.submit(new Runnable() { + @Override + public void run() { + try { + final URL url = new URL(destination); + final HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + final InputStream inputStream = connection.getInputStream(); + final SVG svg = SVG.getFromInputStream(inputStream); + final Picture picture = svg.renderToPicture(); + final Drawable drawable = new PictureDrawable(picture); + drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); + asyncDrawable.setResult(drawable); + + } catch (Throwable t) { + Debug.e(t); + } + } + }); + } + + private Future loadGif(final String destination, final AsyncDrawable asyncDrawable) { + return executorService.submit(new Runnable() { + @Override + public void run() { + try { + final URL url = new URL(destination); + final HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + final InputStream inputStream = connection.getInputStream(); + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + final byte[] buffer = new byte[1024 * 8]; + int read; + while ((read = inputStream.read(buffer, 0, buffer.length)) != -1) { + baos.write(buffer, 0, read); + } + final GifDrawable drawable = new GifDrawable(baos.toByteArray()); + drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); + asyncDrawable.setResult(drawable); + } catch (Throwable t) { + Debug.e(t); + } + } + }); + } +} diff --git a/app/src/main/java/ru/noties/markwon/MainActivity.java b/app/src/main/java/ru/noties/markwon/MainActivity.java index 2aec835e..96e811a1 100644 --- a/app/src/main/java/ru/noties/markwon/MainActivity.java +++ b/app/src/main/java/ru/noties/markwon/MainActivity.java @@ -3,7 +3,6 @@ package ru.noties.markwon; import android.app.Activity; import android.os.Bundle; import android.os.SystemClock; -import android.support.annotation.NonNull; import android.text.method.LinkMovementMethod; import android.widget.TextView; @@ -33,15 +32,7 @@ public class MainActivity extends Activity { final TextView textView = (TextView) findViewById(R.id.activity_main); -// -// final Picasso picasso = new Picasso.Builder(this) -// .listener(new Picasso.Listener() { -// @Override -// public void onImageLoadFailed(Picasso picasso, Uri uri, Exception exception) { -// Debug.i(exception, uri); -// } -// }) -// .build(); + final AsyncDrawable.Loader loader = new AsyncDrawableLoader(textView); new Thread(new Runnable() { @Override @@ -75,17 +66,7 @@ public class MainActivity extends Activity { final long start = SystemClock.uptimeMillis(); final SpannableConfiguration configuration = SpannableConfiguration.builder(MainActivity.this) - .asyncDrawableLoader(new AsyncDrawable.Loader() { - @Override - public void load(@NonNull String destination, @NonNull AsyncDrawable drawable) { - Debug.i("destination: %s, drawable: %s", destination, drawable); - } - - @Override - public void cancel(@NonNull String destination) { - Debug.i("destination: %s", destination); - } - }) + .asyncDrawableLoader(loader) .build(); final CharSequence text = Markwon.markdown(configuration, md); diff --git a/app/src/main/java/ru/noties/markwon/Markwon.java b/app/src/main/java/ru/noties/markwon/Markwon.java deleted file mode 100644 index 2a76c84c..00000000 --- a/app/src/main/java/ru/noties/markwon/Markwon.java +++ /dev/null @@ -1,7 +0,0 @@ -//package ru.noties.markwon; -// -//public class Markwon { -// -// // todo, annotation processor to PRE_COMPILE markdown!! no... multiple lnguages and you are out, forget about it -// // view for debugging (to view in preview) x3! -//} diff --git a/app/src/main/java/ru/noties/markwon/SpannableRenderer.java b/app/src/main/java/ru/noties/markwon/SpannableRenderer.java deleted file mode 100644 index aee93ac4..00000000 --- a/app/src/main/java/ru/noties/markwon/SpannableRenderer.java +++ /dev/null @@ -1,444 +0,0 @@ -//package ru.noties.markwon; -// -//import android.graphics.Canvas; -//import android.graphics.ColorFilter; -//import android.graphics.drawable.Drawable; -//import android.os.Handler; -//import android.os.Looper; -//import android.support.annotation.IntRange; -//import android.support.annotation.NonNull; -//import android.support.annotation.Nullable; -//import android.text.Html; -//import android.text.SpannableStringBuilder; -//import android.text.Spanned; -//import android.text.style.AbsoluteSizeSpan; -//import android.text.style.StrikethroughSpan; -//import android.text.style.URLSpan; -// -//import org.commonmark.ext.gfm.strikethrough.Strikethrough; -//import org.commonmark.node.AbstractVisitor; -//import org.commonmark.node.BlockQuote; -//import org.commonmark.node.BulletList; -//import org.commonmark.node.Code; -//import org.commonmark.node.CustomBlock; -//import org.commonmark.node.CustomNode; -//import org.commonmark.node.Document; -//import org.commonmark.node.Emphasis; -//import org.commonmark.node.FencedCodeBlock; -//import org.commonmark.node.HardLineBreak; -//import org.commonmark.node.Heading; -//import org.commonmark.node.HtmlBlock; -//import org.commonmark.node.HtmlInline; -//import org.commonmark.node.Image; -//import org.commonmark.node.IndentedCodeBlock; -//import org.commonmark.node.Link; -//import org.commonmark.node.ListItem; -//import org.commonmark.node.Node; -//import org.commonmark.node.OrderedList; -//import org.commonmark.node.Paragraph; -//import org.commonmark.node.SoftLineBreak; -//import org.commonmark.node.StrongEmphasis; -//import org.commonmark.node.Text; -//import org.commonmark.node.ThematicBreak; -//import org.commonmark.renderer.Renderer; -// -//import java.util.ArrayDeque; -//import java.util.Arrays; -//import java.util.Deque; -// -//import ru.noties.debug.Debug; -//import ru.noties.markwon.spans.BlockQuoteSpan; -//import ru.noties.markwon.spans.CodeSpan; -//import ru.noties.markwon.spans.AsyncDrawableSpan; -//import ru.noties.markwon.spans.EmphasisSpan; -//import ru.noties.markwon.spans.BulletListItemSpan; -//import ru.noties.markwon.spans.StrongEmphasisSpan; -//import ru.noties.markwon.spans.SubScriptSpan; -//import ru.noties.markwon.spans.SuperScriptSpan; -//import ru.noties.markwon.spans.ThematicBreakSpan; -// -//public class SpannableRenderer implements Renderer { -// -// // todo, util to extract all drawables and attach to textView (gif, animations, lazyLoading, etc) -// -// @Override -// public void render(Node node, Appendable output) { -// -// } -// -// @Override -// public String render(Node node) { -// // hm.. doesn't make sense to render to string -// throw null; -// } -// -// public CharSequence _render(Node node) { -// final SpannableStringBuilder builder = new SpannableStringBuilder(); -// node.accept(new SpannableNodeRenderer(builder)); -// return builder; -// } -// -// private static class SpannableNodeRenderer extends AbstractVisitor { -// -//// private static final float[] HEADING_SIZES = { -//// 1.5f, 1.4f, 1.3f, 1.2f, 1.1f, 1f, -//// }; -// -// private final SpannableStringBuilder builder; -// -// private int blockQuoteIndent; -// private int listLevel; -// -// SpannableNodeRenderer(SpannableStringBuilder builder) { -// this.builder = builder; -// } -// -// @Override -// public void visit(HardLineBreak hardLineBreak) { -// // todo -// Debug.i(hardLineBreak); -// } -// -// @Override -// public void visit(Text text) { -// builder.append(text.getLiteral()); -// } -// -// @Override -// public void visit(StrongEmphasis strongEmphasis) { -// final int length = builder.length(); -// visitChildren(strongEmphasis); -// builder.setSpan(new StrongEmphasisSpan(), length, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); -// } -// -// @Override -// public void visit(Emphasis emphasis) { -// final int length = builder.length(); -// visitChildren(emphasis); -// builder.setSpan(new EmphasisSpan(), length, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); -// } -// -// @Override -// public void visit(IndentedCodeBlock indentedCodeBlock) { -// // todo -// Debug.i(indentedCodeBlock); -// } -// -// @Override -// public void visit(BlockQuote blockQuote) { -// builder.append('\n'); -// final int length = builder.length(); -// blockQuoteIndent += 1; -// visitChildren(blockQuote); -// builder.setSpan(new BlockQuoteSpan(blockQuoteIndent), length, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); -// blockQuoteIndent -= 1; -// } -// -// @Override -// public void visit(Code code) { -// final int length = builder.length(); -// builder.append(code.getLiteral()); -//// builder.setSpan(new ForegroundColorSpan(0xff00ff00), length, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); -// builder.setSpan(new CodeSpan(false, length, builder.length()), length, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); -// } -// -// @Override -// public void visit(BulletList bulletList) { -// Debug.i(bulletList, bulletList.getBulletMarker()); -// visitChildren(bulletList); -// } -// -// @Override -// public void visit(ListItem listItem) { -// Debug.i(listItem); -//// builder.append('\n'); -// if (builder.charAt(builder.length() - 1) != '\n') { -// builder.append('\n'); -// } -// final int length = builder.length(); -// blockQuoteIndent += 1; -// listLevel += 1; -// visitChildren(listItem); -//// builder.setSpan(new BulletSpan(4, 0xff0000ff), length, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); -// builder.setSpan(new BulletListItemSpan(blockQuoteIndent, listLevel > 1, length), length, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); -// blockQuoteIndent -= 1; -// listLevel -= 1; -// } -// -// @Override -// public void visit(ThematicBreak thematicBreak) { -// final int length = builder.length(); -// builder.append('\n') -// .append(' '); // without space it won't render -// builder.setSpan(new ThematicBreakSpan(), length, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); -// builder.append('\n'); -// } -// -// @Override -// public void visit(OrderedList orderedList) { -// Debug.i(orderedList, orderedList.getDelimiter(), orderedList.getStartNumber()); -// // todo, ordering numbers -// super.visit(orderedList); -// } -// -// @Override -// public void visit(SoftLineBreak softLineBreak) { -// Debug.i(softLineBreak); -// } -// -// @Override -// public void visit(Heading heading) { -// Debug.i(heading); -// if (builder.length() != 0 && builder.charAt(builder.length() - 1) != '\n') { -// builder.append('\n'); -// } -// final int length = builder.length(); -// visitChildren(heading); -// final int max = 120; -// final int one = 20; // total is 6 -// final int size = max - ((heading.getLevel() - 1) * one); -// builder.setSpan(new AbsoluteSizeSpan(size), length, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); -// builder.append('\n'); -// } -// -// @Override -// public void visit(FencedCodeBlock fencedCodeBlock) { -// builder.append('\n'); -// final int length = builder.length(); -// builder.append(fencedCodeBlock.getLiteral()); -// builder.setSpan(new CodeSpan(true, length, builder.length() - 1), length, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); -// } -// -// @Override -// public void visit(Paragraph paragraph) { -// Debug.i(paragraph); -// if (listLevel == 0 -// && blockQuoteIndent == 0) { -// builder.append('\n') -// .append('\n'); -// } -// visitChildren(paragraph); -// -// if (listLevel == 0 -// && blockQuoteIndent == 0) { -// builder.append('\n') -// .append('\n'); -// } -// } -// -//// private int htmlStart = -1; -// private final Deque htmlStack = new ArrayDeque<>(); -// -// private static class HtmlInlineItem { -// -// final int start; -// final String tag; -// -// private HtmlInlineItem(int start, String tag) { -// this.start = start; -// this.tag = tag; -// } -// } -// -// @Override -// public void visit(HtmlInline htmlInline) { -// -//// Debug.i(htmlInline, htmlStart); -//// Debug.i(htmlInline.getLiteral(), htmlInline.toString()); -// -// // okay, it's seems that we desperately need to understand if it's opening tag or closing -// -// final HtmlTag tag = parseTag(htmlInline.getLiteral()); -// -// Debug.i(htmlInline.getLiteral(), tag); -// -// if (tag != null) { -// Debug.i("tag: %s, closing: %s", tag.tag, tag.closing); -// if (!tag.closing) { -// htmlStack.push(new HtmlInlineItem(builder.length(), tag.tag)); -// visitChildren(htmlInline); -// } else { -// final HtmlInlineItem item = htmlStack.pop(); -// final int start = item.start; -// final int end = builder.length(); -// // here, additionally, we can render some tags ourselves (sup/sub) -// if ("sup".equals(item.tag)) { -// builder.setSpan(new SuperScriptSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); -// } else if("sub".equals(item.tag)) { -// builder.setSpan(new SubScriptSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); -// } else if("del".equals(item.tag)) { -// // weird, but `Html` class does not return a spannable for `o` -// // seems like a bug -// builder.setSpan(new StrikethroughSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); -// } else { -// final String html = "<" + item.tag + ">" + (builder.subSequence(start, end).toString()) + ""; -// final Spanned spanned = Html.fromHtml(html); -// final Object[] spans = spanned.getSpans(0, spanned.length(), Object.class); -// -// Debug.i("html: %s, start: %d, end: %d, spans: %s", html, start, end, Arrays.toString(spans)); -// -// if (spans != null -// && spans.length > 0) { -// for (Object span: spans) { -// Debug.i(span); -// builder.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); -// } -// } -// } -// } -// } else { -// super.visit(htmlInline); -// } -// } -// -// private static class HtmlTag { -// final String tag; -// final boolean closing; -// HtmlTag(String tag, boolean closing) { -// this.tag = tag; -// this.closing = closing; -// } -// @Override -// public String toString() { -// return "HtmlTag{" + -// "tag='" + tag + '\'' + -// ", closing=" + closing + -// '}'; -// } -// } -// -// private static HtmlTag parseTag(String in) { -// -// final HtmlTag out; -// -// final int length = in != null -// ? in.length() -// : 0; -// -// Debug.i(in, length); -// -// if (length == 0 || length < 3) { -// out = null; -// } else { -// -// final boolean closing = '<' == in.charAt(0) && '/' == in.charAt(1); -// final String tag = closing -// ? in.substring(2, in.length() - 1) -// : in.substring(1, in.length() - 1); -// out = new HtmlTag(tag, closing); -// } -// -// return out; -// } -// -// @Override -// public void visit(HtmlBlock htmlBlock) { -// // interestring thing... what is it also? -// Debug.i(htmlBlock); -// } -// -// @Override -// public void visit(CustomBlock customBlock) { -// // not supported, what is it anyway? -// Debug.i(customBlock); -// } -// -// @Override -// public void visit(Document document) { -// // the whole document, no need to do anything -// Debug.i(document); -// super.visit(document); -// } -// -// @Override -// public void visit(Link link) { -// Debug.i(link); -// final int length = builder.length(); -// visitChildren(link); -// builder.setSpan(new URLSpan(link.getDestination()), length, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); -// } -// -// @Override -// public void visit(Image image) { -// // not supported... maybe for now? -// Debug.i(image); -// final int length = builder.length(); -// super.visit(image); -// -//// final int length = builder.length(); -// final TestDrawable drawable = new TestDrawable(); -// final AsyncDrawableSpan span = new AsyncDrawableSpan(drawable); -// builder.append(" "); -// builder.setSpan(span, length, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); -// } -// -// @Override -// public void visit(CustomNode customNode) { -// -// Debug.i(customNode); -// -// if (customNode instanceof Strikethrough) { -// final int length = builder.length(); -// visitChildren(customNode); -// builder.setSpan(new StrikethroughSpan(), length, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); -// } else { -// super.visit(customNode); -// } -// } -// } -// -// -// private static class TestDrawable extends Drawable { -// -// private final Handler handler = new Handler(Looper.getMainLooper()); -// private boolean called; -// -// TestDrawable() { -// setBounds(0, 0, 50, 50); -// } -// -// @Override -// public void draw(@NonNull final Canvas canvas) { -// canvas.clipRect(getBounds()); -// if (!called) { -// canvas.drawColor(0xFF00ff00); -// handler.removeCallbacksAndMessages(null); -// handler.postDelayed(new Runnable() { -// @Override -// public void run() { -// called = true; -// setBounds(0, 0, 400, 400); -// invalidateSelf(); -// } -// }, 2000L); -// } else { -// canvas.drawColor(0xFFff0000); -// } -// } -// -// @Override -// public void setAlpha(@IntRange(from = 0, to = 255) int alpha) { -// -// } -// -// @Override -// public void setColorFilter(@Nullable ColorFilter colorFilter) { -// -// } -// -// @Override -// public int getOpacity() { -// return 0; -// } -// -// @Override -// public int getIntrinsicWidth() { -// return called ? 400 : 50; -// } -// -// @Override -// public int getIntrinsicHeight() { -// return called ? 400 : 50; -// } -// } -//} diff --git a/app/src/main/java/ru/noties/markwon/TextViewTarget.java b/app/src/main/java/ru/noties/markwon/TextViewTarget.java new file mode 100644 index 00000000..c491beee --- /dev/null +++ b/app/src/main/java/ru/noties/markwon/TextViewTarget.java @@ -0,0 +1,74 @@ +package ru.noties.markwon; + +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.widget.TextView; + +import com.squareup.picasso.Picasso; +import com.squareup.picasso.Target; + +import java.util.ArrayList; +import java.util.List; + +import ru.noties.markwon.spans.AsyncDrawable; + +public class TextViewTarget implements Target { + + private final TextView view; + private final AsyncDrawable asyncDrawable; + + public TextViewTarget(TextView view, AsyncDrawable asyncDrawable) { + this.view = view; + this.asyncDrawable = asyncDrawable; + + attach(); + } + + @Override + public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) { + if (bitmap != null) { + final Drawable drawable = new BitmapDrawable(view.getResources(), bitmap); + drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); + asyncDrawable.setResult(drawable); + } + detach(); + } + + @Override + public void onBitmapFailed(Drawable errorDrawable) { + if (errorDrawable != null) { + asyncDrawable.setResult(errorDrawable); + } + detach(); + } + + @Override + public void onPrepareLoad(Drawable placeHolderDrawable) { + if (placeHolderDrawable != null) { + asyncDrawable.setResult(placeHolderDrawable); + } + } + + private void attach() { + + // amazing stuff here, in order to keep this target alive (picasso stores target in a WeakReference) + // we need to do this + + //noinspection unchecked + List list = (List) view.getTag(R.id.amazing); + if (list == null) { + list = new ArrayList<>(2); + view.setTag(R.id.amazing, list); + } + list.add(this); + } + + private void detach() { + //noinspection unchecked + final List list = (List) view.getTag(R.id.amazing); + if (list != null) { + list.remove(this); + } + } +} diff --git a/app/src/main/java/ru/noties/markwon/spans2/BlockQuoteSpan.java b/app/src/main/java/ru/noties/markwon/spans2/BlockQuoteSpan.java deleted file mode 100644 index 68937347..00000000 --- a/app/src/main/java/ru/noties/markwon/spans2/BlockQuoteSpan.java +++ /dev/null @@ -1,45 +0,0 @@ -package ru.noties.markwon.spans2; - -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Rect; -import android.graphics.RectF; -import android.text.Layout; -import android.text.style.LeadingMarginSpan; -import ru.noties.debug.Debug; - -public class BlockQuoteSpan implements LeadingMarginSpan { - - private final int indent; - - public BlockQuoteSpan(int indent) { - this.indent = indent; - } - - @Override - public int getLeadingMargin(boolean first) { - return 24; - } - - @Override - public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, int top, int baseline, int bottom, CharSequence text, int start, int end, boolean first, Layout layout) { -// Debug.i("x: %d, dir: %d, top: %d, baseline: %d, bottom: %d, first: %s", -// x, dir, top, baseline, bottom, first -// ); - - final int save = c.save(); - try { - final int left = 24 * (indent - 1); -// final RectF rectF = new RectF(0, 0, 16, 16); - final Rect rect = new Rect(left, top, left + 8, bottom); - final Paint paint = new Paint(); - paint.setStyle(Paint.Style.FILL); - paint.setColor(0xFFf0f0f0); - c.drawRect(rect, paint); -// c.translate(x, .0F); -// c.drawOval(rectF, paint); - } finally { - c.restoreToCount(save); - } - } -} diff --git a/app/src/main/java/ru/noties/markwon/spans2/CodeSpan.java b/app/src/main/java/ru/noties/markwon/spans2/CodeSpan.java deleted file mode 100644 index 485592b3..00000000 --- a/app/src/main/java/ru/noties/markwon/spans2/CodeSpan.java +++ /dev/null @@ -1,270 +0,0 @@ -package ru.noties.markwon.spans2; - -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Rect; -import android.support.annotation.IntRange; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.text.style.ReplacementSpan; - -import ru.noties.debug.Debug; - -// we will use Replacement span because code blocks cannot contain other markdown -// so we will render the string (not a charSequence with possible metric affecting spans) -public class CodeSpan extends ReplacementSpan/* implements LeadingMarginSpan*/ { - - private final boolean multiline; - private final int start; - private final int end; - - private final Rect rect = new Rect(); - private final Rect borderRect = new Rect(); - private final Paint paint = new Paint(); - - public CodeSpan(boolean multiline, int start, int end) { - this.multiline = multiline; - this.start = start; - this.end = end; - - paint.setStyle(Paint.Style.FILL); - } - - @Override - public int getSize( - @NonNull Paint paint, - CharSequence text, - @IntRange(from = 0) int start, - @IntRange(from = 0) int end, - @Nullable Paint.FontMetricsInt fm - ) { - - final CharSequence cs = text.subSequence(start, end); - final int width = 32 + (int) (paint.measureText(cs, 0, cs.length()) + .5F); - -// final StaticLayout layout = new StaticLayout(cs, new TextPaint(paint), 10000, Layout.Alignment.ALIGN_NORMAL, 1.F, .0F, false); -// final float width = layout.getLineWidth(0); -// final int out = 32 + (int) (width + .5F); - -// Debug.i("text: %s, width: %s", cs, width); - - if (fm != null) { - // we add a padding top & bottom - Debug.i("a: %s, d: %s, t: %s, b: %s", fm.ascent, fm.descent, fm.top, fm.bottom); - final float ratio = .62F; // golden ratio - fm.ascent = fm.ascent - 8; - fm.descent = (int) (-fm.ascent * ratio); - fm.top = fm.ascent; - fm.bottom = fm.descent; - } - - return width; - } - - @Override - public void draw( - @NonNull Canvas canvas, - CharSequence text, - @IntRange(from = 0) int start, - @IntRange(from = 0) int end, - float x, - int top, - int y, - int bottom, - @NonNull Paint paint - ) { - - final int left = (int) (x + .5F); - - final int right; - if (multiline) { - right = canvas.getWidth(); - } else { - final int width = (16 * 2) + (int) (paint.measureText(text, start, end) + .5F); - right = left + width; - } - - rect.set(left, top, right, bottom); - - - // okay, draw background first - drawBackground(canvas); - - // then, if any, draw borders - drawBorders(canvas, this.start == start, this.end == end); - - // draw text - // y center position - final int b = bottom - ((bottom - top) / 2) - (int) ((paint.descent() + paint.ascent()) / 2); -// if (config.textColor != 0) { -// // we will use Paint object that is used to draw all the text (textSize, textColor, typeface, etc) -// paint.setColor(config.textColor); -// } - canvas.drawText(text, start, end, x + 16, b, paint); - - -// Debug.i("text: %s, x: %s, top: %s, y: %s, bottom: %s", text.subSequence(start, end), x, top, y, bottom); -// -// final CharSequence cs = text.subSequence(start, end); -// -// final int width = 32 + (int) (paint.measureText(cs, 0, cs.length()) + .5F); -// -// final int left = (int) (x + .5F); -// final int right = multiline -// ? canvas.getWidth() -// : left + width; -// -// final Rect rect = new Rect( -// left, -// top, -// right, -// bottom -// ); -// -// final Paint p = new Paint(); -// p.setStyle(Paint.Style.FILL); -// p.setColor(0x80ff0000); -// canvas.drawRect(rect, p); -// -// // y center position -// final int b = bottom - ((bottom - top) / 2) - (int) ((paint.descent() + paint.ascent()) / 2); -// p.setColor(0xFF000000); -// canvas.drawText(cs, 0, cs.length(), x + 16, b, paint); - } - - private void drawBackground(Canvas canvas) { -// final int color = config.backgroundColor; -// if (color != 0) { - paint.setColor(0x40ff0000); - canvas.drawRect(rect, paint); -// } - } - - private void drawBorders(Canvas canvas, boolean top, boolean bottom) { - - final int color = 0xFFff0000; - final int width = 4; -// if (color == 0 -// || width == 0) { -// return; -// } - - paint.setColor(color); - - // left and right are always drawn - - // LEFT - borderRect.set(rect.left, rect.top, rect.left + width, rect.bottom); - canvas.drawRect(borderRect, paint); - - // RIGHT - borderRect.set(rect.right - width, rect.top, rect.right, rect.bottom); - canvas.drawRect(borderRect, paint); - - // TOP - if (top) { - borderRect.set(rect.left, rect.top, rect.right, rect.top + width); - canvas.drawRect(borderRect, paint); - } - - // BOTTOM - if (bottom) { - borderRect.set(rect.left, rect.bottom - width, rect.right, rect.bottom); - canvas.drawRect(borderRect, paint); - } - } - - -// @Override -// public int getLeadingMargin(boolean first) { -// return 1; -// } -// -// @Override -// public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, int top, int baseline, int bottom, CharSequence text, int start, int end, boolean first, Layout layout) { -//// Debug.i("x: %d, top: %d, bottom: %d", x, top, bottom); -// -//// Debug.i("this: [%d, %d], came: [%d, %d]", this.start, this.end, start, end); -// Debug.i("x: %d, canvas: [%d-%d], text: %s", x, c.getWidth(), c.getHeight(), (text.subSequence(start, end))); -// -// // the thing is... if we do not draw, then text won't be drawn also -// final Rect rect = new Rect(); -// -// final Paint paint = new Paint(); -// paint.setStyle(Paint.Style.FILL); -// paint.setColor(0xffcccccc); -// -// rect.set(x, top, c.getWidth(), bottom); -// c.drawRect(rect, paint); -// -// if (this.start == start) { -// this.top = top; -// -//// final int save = c.save(); -//// try { -//// c.drawColor(0x00ffffff); -//// } finally { -//// c.restoreToCount(save); -//// } -// -//// c.drawColor(0x00ffffff); -// } -// -// if (this.end == end) { -// // draw borders -// final Rect r = new Rect(x + 1, this.top, c.getWidth() - x, bottom); -// final Paint pa = new Paint(); -// pa.setStyle(Paint.Style.STROKE); -// pa.setColor(0xff999999); -// c.drawRect(r, pa); -// } -//// rect.inset((int) paint.getStrokeWidth(), (int) paint.getStrokeWidth()); -//// paint.setStyle(Paint.Style.STROKE); -//// paint.setColor(0xff333333); -//// c.drawRect(rect, paint); -// } - -// @Override -// public void chooseHeight(CharSequence text, int start, int end, int spanstartv, int v, Paint.FontMetricsInt fm) { -//// int ht = mDrawable.getIntrinsicHeight(); -//// -//// int need = ht - (v + fm.descent - fm.ascent - istartv); -//// if (need > 0) -//// fm.descent += need; -//// -//// need = ht - (v + fm.bottom - fm.top - istartv); -//// if (need > 0) -//// fm.bottom += need; -//// -// -//// final int lineOffset = v - spanstartv; -//// final int desired = 128; -//// final int currentLineHeight = -fm.ascent + fm.descent; -//// final float ratio = (float) desired / currentLineHeight; -//// -//// Debug.i("fm, came: %s", fm); -//// Debug.i("lineOffset: %d, current: %d, ratio: %s", lineOffset, currentLineHeight, ratio); -//// -//// fm.ascent = (int) (ratio * fm.ascent + .5F); -//// fm.descent = (int) (ratio * fm.descent + .5F); -//// -//// Debug.i("fm, out: %s", fm); -// -//// Debug.i("top: %d, bottom: %d, ascent: %d, descent: %d", fm.top, fm.bottom, fm.ascent, fm.descent); -//// Debug.i("lineHeight: %d, v: %d, spanstartv: %d", lineOffset, v, spanstartv); -//// -//// final int h = 128; -//// final int descentNeed = h - (v + fm.descent - fm.ascent - spanstartv); -//// if (descentNeed > 0) { -//// fm.ascent -= descentNeed / 2; -//// fm.descent += descentNeed / 2; -//// } -//// final int bottomNeed = h - (v + fm.bottom - fm.top - spanstartv); -//// if (bottomNeed > 0) { -//// fm.top -= bottomNeed; -//// fm.bottom += bottomNeed; -//// } -//// -//// Debug.i("out, ascent: %d, descent: %d, bottom: %d", fm.ascent, fm.descent, fm.bottom); -// } -} diff --git a/app/src/main/java/ru/noties/markwon/spans2/DrawableSpan.java b/app/src/main/java/ru/noties/markwon/spans2/DrawableSpan.java deleted file mode 100644 index 5da2fbce..00000000 --- a/app/src/main/java/ru/noties/markwon/spans2/DrawableSpan.java +++ /dev/null @@ -1,97 +0,0 @@ -package ru.noties.markwon.spans2; - -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.support.annotation.IntDef; -import android.support.annotation.IntRange; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.text.style.ReplacementSpan; - -public class DrawableSpan extends ReplacementSpan { - - @IntDef({ ALIGN_BOTTOM, ALIGN_BASELINE, ALIGN_CENTER }) - @interface Alignment {} - - public static final int ALIGN_BOTTOM = 0; - public static final int ALIGN_BASELINE = 1; - public static final int ALIGN_CENTER = 2; - - private final Drawable drawable; - private final int alignment; - - public DrawableSpan(@NonNull Drawable drawable) { - this(drawable, ALIGN_BOTTOM); - } - - public DrawableSpan(@NonNull Drawable drawable, @Alignment int alignment) { - this.drawable = drawable; - this.alignment = alignment; - - // additionally set intrinsic bounds if empty - final Rect rect = drawable.getBounds(); - if (rect.isEmpty()) { - drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); - } - } - - @Override - public int getSize( - @NonNull Paint paint, - CharSequence text, - @IntRange(from = 0) int start, - @IntRange(from = 0) int end, - @Nullable Paint.FontMetricsInt fm) { - - final Rect rect = drawable.getBounds(); - - if (fm != null) { - fm.ascent = -rect.bottom; - fm.descent = 0; - - fm.top = fm.ascent; - fm.bottom = 0; - } - - return rect.right; - } - - @Override - public void draw( - @NonNull Canvas canvas, - CharSequence text, - @IntRange(from = 0) int start, - @IntRange(from = 0) int end, - float x, - int top, - int y, - int bottom, - @NonNull Paint paint) { - - final Drawable drawable = this.drawable; - - final int b = bottom - drawable.getBounds().bottom; - - final int save = canvas.save(); - try { - final int translationY; - if (ALIGN_CENTER == alignment) { - translationY = (int) (b / 2.F + .5F); - } else if (ALIGN_BASELINE == alignment) { - translationY = b - paint.getFontMetricsInt().descent; - } else { - translationY = b; - } - canvas.translate(x, translationY); - drawable.draw(canvas); - } finally { - canvas.restoreToCount(save); - } - } - - public Drawable getDrawable() { - return drawable; - } -} diff --git a/app/src/main/java/ru/noties/markwon/spans2/DrawableSpanUtils.java b/app/src/main/java/ru/noties/markwon/spans2/DrawableSpanUtils.java deleted file mode 100644 index 8b3fe20b..00000000 --- a/app/src/main/java/ru/noties/markwon/spans2/DrawableSpanUtils.java +++ /dev/null @@ -1,116 +0,0 @@ -package ru.noties.markwon.spans2; - -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.os.SystemClock; -import android.support.annotation.NonNull; -import android.text.Spanned; -import android.view.View; -import android.widget.TextView; - -import java.util.ArrayList; -import java.util.List; - -import ru.noties.debug.Debug; - -public class DrawableSpanUtils { - - // this method is not completely valid because DynamicDrawableSpan stores - // a drawable in weakReference & it could easily be freed, thus we might need - // to re-schedule a new one, but we have no means to do it - public static void scheduleDrawables(@NonNull final TextView textView) { - - final CharSequence cs = textView.getText(); - final int length = cs != null - ? cs.length() - : 0; - if (length == 0 || !(cs instanceof Spanned)) { - return; - } - - final Object[] spans = ((Spanned) cs).getSpans(0, length, Object.class); - if (spans != null - && spans.length > 0) { - - final List list = new ArrayList<>(2); - - for (Object span: spans) { - if (span instanceof DrawableSpan) { - list.add(((DrawableSpan) span).getDrawable()); - } - } - - if (list.size() > 0) { - textView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { - @Override - public void onViewAttachedToWindow(View v) { - // can it happen that the same view first detached & them attached with all previous content? hm.. - // no op for now - } - - @Override - public void onViewDetachedFromWindow(View v) { - // remove callbacks... - textView.removeOnAttachStateChangeListener(this); - for (Drawable drawable: list) { - drawable.setCallback(null); - } - } - }); - - for (Drawable drawable: list) { - drawable.setCallback(new DrawableCallbackImpl(textView, drawable.getBounds())); - } - } - } - } - - private DrawableSpanUtils() {} - - private static class DrawableCallbackImpl implements Drawable.Callback { - - private final TextView view; - private Rect previousBounds; - - DrawableCallbackImpl(TextView view, Rect initialBounds) { - this.view = view; - this.previousBounds = new Rect(initialBounds); - } - - @Override - public void invalidateDrawable(@NonNull Drawable who) { - - // okay... teh thing is IF we do not change bounds size, normal invalidate would do - // but if the size has changed, then we need to update the whole layout... - - final Rect rect = who.getBounds(); - - if (!previousBounds.equals(rect)) { - // the only method that seems to work when bounds have changed - view.setText(view.getText()); - previousBounds = new Rect(rect); - } else { - // if bounds are the same then simple invalidate would do - final int scrollX = view.getScrollX(); - final int scrollY = view.getScrollY(); - view.postInvalidate( - scrollX + rect.left, - scrollY + rect.top, - scrollX + rect.right, - scrollY + rect.bottom - ); - } - } - - @Override - public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) { - final long delay = when - SystemClock.uptimeMillis(); - view.postDelayed(what, delay); - } - - @Override - public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) { - view.removeCallbacks(what); - } - } -} diff --git a/app/src/main/java/ru/noties/markwon/spans2/EmphasisSpan.java b/app/src/main/java/ru/noties/markwon/spans2/EmphasisSpan.java deleted file mode 100644 index bcd8804b..00000000 --- a/app/src/main/java/ru/noties/markwon/spans2/EmphasisSpan.java +++ /dev/null @@ -1,17 +0,0 @@ -package ru.noties.markwon.spans2; - -import android.text.TextPaint; -import android.text.style.MetricAffectingSpan; - -public class EmphasisSpan extends MetricAffectingSpan { - - @Override - public void updateMeasureState(TextPaint p) { - p.setTextSkewX(-0.25f); - } - - @Override - public void updateDrawState(TextPaint tp) { - tp.setTextSkewX(-0.25f); - } -} diff --git a/app/src/main/java/ru/noties/markwon/spans2/ListItemSpan.java b/app/src/main/java/ru/noties/markwon/spans2/ListItemSpan.java deleted file mode 100644 index 5b81952d..00000000 --- a/app/src/main/java/ru/noties/markwon/spans2/ListItemSpan.java +++ /dev/null @@ -1,50 +0,0 @@ -package ru.noties.markwon.spans2; - -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.RectF; -import android.text.Layout; -import android.text.style.LeadingMarginSpan; -import ru.noties.debug.Debug; - -public class ListItemSpan implements LeadingMarginSpan { - - private final int blockIndent; - private final boolean nested; - private final int start; - - public ListItemSpan(int blockIndent, boolean nested, int start) { - this.blockIndent = blockIndent; - this.nested = nested; - this.start = start; - } - - @Override - public int getLeadingMargin(boolean first) { - return 36; - } - - @Override - public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, int top, int baseline, int bottom, CharSequence text, int start, int end, boolean first, Layout layout) { -// Debug.i("x: %d, dir: %d, top: %d, baseline: %d, bottom: %d, first: %s", -// x, dir, top, baseline, bottom, first -// ); - - // if there was a line break, we don't need to draw it - if (this.start != start) { - return; - } - - final int save = c.save(); - try { - final int left = 24 * (blockIndent - 1) + (first ? 12 : 0); - final RectF rectF = new RectF(left, top, left + 16, bottom); - final Paint paint = new Paint(); - paint.setStyle(nested ? Paint.Style.STROKE : Paint.Style.FILL); - paint.setColor(0xFFff0000); - c.drawOval(rectF, paint); - } finally { - c.restoreToCount(save); - } - } -} diff --git a/app/src/main/java/ru/noties/markwon/spans2/StrongEmphasisSpan.java b/app/src/main/java/ru/noties/markwon/spans2/StrongEmphasisSpan.java deleted file mode 100644 index 0ec3e8b7..00000000 --- a/app/src/main/java/ru/noties/markwon/spans2/StrongEmphasisSpan.java +++ /dev/null @@ -1,17 +0,0 @@ -package ru.noties.markwon.spans2; - -import android.text.TextPaint; -import android.text.style.MetricAffectingSpan; - -public class StrongEmphasisSpan extends MetricAffectingSpan { - - @Override - public void updateMeasureState(TextPaint p) { - p.setFakeBoldText(true); - } - - @Override - public void updateDrawState(TextPaint tp) { - tp.setFakeBoldText(true); - } -} diff --git a/app/src/main/java/ru/noties/markwon/spans2/SubSpan.java b/app/src/main/java/ru/noties/markwon/spans2/SubSpan.java deleted file mode 100644 index 991219c6..00000000 --- a/app/src/main/java/ru/noties/markwon/spans2/SubSpan.java +++ /dev/null @@ -1,19 +0,0 @@ -package ru.noties.markwon.spans2; - -import android.text.TextPaint; -import android.text.style.MetricAffectingSpan; - -public class SubSpan extends MetricAffectingSpan { - - @Override - public void updateDrawState(TextPaint tp) { - tp.setTextSize(tp.getTextSize() * .75F); - tp.baselineShift -= (int) (tp.ascent() / 2); - } - - @Override - public void updateMeasureState(TextPaint tp) { - tp.setTextSize(tp.getTextSize() * .75F); - tp.baselineShift -= (int) (tp.ascent() / 2); - } -} diff --git a/app/src/main/java/ru/noties/markwon/spans2/SupSpan.java b/app/src/main/java/ru/noties/markwon/spans2/SupSpan.java deleted file mode 100644 index 044efc70..00000000 --- a/app/src/main/java/ru/noties/markwon/spans2/SupSpan.java +++ /dev/null @@ -1,19 +0,0 @@ -package ru.noties.markwon.spans2; - -import android.text.TextPaint; -import android.text.style.MetricAffectingSpan; - -public class SupSpan extends MetricAffectingSpan { - - @Override - public void updateDrawState(TextPaint tp) { - tp.setTextSize(tp.getTextSize() * .75F); - tp.baselineShift += (int) (tp.ascent() / 2); - } - - @Override - public void updateMeasureState(TextPaint tp) { - tp.setTextSize(tp.getTextSize() * .75F); - tp.baselineShift += (int) (tp.ascent() / 2); - } -} diff --git a/app/src/main/java/ru/noties/markwon/spans2/ThematicBreakSpan.java b/app/src/main/java/ru/noties/markwon/spans2/ThematicBreakSpan.java deleted file mode 100644 index 731c9628..00000000 --- a/app/src/main/java/ru/noties/markwon/spans2/ThematicBreakSpan.java +++ /dev/null @@ -1,25 +0,0 @@ -package ru.noties.markwon.spans2; - -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Rect; -import android.text.Layout; -import android.text.style.LeadingMarginSpan; - -public class ThematicBreakSpan implements LeadingMarginSpan { - - @Override - public int getLeadingMargin(boolean first) { - return 1; - } - - @Override - public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, int top, int baseline, int bottom, CharSequence text, int start, int end, boolean first, Layout layout) { - final int middle = (bottom - top) / 2; - final Rect rect = new Rect(0, top + middle - 2, c.getWidth(), top + middle + 2); - final Paint paint = new Paint(); - paint.setStyle(Paint.Style.FILL); - paint.setColor(0x80000000); - c.drawRect(rect, paint); - } -} diff --git a/app/src/main/res/values/ids.xml b/app/src/main/res/values/ids.xml new file mode 100644 index 00000000..edca2c81 --- /dev/null +++ b/app/src/main/res/values/ids.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/src/test/java/ru/noties/markwon/ExampleUnitTest.java b/app/src/test/java/ru/noties/markwon/ExampleUnitTest.java deleted file mode 100644 index 10bd839a..00000000 --- a/app/src/test/java/ru/noties/markwon/ExampleUnitTest.java +++ /dev/null @@ -1,17 +0,0 @@ -package ru.noties.markwon; - -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * Example local unit test, which will execute on the development machine (host). - * - * @see Testing documentation - */ -public class ExampleUnitTest { - @Test - public void addition_isCorrect() throws Exception { - assertEquals(4, 2 + 2); - } -} \ No newline at end of file diff --git a/library-renderer/src/main/java/ru/noties/markwon/DrawablesScheduler.java b/library-renderer/src/main/java/ru/noties/markwon/DrawablesScheduler.java index c385b749..e5f92524 100644 --- a/library-renderer/src/main/java/ru/noties/markwon/DrawablesScheduler.java +++ b/library-renderer/src/main/java/ru/noties/markwon/DrawablesScheduler.java @@ -2,6 +2,7 @@ package ru.noties.markwon; import android.graphics.Rect; import android.graphics.drawable.Drawable; +import android.os.Looper; import android.os.SystemClock; import android.support.annotation.NonNull; import android.text.Spanned; @@ -13,6 +14,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import ru.noties.debug.Debug; import ru.noties.markwon.spans.AsyncDrawable; import ru.noties.markwon.spans.AsyncDrawableSpan; @@ -21,6 +23,7 @@ abstract class DrawablesScheduler { static void schedule(@NonNull final TextView textView) { final List list = extract(textView); + Debug.i(list); if (list.size() > 0) { textView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { @Override @@ -36,13 +39,15 @@ abstract class DrawablesScheduler { }); for (AsyncDrawable d : list) { - d.setCallback2(new DrawableCallbackImpl(textView, d.getBounds())); + Debug.i(d); + d.setCallback2(new DrawableCallbackImpl(textView, null, d.getBounds())); } } } // must be called when text manually changed in TextView static void unschedule(@NonNull TextView view) { + Debug.i(); for (AsyncDrawable d : extract(view)) { d.setCallback2(null); } @@ -93,38 +98,61 @@ abstract class DrawablesScheduler { private DrawablesScheduler() { } + private interface CoordinatesProvider { + int getX(); + int getY(); + } + private static class DrawableCallbackImpl implements Drawable.Callback { private final TextView view; + private final CoordinatesProvider coordinatesProvider; private Rect previousBounds; - DrawableCallbackImpl(TextView view, Rect initialBounds) { + DrawableCallbackImpl(TextView view, CoordinatesProvider provider, Rect initialBounds) { this.view = view; + this.coordinatesProvider = provider; this.previousBounds = new Rect(initialBounds); } @Override - public void invalidateDrawable(@NonNull Drawable who) { + public void invalidateDrawable(@NonNull final Drawable who) { + + if (Looper.myLooper() != Looper.getMainLooper()) { + view.post(new Runnable() { + @Override + public void run() { + invalidateDrawable(who); + } + }); + return; + } + + final Rect rect = who.getBounds(); // okay... teh thing is IF we do not change bounds size, normal invalidate would do // but if the size has changed, then we need to update the whole layout... - final Rect rect = who.getBounds(); - if (!previousBounds.equals(rect)) { // the only method that seems to work when bounds have changed view.setText(view.getText()); previousBounds = new Rect(rect); } else { - // if bounds are the same then simple invalidate would do - final int scrollX = view.getScrollX(); - final int scrollY = view.getScrollY(); - view.postInvalidate( - scrollX + rect.left, - scrollY + rect.top, - scrollX + rect.right, - scrollY + rect.bottom - ); +// // if bounds are the same then simple invalidate would do +// if (coordinatesProvider != null) { +// final int x = coordinatesProvider.getX(); +// final int y = coordinatesProvider.getY(); +// view.postInvalidate( +// x + rect.left, +// y + rect.top, +// x + rect.right, +// y + rect.bottom +// ); +// } else { +// // else all we can do is request full re-draw... maybe system is smart enough not re-draw what is not on screen? +// view.postInvalidate(); +// } + view.postInvalidate(); } } @@ -139,4 +167,23 @@ abstract class DrawablesScheduler { view.removeCallbacks(what); } } + + private static class AsyncDrawableCoordinatesProvider implements CoordinatesProvider { + + private final AsyncDrawableSpan span; + + private AsyncDrawableCoordinatesProvider(AsyncDrawableSpan span) { + this.span = span; + } + + @Override + public int getX() { + return span.lastKnownDrawX(); + } + + @Override + public int getY() { + return span.lastKnownDrawY(); + } + } } diff --git a/library-renderer/src/main/java/ru/noties/markwon/spans/AsyncDrawable.java b/library-renderer/src/main/java/ru/noties/markwon/spans/AsyncDrawable.java index ab90e754..b5d0fe8c 100644 --- a/library-renderer/src/main/java/ru/noties/markwon/spans/AsyncDrawable.java +++ b/library-renderer/src/main/java/ru/noties/markwon/spans/AsyncDrawable.java @@ -20,6 +20,7 @@ public class AsyncDrawable extends Drawable { private final Loader loader; private Drawable result; + private Callback callback; public AsyncDrawable(@NonNull String destination, @NonNull Loader loader) { this.destination = destination; @@ -45,6 +46,7 @@ public class AsyncDrawable extends Drawable { // yeah public void setCallback2(@Nullable Callback callback) { + this.callback = callback; super.setCallback(callback); // if not null -> means we are attached @@ -58,7 +60,7 @@ public class AsyncDrawable extends Drawable { } } - public void setResult(Drawable result) { + public void setResult(@NonNull Drawable result) { // if we have previous one, detach it if (this.result != null) { @@ -66,7 +68,7 @@ public class AsyncDrawable extends Drawable { } this.result = result; - this.result.setCallback(getCallback()); + this.result.setCallback(callback); // should we copy the data here? like bounds etc? // if we are async and we load some image from some source diff --git a/library-renderer/src/main/java/ru/noties/markwon/spans/AsyncDrawableSpan.java b/library-renderer/src/main/java/ru/noties/markwon/spans/AsyncDrawableSpan.java index 637c0e95..c2321850 100644 --- a/library-renderer/src/main/java/ru/noties/markwon/spans/AsyncDrawableSpan.java +++ b/library-renderer/src/main/java/ru/noties/markwon/spans/AsyncDrawableSpan.java @@ -25,6 +25,9 @@ public class AsyncDrawableSpan extends ReplacementSpan { private final int alignment; private final boolean replacementTextIsLink; + private int lastKnownDrawX; + private int lastKnownDrawY; + public AsyncDrawableSpan(@NonNull SpannableTheme theme, @NonNull AsyncDrawable drawable) { this(theme, drawable, ALIGN_BOTTOM); } @@ -105,6 +108,9 @@ public class AsyncDrawableSpan extends ReplacementSpan { int bottom, @NonNull Paint paint) { + this.lastKnownDrawX = (int) (x + .5F); + this.lastKnownDrawY = y; + final AsyncDrawable drawable = this.drawable; if (drawable.hasResult()) { @@ -144,4 +150,12 @@ public class AsyncDrawableSpan extends ReplacementSpan { public AsyncDrawable getDrawable() { return drawable; } + + public int lastKnownDrawX() { + return lastKnownDrawX; + } + + public int lastKnownDrawY() { + return lastKnownDrawY; + } } diff --git a/library-view/build.gradle b/library-view/build.gradle deleted file mode 100644 index fa1bfd9d..00000000 --- a/library-view/build.gradle +++ /dev/null @@ -1,19 +0,0 @@ -apply plugin: 'com.android.library' - -android { - - compileSdkVersion TARGET_SDK - buildToolsVersion BUILD_TOOLS - - defaultConfig { - minSdkVersion MIN_SDK - targetSdkVersion TARGET_SDK - versionCode 1 - versionName version - } -} - -dependencies { - compile project(':library-renderer') - compile SUPPORT_ANNOTATIONS -} \ No newline at end of file diff --git a/library-view/src/main/AndroidManifest.xml b/library-view/src/main/AndroidManifest.xml deleted file mode 100644 index f763152f..00000000 --- a/library-view/src/main/AndroidManifest.xml +++ /dev/null @@ -1 +0,0 @@ - diff --git a/library-view/src/main/java/ru/noties/markwon/view/MarkdownTextView.java b/library-view/src/main/java/ru/noties/markwon/view/MarkdownTextView.java deleted file mode 100644 index a229a859..00000000 --- a/library-view/src/main/java/ru/noties/markwon/view/MarkdownTextView.java +++ /dev/null @@ -1,59 +0,0 @@ -package ru.noties.markwon.view; - -import android.annotation.TargetApi; -import android.content.Context; -import android.os.Build; -import android.support.annotation.Nullable; -import android.util.AttributeSet; -import android.widget.TextView; - -import org.commonmark.ext.gfm.strikethrough.StrikethroughExtension; -import org.commonmark.node.Node; -import org.commonmark.parser.Parser; - -import java.util.Collections; - -import ru.noties.markwon.SpannableConfiguration; -import ru.noties.markwon.renderer.SpannableRenderer; - -public class MarkdownTextView extends TextView { - - private Parser parser; - private SpannableRenderer renderer; - private SpannableConfiguration configuration; - - public MarkdownTextView(Context context) { - super(context); - } - - public MarkdownTextView(Context context, @Nullable AttributeSet attrs) { - super(context, attrs); - } - - public MarkdownTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - public MarkdownTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - } - - @Override - public void setText(CharSequence text, BufferType type) { - if (parser == null) { - parser = Parser.builder() - .extensions(Collections.singletonList(StrikethroughExtension.create())) - .build(); - } - if (renderer == null) { - renderer = new SpannableRenderer(); - } - if (configuration == null) { - configuration = SpannableConfiguration.create(getContext()); - } - final Node node = parser.parse(text.toString()); - final CharSequence cs = renderer.render(configuration, node); - super.setText(cs, type); - } -} diff --git a/settings.gradle b/settings.gradle index 330339ba..43603efd 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -include ':app', ':library-renderer', ':library-view' +include ':app', ':library-renderer'