diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index d3266265..f85d8750 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -37,6 +37,7 @@ + diff --git a/sample/src/main/java/io/noties/markwon/sample/MainActivity.java b/sample/src/main/java/io/noties/markwon/sample/MainActivity.java index a8391ce8..2ac55d99 100644 --- a/sample/src/main/java/io/noties/markwon/sample/MainActivity.java +++ b/sample/src/main/java/io/noties/markwon/sample/MainActivity.java @@ -28,6 +28,7 @@ import io.noties.markwon.sample.htmldetails.HtmlDetailsActivity; import io.noties.markwon.sample.images.ImagesActivity; import io.noties.markwon.sample.inlineparser.InlineParserActivity; import io.noties.markwon.sample.latex.LatexActivity; +import io.noties.markwon.sample.notification.NotificationActivity; import io.noties.markwon.sample.precomputed.PrecomputedActivity; import io.noties.markwon.sample.recycler.RecyclerActivity; import io.noties.markwon.sample.simpleext.SimpleExtActivity; @@ -142,6 +143,10 @@ public class MainActivity extends Activity { activity = ImagesActivity.class; break; + case REMOTE_VIEWS: + activity = NotificationActivity.class; + break; + default: throw new IllegalStateException("No Activity is associated with sample-item: " + item); } diff --git a/sample/src/main/java/io/noties/markwon/sample/Sample.java b/sample/src/main/java/io/noties/markwon/sample/Sample.java index bf05297d..f18ed25b 100644 --- a/sample/src/main/java/io/noties/markwon/sample/Sample.java +++ b/sample/src/main/java/io/noties/markwon/sample/Sample.java @@ -31,7 +31,9 @@ public enum Sample { TASK_LIST(R.string.sample_task_list), - IMAGES(R.string.sample_images); + IMAGES(R.string.sample_images), + + REMOTE_VIEWS(R.string.sample_remote_views); private final int textResId; diff --git a/sample/src/main/java/io/noties/markwon/sample/notification/NotificationActivity.java b/sample/src/main/java/io/noties/markwon/sample/notification/NotificationActivity.java new file mode 100644 index 00000000..0012f41f --- /dev/null +++ b/sample/src/main/java/io/noties/markwon/sample/notification/NotificationActivity.java @@ -0,0 +1,254 @@ +package io.noties.markwon.sample.notification; + +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Typeface; +import android.os.Build; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.style.AbsoluteSizeSpan; +import android.text.style.BulletSpan; +import android.text.style.DynamicDrawableSpan; +import android.text.style.ImageSpan; +import android.text.style.QuoteSpan; +import android.text.style.StrikethroughSpan; +import android.text.style.StyleSpan; + +import androidx.annotation.NonNull; + +import org.commonmark.ext.gfm.strikethrough.Strikethrough; +import org.commonmark.node.BlockQuote; +import org.commonmark.node.Emphasis; +import org.commonmark.node.Heading; +import org.commonmark.node.ListItem; +import org.commonmark.node.StrongEmphasis; + +import io.noties.debug.Debug; +import io.noties.markwon.AbstractMarkwonPlugin; +import io.noties.markwon.Markwon; +import io.noties.markwon.MarkwonSpansFactory; +import io.noties.markwon.core.CoreProps; +import io.noties.markwon.ext.strikethrough.StrikethroughPlugin; +import io.noties.markwon.sample.ActivityWithMenuOptions; +import io.noties.markwon.sample.MenuOptions; +import io.noties.markwon.sample.R; + +public class NotificationActivity extends ActivityWithMenuOptions { + + private static final String CHANNEL_ID = "whatever"; + + @NonNull + @Override + public MenuOptions menuOptions() { + return MenuOptions.create() + .add("bold-italic", this::bold_italic) + .add("heading", this::heading) + .add("lists", this::lists) + .add("image", this::image) + .add("link", this::link) + .add("blockquote", this::blockquote) + .add("strikethrough", this::strikethrough); + } + + private void bold_italic() { + // Unfortunately we cannot just use Markwon created CharSequence in a RemoteViews context + // because it requires for spans to be platform ones + + final String md = "Just a **bold** here and _italic_, but what if **it is bold _and italic_**?"; + final Markwon markwon = Markwon.builder(this) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) { + builder + .setFactory(StrongEmphasis.class, (configuration, props) -> new StyleSpan(Typeface.BOLD)) + .setFactory(Emphasis.class, (configuration, props) -> new StyleSpan(Typeface.ITALIC)); + } + }) + .build(); + display(markwon.toMarkdown(md)); + } + + private void heading() { + + // please note that heading doesn't seem to be working in remote views, + // tried both `RelativeSizeSpan` and `AbsoluteSizeSpan` with no effect + + final float base = 12; + + final float[] sizes = { + 2.F, 1.5F, 1.17F, 1.F, .83F, .67F, + }; + + final String md = "" + + "# H1\n" + + "## H2\n" + + "### H3\n" + + "#### H4\n" + + "##### H5\n" + + "###### H6\n\n"; + + final Markwon markwon = Markwon.builder(this) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) { + builder.setFactory(Heading.class, (configuration, props) -> { + final Integer level = CoreProps.HEADING_LEVEL.get(props); + Debug.i(level); + if (level != null && level > 0 && level <= sizes.length) { +// return new RelativeSizeSpan(sizes[level - 1]); + final Object span = new AbsoluteSizeSpan((int) (base * sizes[level - 1] + .5F), true); + return new Object[]{ + span, + new StyleSpan(Typeface.BOLD) + }; + } + return null; + }); + } + }) + .build(); + display(markwon.toMarkdown(md)); + } + + private void lists() { + final String md = "" + + "* bullet 1\n" + + "* bullet 2\n" + + "* * bullet 2 1\n" + + " * bullet 2 0 1\n" + + "1) order 1\n" + + "1) order 2\n" + + "1) order 3\n"; + + // ordered lists _could_ be translated to raw text representation (`1.`, `1)` etc) in resulting markdown + // or they could be _disabled_ all together... (can ordered lists be disabled in parser?) + + final Markwon markwon = Markwon.builder(this) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) { + builder.setFactory(ListItem.class, (configuration, props) -> { + final CoreProps.ListItemType type = CoreProps.LIST_ITEM_TYPE.get(props); + if (type != null) { + // bullet and ordered list share the same markdown node + return new BulletSpan(); + } + return null; + }); + } + }) + .build(); + + display(markwon.toMarkdown(md)); + } + + private void image() { + // please note that image _could_ be supported only if it would be available immediately + // debugging possibility + // + // doesn't seem to be working + + final Bitmap bitmap = Bitmap.createBitmap(128, 256, Bitmap.Config.ARGB_4444); + final Canvas canvas = new Canvas(bitmap); + canvas.drawColor(0xFFAD1457); + + final SpannableStringBuilder builder = new SpannableStringBuilder(); + builder.append("An image: "); + + final int length = builder.length(); + builder.append("[bitmap]"); + builder.setSpan( + new ImageSpan(this, bitmap, DynamicDrawableSpan.ALIGN_BOTTOM), + length, + builder.length(), + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE + ); + + builder.append(" okay, and "); + + final int start = builder.length(); + builder.append("[resource]"); + builder.setSpan( + new ImageSpan(this, R.drawable.ic_memory_black_48dp), + start, + builder.length(), + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE + ); + + display(builder); + } + + private void link() { + final String md = "" + + "[a link](https://isa.link/) is here, styling yes, clicking - no"; + display(Markwon.create(this).toMarkdown(md)); + } + + private void blockquote() { + final String md = "" + + "> This was once said by me\n" + + "> > And this one also\n\n" + + "Me"; + final Markwon markwon = Markwon.builder(this) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) { + builder.setFactory(BlockQuote.class, (configuration, props) -> new QuoteSpan()); + } + }) + .build(); + display(markwon.toMarkdown(md)); + } + + private void strikethrough() { + final String md = "~~strike that!~~"; + final Markwon markwon = Markwon.builder(this) + .usePlugin(new StrikethroughPlugin()) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) { + builder.setFactory(Strikethrough.class, (configuration, props) -> new StrikethroughSpan()); + } + }) + .build(); + display(markwon.toMarkdown(md)); + } + + private void display(@NonNull CharSequence cs) { + final NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); + if (manager == null) { + throw new IllegalStateException("No NotificationManager is available"); + } + + ensureChannel(manager); + + final Notification.Builder builder = new Notification.Builder(this) + .setSmallIcon(R.drawable.ic_stat_name) + .setContentTitle("Markwon") + .setContentText(cs) + .setStyle(new Notification.BigTextStyle().bigText(cs)); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + builder.setChannelId(CHANNEL_ID); + } + + manager.notify(1, builder.build()); + } + + private void ensureChannel(@NonNull NotificationManager manager) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + return; + } + + final NotificationChannel channel = manager.getNotificationChannel(CHANNEL_ID); + if (channel == null) { + manager.createNotificationChannel(new NotificationChannel( + CHANNEL_ID, + CHANNEL_ID, + NotificationManager.IMPORTANCE_DEFAULT)); + } + } +} diff --git a/sample/src/main/res/drawable-anydpi-v24/ic_stat_name.xml b/sample/src/main/res/drawable-anydpi-v24/ic_stat_name.xml new file mode 100644 index 00000000..fd7cefc2 --- /dev/null +++ b/sample/src/main/res/drawable-anydpi-v24/ic_stat_name.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/sample/src/main/res/drawable-hdpi/ic_stat_name.png b/sample/src/main/res/drawable-hdpi/ic_stat_name.png new file mode 100644 index 00000000..19e7a26b Binary files /dev/null and b/sample/src/main/res/drawable-hdpi/ic_stat_name.png differ diff --git a/sample/src/main/res/drawable-mdpi/ic_stat_name.png b/sample/src/main/res/drawable-mdpi/ic_stat_name.png new file mode 100644 index 00000000..0525d874 Binary files /dev/null and b/sample/src/main/res/drawable-mdpi/ic_stat_name.png differ diff --git a/sample/src/main/res/drawable-xhdpi/ic_stat_name.png b/sample/src/main/res/drawable-xhdpi/ic_stat_name.png new file mode 100644 index 00000000..c5f2f076 Binary files /dev/null and b/sample/src/main/res/drawable-xhdpi/ic_stat_name.png differ diff --git a/sample/src/main/res/drawable-xxhdpi/ic_stat_name.png b/sample/src/main/res/drawable-xxhdpi/ic_stat_name.png new file mode 100644 index 00000000..993df1f0 Binary files /dev/null and b/sample/src/main/res/drawable-xxhdpi/ic_stat_name.png differ diff --git a/sample/src/main/res/values/strings-samples.xml b/sample/src/main/res/values/strings-samples.xml index d174e736..0305b471 100644 --- a/sample/src/main/res/values/strings-samples.xml +++ b/sample/src/main/res/values/strings-samples.xml @@ -35,4 +35,6 @@ # \# Images\n\nUsage of different images plugins + # \# Notification\n\nExample usage in notifications and other remote views + \ No newline at end of file