Add recycler-table module
@ -1,4 +1,4 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// this is a generated file, do not modify. To update it run 'collectArtifacts.js' script
 | 
					// this is a generated file, do not modify. To update it run 'collectArtifacts.js' script
 | 
				
			||||||
const artifacts = [{"id":"core","name":"Core","group":"ru.noties.markwon","description":"Core Markwon artifact that includes basic markdown parsing and rendering"},{"id":"ext-latex","name":"LaTeX","group":"ru.noties.markwon","description":"Extension to add LaTeX formulas to Markwon markdown"},{"id":"ext-strikethrough","name":"Strikethrough","group":"ru.noties.markwon","description":"Extension to add strikethrough markup to Markwon markdown"},{"id":"ext-tables","name":"Tables","group":"ru.noties.markwon","description":"Extension to add tables markup (GFM) to Markwon markdown"},{"id":"ext-tasklist","name":"Task List","group":"ru.noties.markwon","description":"Extension to add task lists (GFM) to Markwon markdown"},{"id":"html","name":"HTML","group":"ru.noties.markwon","description":"Provides HTML parsing functionality"},{"id":"image-gif","name":"Image GIF","group":"ru.noties.markwon","description":"Adds GIF media support to Markwon markdown"},{"id":"image-okhttp","name":"Image OkHttp","group":"ru.noties.markwon","description":"Adds OkHttp client to retrieve images data from network"},{"id":"image-svg","name":"Image SVG","group":"ru.noties.markwon","description":"Adds SVG media support to Markwon markdown"},{"id":"recycler","name":"Recycler","group":"ru.noties.markwon","description":"Provides RecyclerView.Adapter to display Markwon markdown"},{"id":"syntax-highlight","name":"Syntax Highlight","group":"ru.noties.markwon","description":"Add syntax highlight to Markwon markdown via Prism4j library"}];
 | 
					const artifacts = [{"id":"core","name":"Core","group":"ru.noties.markwon","description":"Core Markwon artifact that includes basic markdown parsing and rendering"},{"id":"ext-latex","name":"LaTeX","group":"ru.noties.markwon","description":"Extension to add LaTeX formulas to Markwon markdown"},{"id":"ext-strikethrough","name":"Strikethrough","group":"ru.noties.markwon","description":"Extension to add strikethrough markup to Markwon markdown"},{"id":"ext-tables","name":"Tables","group":"ru.noties.markwon","description":"Extension to add tables markup (GFM) to Markwon markdown"},{"id":"ext-tasklist","name":"Task List","group":"ru.noties.markwon","description":"Extension to add task lists (GFM) to Markwon markdown"},{"id":"html","name":"HTML","group":"ru.noties.markwon","description":"Provides HTML parsing functionality"},{"id":"image-gif","name":"Image GIF","group":"ru.noties.markwon","description":"Adds GIF media support to Markwon markdown"},{"id":"image-okhttp","name":"Image OkHttp","group":"ru.noties.markwon","description":"Adds OkHttp client to retrieve images data from network"},{"id":"image-svg","name":"Image SVG","group":"ru.noties.markwon","description":"Adds SVG media support to Markwon markdown"},{"id":"recycler","name":"Recycler","group":"ru.noties.markwon","description":"Provides RecyclerView.Adapter to display Markwon markdown"},{"id":"recycler-table","name":"Recycler Table","group":"ru.noties.markwon","description":"Provides MarkwonAdapter.Entry to render TableBlocks inside Android-native TableLayout widget"},{"id":"syntax-highlight","name":"Syntax Highlight","group":"ru.noties.markwon","description":"Add syntax highlight to Markwon markdown via Prism4j library"}];
 | 
				
			||||||
export { artifacts };
 | 
					export { artifacts };
 | 
				
			||||||
 | 
				
			|||||||
@ -60,6 +60,7 @@ module.exports = {
 | 
				
			|||||||
                '/docs/v3/image/okhttp.md',
 | 
					                '/docs/v3/image/okhttp.md',
 | 
				
			||||||
                '/docs/v3/image/svg.md',
 | 
					                '/docs/v3/image/svg.md',
 | 
				
			||||||
                '/docs/v3/recycler/',
 | 
					                '/docs/v3/recycler/',
 | 
				
			||||||
 | 
					                '/docs/v3/recycler-table/',
 | 
				
			||||||
                '/docs/v3/syntax-highlight/',
 | 
					                '/docs/v3/syntax-highlight/',
 | 
				
			||||||
                '/docs/v3/migration-2-3.md'
 | 
					                '/docs/v3/migration-2-3.md'
 | 
				
			||||||
            ],
 | 
					            ],
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										
											BIN
										
									
								
								docs/.vuepress/public/assets/recycler-table-screenshot.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 77 KiB  | 
@ -54,8 +54,11 @@ final Table table = Table.parse(Markwon, TableBlock);
 | 
				
			|||||||
myTableWidget.setTable(table);
 | 
					myTableWidget.setTable(table);
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Unfortunately Markwon does not provide a widget that can be used for tables. But it does
 | 
					:::tip
 | 
				
			||||||
provide API that can be used to achieve desired result.
 | 
					To take advantage of this functionality and render tables without limitations (including
 | 
				
			||||||
 | 
					horizontally scrollable layout when its contents exceed screen width), refer to [recycler-table](/docs/v3/recycler-table)
 | 
				
			||||||
 | 
					module documentation that adds support for rendering `TableBlock` markdown node inside Android-native `TableLayout` widget.
 | 
				
			||||||
 | 
					:::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Theme
 | 
					## Theme
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -7,4 +7,140 @@ Adds support for GFM (Github-flavored markdown) task-lists:
 | 
				
			|||||||
```java
 | 
					```java
 | 
				
			||||||
Markwon.builder(context)
 | 
					Markwon.builder(context)
 | 
				
			||||||
        .usePlugin(TaskListPlugin.create(context));
 | 
					        .usePlugin(TaskListPlugin.create(context));
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Create a default instance of `TaskListPlugin` with `TaskListDrawable` initialized to use
 | 
				
			||||||
 | 
					`android.R.attr.textColorLink` as primary color and `android.R.attr.colorBackground` as background
 | 
				
			||||||
 | 
					```java
 | 
				
			||||||
 | 
					TaskListPlugin.create(context);
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Create an instance of `TaskListPlugin` with exact color values to use:
 | 
				
			||||||
 | 
					```java
 | 
				
			||||||
 | 
					// obtain color values
 | 
				
			||||||
 | 
					final int checkedFillColor = /* */;
 | 
				
			||||||
 | 
					final int normalOutlineColor = /* */;
 | 
				
			||||||
 | 
					final int checkMarkColor = /* */;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TaskListPlugin.create(checkedFillColor, normalOutlineColor, checkMarkColor);
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Specify own drawable for a task list item:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```java
 | 
				
			||||||
 | 
					// obtain drawable
 | 
				
			||||||
 | 
					final Drawable drawable = /* */;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TaskListPlugin.create(drawable);
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					:::warning
 | 
				
			||||||
 | 
					Please note that custom drawable for a task list item must correctly handle state
 | 
				
			||||||
 | 
					in order to display done/not-done:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```java
 | 
				
			||||||
 | 
					public class MyTaskListDrawable extends Drawable {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private boolean isChecked;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public void draw(@NonNull Canvas canvas) {
 | 
				
			||||||
 | 
					        // draw accordingly to the isChecked value
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    /* implementation omitted */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    protected boolean onStateChange(int[] state) {
 | 
				
			||||||
 | 
					        final boolean isChecked = contains(state, android.R.attr.state_checked);
 | 
				
			||||||
 | 
					        final boolean result = this.isChecked != isChecked;
 | 
				
			||||||
 | 
					        if (result) {
 | 
				
			||||||
 | 
					            this.isChecked = isChecked;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return result;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static boolean contains(@Nullable int[] states, int value) {
 | 
				
			||||||
 | 
					        if (states != null) {
 | 
				
			||||||
 | 
					            for (int state : states) {
 | 
				
			||||||
 | 
					                if (state == value) {
 | 
				
			||||||
 | 
					                    // NB return here
 | 
				
			||||||
 | 
					                    return true;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					:::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Task list mutation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					It is possible to mutate task list item state (toggle done/not-done). But note
 | 
				
			||||||
 | 
					that `Markwon` won't handle state change internally by any means and this change
 | 
				
			||||||
 | 
					is merely a visual one. If you need to persist state of a task list
 | 
				
			||||||
 | 
					item change you have to implement it yourself. This should get your started:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```java
 | 
				
			||||||
 | 
					final Markwon markwon = Markwon.builder(context)
 | 
				
			||||||
 | 
					        .usePlugin(TaskListPlugin.create(context))
 | 
				
			||||||
 | 
					        .usePlugin(new AbstractMarkwonPlugin() {
 | 
				
			||||||
 | 
					            @Override
 | 
				
			||||||
 | 
					            public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // obtain original SpanFactory set by TaskListPlugin
 | 
				
			||||||
 | 
					                final SpanFactory origin = builder.getFactory(TaskListItem.class);
 | 
				
			||||||
 | 
					                if (origin == null) {
 | 
				
			||||||
 | 
					                    // or throw, as it's a bit weird state and we expect
 | 
				
			||||||
 | 
					                    // this factory to be present
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                builder.setFactory(TaskListItem.class, new SpanFactory() {
 | 
				
			||||||
 | 
					                    @Override
 | 
				
			||||||
 | 
					                    public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) {
 | 
				
			||||||
 | 
					                        // it's a bit non-secure behavior and we should validate
 | 
				
			||||||
 | 
					                        // the type of returned span first, but for the sake of brevity
 | 
				
			||||||
 | 
					                        // we skip this step
 | 
				
			||||||
 | 
					                        final TaskListSpan span = (TaskListSpan) origin.getSpans(configuration, props);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        if (span == null) {
 | 
				
			||||||
 | 
					                            // or throw
 | 
				
			||||||
 | 
					                            return null;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        // return an array of spans
 | 
				
			||||||
 | 
					                        return new Object[]{
 | 
				
			||||||
 | 
					                                span,
 | 
				
			||||||
 | 
					                                new ClickableSpan() {
 | 
				
			||||||
 | 
					                                    @Override
 | 
				
			||||||
 | 
					                                    public void onClick(@NonNull View widget) {
 | 
				
			||||||
 | 
					                                        // toggle VISUAL state
 | 
				
			||||||
 | 
					                                        span.setDone(!span.isDone());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                        // do not forget to invalidate widget
 | 
				
			||||||
 | 
					                                        widget.invalidate();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                        // execute your persistence logic
 | 
				
			||||||
 | 
					                                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                    @Override
 | 
				
			||||||
 | 
					                                    public void updateDrawState(@NonNull TextPaint ds) {
 | 
				
			||||||
 | 
					                                        // no-op, so appearance is not changed (otherwise
 | 
				
			||||||
 | 
					                                        // task list item will look like a link)
 | 
				
			||||||
 | 
					                                    }
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					                        };
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        .build();
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
@ -10,62 +10,6 @@ next: /docs/v3/core/getting-started.md
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<ArtifactPicker />
 | 
					<ArtifactPicker />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Bundle <Badge text="3.0.0" />
 | 
					 | 
				
			||||||
If you wish to include all Markwon artifacts or add specific artifacts 
 | 
					 | 
				
			||||||
in a different manner than explicit gradle dependency definition, you can 
 | 
					 | 
				
			||||||
use `markwon-bundle.gradle` gradle script:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
*(in your `build.gradle`)*
 | 
					 | 
				
			||||||
```groovy
 | 
					 | 
				
			||||||
apply plugin: 'com.android.application'
 | 
					 | 
				
			||||||
apply from: 'https://raw.githubusercontent.com/noties/Markwon/master/markwon-bundle.gradle'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
android { /* */ }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
ext.markwon = [
 | 
					 | 
				
			||||||
    'version': '3.0.0',
 | 
					 | 
				
			||||||
    'includeAll': true
 | 
					 | 
				
			||||||
]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
dependencies { /* */ }
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
`markwon` object can have these properties:
 | 
					 | 
				
			||||||
* `version` - (required) version of `Markwon`
 | 
					 | 
				
			||||||
* `includeAll` - if _true_ will add all known Markwon artifacts. Can be used with `exclude`
 | 
					 | 
				
			||||||
* * `exclude` - an array of artifacts to _exclude_ (cannot exclude `core`)
 | 
					 | 
				
			||||||
* `artifacts` - an array of artifacts (can omit `core`, as it will be added implicitly anyway)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
If `includeAll` property is present and is `true`, then `artifacts` property won't be used.
 | 
					 | 
				
			||||||
If there is no `includeAll` property or if it is `false`, `exclude` property won't be used.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
These 2 markwon objects are equal:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
```groovy
 | 
					 | 
				
			||||||
// #1
 | 
					 | 
				
			||||||
ext.markwon = [
 | 
					 | 
				
			||||||
    'version': '3.0.0',
 | 
					 | 
				
			||||||
    'artifacts': [
 | 
					 | 
				
			||||||
        'ext-latex',
 | 
					 | 
				
			||||||
        'ext-strikethrough',
 | 
					 | 
				
			||||||
        'ext-tables',
 | 
					 | 
				
			||||||
        'ext-tasklist',
 | 
					 | 
				
			||||||
        'html',
 | 
					 | 
				
			||||||
        'image-gif',
 | 
					 | 
				
			||||||
        'image-okhttp',
 | 
					 | 
				
			||||||
        'image-svg',
 | 
					 | 
				
			||||||
        'recycler',
 | 
					 | 
				
			||||||
        'syntax-highlight'
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// #2
 | 
					 | 
				
			||||||
ext.markwon = [
 | 
					 | 
				
			||||||
    'version': '3.0.0',
 | 
					 | 
				
			||||||
    'includeAll': true
 | 
					 | 
				
			||||||
]
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Snapshot
 | 
					## Snapshot
 | 
				
			||||||
 | 
					
 | 
				
			||||||
In order to use latest `SNAPSHOT` version add snapshot repository 
 | 
					In order to use latest `SNAPSHOT` version add snapshot repository 
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										90
									
								
								docs/docs/v3/recycler-table/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,90 @@
 | 
				
			|||||||
 | 
					# Recycler Table <Badge text="3.0.0" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Artifact that provides [MarkwonAdapter.Entry](/docs/v3/recycler/) to render `TableBlock` inside 
 | 
				
			||||||
 | 
					Android-native `TableLayout` widget.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<img :src="$withBase('/assets/recycler-table-screenshot.png')" alt="screenshot" width="45%">
 | 
				
			||||||
 | 
					<br>
 | 
				
			||||||
 | 
					<small><em><sup>*</sup> It's possible to wrap `TableLayout` inside a `HorizontalScrollView` to include all table content</em></small>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Register instance of `TableEntry` with `MarkwonAdapter` to render TableBlocks:
 | 
				
			||||||
 | 
					```java
 | 
				
			||||||
 | 
					final MarkwonAdapter adapter = MarkwonAdapter.builder(R.layout.adapter_default_entry, R.id.text)
 | 
				
			||||||
 | 
					        .include(TableBlock.class, TableEntry.create(builder -> builder
 | 
				
			||||||
 | 
					                .tableLayout(R.layout.adapter_table_block, R.id.table_layout)
 | 
				
			||||||
 | 
					                .textLayoutIsRoot(R.layout.view_table_entry_cell)))
 | 
				
			||||||
 | 
					        .build();
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					`TableEntry` requires at least 2 arguments:
 | 
				
			||||||
 | 
					* `tableLayout` - layout with `TableLayout` inside
 | 
				
			||||||
 | 
					* `textLayout` - layout with `TextView` inside (represents independent table cell)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					In case when required view is the root of layout specific builder methods can be used:
 | 
				
			||||||
 | 
					* `tableLayoutIsRoot(int)`
 | 
				
			||||||
 | 
					* `textLayoutIsRoot(int)`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					If your layouts have different structure (for example wrap a `TableView` inside a `HorizontalScrollView`)
 | 
				
			||||||
 | 
					then you should use methods that accept ID of required view inside layout:
 | 
				
			||||||
 | 
					* `tableLayout(int, int)`
 | 
				
			||||||
 | 
					* `textLayout(int, int)`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					To display `TableBlock` as a `TableLayout` specific `MarkwonPlugin` must be used: `TableEntryPlugin`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					:::warning
 | 
				
			||||||
 | 
					Do not use `TablePlugin` if you wish to display markdown tables via `TableEntry`. Use **TableEntryPlugin** instead
 | 
				
			||||||
 | 
					:::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					`TableEntryPlugin` can reuse existing `TablePlugin` to make appearance of tables the same in both contexts:
 | 
				
			||||||
 | 
					when rendering _natively_ in a TextView and when rendering in RecyclerView with TableEntry.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* `TableEntryPlugin.create(Context)` - creates plugin with default `TableTheme`
 | 
				
			||||||
 | 
					* `TableEntryPlugin.create(TableTheme)` - creates plugin with provided `TableTheme`
 | 
				
			||||||
 | 
					* `TableEntryPlugin.create(TablePlugin.ThemeConfigure)` - creates plugin with theme configured by `ThemeConfigure`
 | 
				
			||||||
 | 
					* `TableEntryPlugin.create(TablePlugin)` - creates plugin with `TableTheme` used in provided `TablePlugin`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```java
 | 
				
			||||||
 | 
					final Markwon markwon = Markwon.builder(context)
 | 
				
			||||||
 | 
					        .usePlugin(TableEntryPlugin.create(context))
 | 
				
			||||||
 | 
					        // other plugins
 | 
				
			||||||
 | 
					        .build();
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```java
 | 
				
			||||||
 | 
					final Markwon markwon = Markwon.builder(context)
 | 
				
			||||||
 | 
					        .usePlugin(TableEntryPlugin.create(builder -> builder
 | 
				
			||||||
 | 
					                .tableBorderWidth(0)
 | 
				
			||||||
 | 
					                .tableHeaderRowBackgroundColor(Color.RED)))
 | 
				
			||||||
 | 
					        // other plugins
 | 
				
			||||||
 | 
					        .build();
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Table with scrollable content
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					To stretch table columns to fit the width of screen or to make table scrollable when content exceeds screen width
 | 
				
			||||||
 | 
					this layout can be used:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```xml
 | 
				
			||||||
 | 
					<HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android"
 | 
				
			||||||
 | 
					    android:layout_width="match_parent"
 | 
				
			||||||
 | 
					    android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					    android:clipChildren="false"
 | 
				
			||||||
 | 
					    android:clipToPadding="false"
 | 
				
			||||||
 | 
					    android:paddingLeft="16dip"
 | 
				
			||||||
 | 
					    android:paddingTop="8dip"
 | 
				
			||||||
 | 
					    android:paddingRight="16dip"
 | 
				
			||||||
 | 
					    android:paddingBottom="8dip"
 | 
				
			||||||
 | 
					    android:scrollbarStyle="outsideInset">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <TableLayout
 | 
				
			||||||
 | 
					        android:id="@+id/table_layout"
 | 
				
			||||||
 | 
					        android:layout_width="wrap_content"
 | 
				
			||||||
 | 
					        android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					        android:stretchColumns="*" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</HorizontalScrollView>
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
@ -1,59 +0,0 @@
 | 
				
			|||||||
// await project initialization and check for markwon object then
 | 
					 | 
				
			||||||
// (so we do not have to force users to put `apply from` block at the bottom
 | 
					 | 
				
			||||||
// of a build.gradle file)
 | 
					 | 
				
			||||||
project.afterEvaluate {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (!project.hasProperty('markwon')) {
 | 
					 | 
				
			||||||
        throw new RuntimeException("No `markwon` property object is found. " +
 | 
					 | 
				
			||||||
                "Define it with `ext.markwon = [prop: value]`")
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    final def markwon = project.markwon
 | 
					 | 
				
			||||||
    if (!(markwon instanceof Map)) {
 | 
					 | 
				
			||||||
        throw new RuntimeException("`markwon` object property must be of type Map. " +
 | 
					 | 
				
			||||||
                "Groovy short-hand to define: `[:]`.")
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    final def version = markwon.version
 | 
					 | 
				
			||||||
    final def includeAll = markwon.computeIfAbsent('includeAll', { false })
 | 
					 | 
				
			||||||
    final def artifacts
 | 
					 | 
				
			||||||
    if (includeAll) {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // cannot exclude core
 | 
					 | 
				
			||||||
        final def exclude = markwon.computeIfAbsent('exclude', { [] })  \
 | 
					 | 
				
			||||||
            .unique()  \
 | 
					 | 
				
			||||||
            .findAll { 'core' != it }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        artifacts = [
 | 
					 | 
				
			||||||
                'core',
 | 
					 | 
				
			||||||
                'ext-latex',
 | 
					 | 
				
			||||||
                'ext-strikethrough',
 | 
					 | 
				
			||||||
                'ext-tables',
 | 
					 | 
				
			||||||
                'ext-tasklist',
 | 
					 | 
				
			||||||
                'html',
 | 
					 | 
				
			||||||
                'image-gif',
 | 
					 | 
				
			||||||
                'image-okhttp',
 | 
					 | 
				
			||||||
                'image-svg',
 | 
					 | 
				
			||||||
                'recycler',
 | 
					 | 
				
			||||||
                'syntax-highlight'
 | 
					 | 
				
			||||||
        ].findAll { !exclude.contains(it) }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
        artifacts = (markwon.containsKey('artifacts') ? markwon.artifacts : ['core']).with {
 | 
					 | 
				
			||||||
            // add implicit core artifact
 | 
					 | 
				
			||||||
            if (!it.contains('core')) {
 | 
					 | 
				
			||||||
                it.add('core')
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            return it
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (!version) {
 | 
					 | 
				
			||||||
        throw new RuntimeException("Please specify version of Markwon, for example: " +
 | 
					 | 
				
			||||||
                "`ext.markwon = [ 'version': '1.0.0']`")
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    artifacts.forEach {
 | 
					 | 
				
			||||||
        project.dependencies.add('implementation', "ru.noties.markwon:$it:$version")
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -2,6 +2,7 @@ package ru.noties.markwon;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import android.content.Context;
 | 
					import android.content.Context;
 | 
				
			||||||
import android.support.annotation.NonNull;
 | 
					import android.support.annotation.NonNull;
 | 
				
			||||||
 | 
					import android.support.annotation.Nullable;
 | 
				
			||||||
import android.text.Spanned;
 | 
					import android.text.Spanned;
 | 
				
			||||||
import android.widget.TextView;
 | 
					import android.widget.TextView;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -101,6 +102,9 @@ public abstract class Markwon {
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    public abstract boolean hasPlugin(@NonNull Class<? extends MarkwonPlugin> plugin);
 | 
					    public abstract boolean hasPlugin(@NonNull Class<? extends MarkwonPlugin> plugin);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Nullable
 | 
				
			||||||
 | 
					    public abstract <P extends MarkwonPlugin> P getPlugin(@NonNull Class<P> type);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Builder for {@link Markwon}.
 | 
					     * Builder for {@link Markwon}.
 | 
				
			||||||
     * <p>
 | 
					     * <p>
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,7 @@
 | 
				
			|||||||
package ru.noties.markwon;
 | 
					package ru.noties.markwon;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import android.support.annotation.NonNull;
 | 
					import android.support.annotation.NonNull;
 | 
				
			||||||
 | 
					import android.support.annotation.Nullable;
 | 
				
			||||||
import android.text.Spanned;
 | 
					import android.text.Spanned;
 | 
				
			||||||
import android.widget.TextView;
 | 
					import android.widget.TextView;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -91,13 +92,19 @@ class MarkwonImpl extends Markwon {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public boolean hasPlugin(@NonNull Class<? extends MarkwonPlugin> type) {
 | 
					    public boolean hasPlugin(@NonNull Class<? extends MarkwonPlugin> type) {
 | 
				
			||||||
        boolean result = false;
 | 
					        return getPlugin(type) != null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Nullable
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public <P extends MarkwonPlugin> P getPlugin(@NonNull Class<P> type) {
 | 
				
			||||||
 | 
					        MarkwonPlugin out = null;
 | 
				
			||||||
        for (MarkwonPlugin plugin : plugins) {
 | 
					        for (MarkwonPlugin plugin : plugins) {
 | 
				
			||||||
            if (type.isAssignableFrom(plugin.getClass())) {
 | 
					            if (type.isAssignableFrom(plugin.getClass())) {
 | 
				
			||||||
                result = true;
 | 
					                out = plugin;
 | 
				
			||||||
                break;
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return result;
 | 
					        //noinspection unchecked
 | 
				
			||||||
 | 
					        return (P) out;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -13,6 +13,10 @@ import java.util.List;
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
public abstract class MarkwonReducer {
 | 
					public abstract class MarkwonReducer {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @return direct children of supplied Node. In the most usual case
 | 
				
			||||||
 | 
					     * will return all BlockNodes of a Document
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
    @NonNull
 | 
					    @NonNull
 | 
				
			||||||
    public static MarkwonReducer directChildren() {
 | 
					    public static MarkwonReducer directChildren() {
 | 
				
			||||||
        return new DirectChildren();
 | 
					        return new DirectChildren();
 | 
				
			||||||
 | 
				
			|||||||
@ -34,6 +34,12 @@ public interface MarkwonSpansFactory {
 | 
				
			|||||||
        @NonNull
 | 
					        @NonNull
 | 
				
			||||||
        <N extends Node> Builder setFactory(@NonNull Class<N> node, @Nullable SpanFactory factory);
 | 
					        <N extends Node> Builder setFactory(@NonNull Class<N> node, @Nullable SpanFactory factory);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /**
 | 
				
			||||||
 | 
					         * Can be useful when <em>enhancing</em> an already defined SpanFactory with another one.
 | 
				
			||||||
 | 
					         */
 | 
				
			||||||
 | 
					        @Nullable
 | 
				
			||||||
 | 
					        <N extends Node> SpanFactory getFactory(@NonNull Class<N> node);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        @NonNull
 | 
					        @NonNull
 | 
				
			||||||
        MarkwonSpansFactory build();
 | 
					        MarkwonSpansFactory build();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -52,6 +52,12 @@ class MarkwonSpansFactoryImpl implements MarkwonSpansFactory {
 | 
				
			|||||||
            return this;
 | 
					            return this;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        @Nullable
 | 
				
			||||||
 | 
					        @Override
 | 
				
			||||||
 | 
					        public <N extends Node> SpanFactory getFactory(@NonNull Class<N> node) {
 | 
				
			||||||
 | 
					            return factories.get(node);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        @NonNull
 | 
					        @NonNull
 | 
				
			||||||
        @Override
 | 
					        @Override
 | 
				
			||||||
        public MarkwonSpansFactory build() {
 | 
					        public MarkwonSpansFactory build() {
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,30 @@
 | 
				
			|||||||
 | 
					package ru.noties.markwon.utils;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import android.support.annotation.NonNull;
 | 
				
			||||||
 | 
					import android.text.Spannable;
 | 
				
			||||||
 | 
					import android.text.SpannableString;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Utility SpannableFactory that re-uses Spannable instance between multiple
 | 
				
			||||||
 | 
					 * `TextView#setText` calls.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @since 3.0.0
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class NoCopySpannableFactory extends Spannable.Factory {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @NonNull
 | 
				
			||||||
 | 
					    public static NoCopySpannableFactory getInstance() {
 | 
				
			||||||
 | 
					        return Holder.INSTANCE;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public Spannable newSpannable(CharSequence source) {
 | 
				
			||||||
 | 
					        return source instanceof Spannable
 | 
				
			||||||
 | 
					                ? (Spannable) source
 | 
				
			||||||
 | 
					                : new SpannableString(source);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    static class Holder {
 | 
				
			||||||
 | 
					        private static final NoCopySpannableFactory INSTANCE = new NoCopySpannableFactory();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -54,13 +54,20 @@ public class TablePlugin extends AbstractMarkwonPlugin {
 | 
				
			|||||||
        return new TablePlugin(builder.build());
 | 
					        return new TablePlugin(builder.build());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private final TableTheme theme;
 | 
				
			||||||
    private final TableVisitor visitor;
 | 
					    private final TableVisitor visitor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @SuppressWarnings("WeakerAccess")
 | 
					    @SuppressWarnings("WeakerAccess")
 | 
				
			||||||
    TablePlugin(@NonNull TableTheme tableTheme) {
 | 
					    TablePlugin(@NonNull TableTheme tableTheme) {
 | 
				
			||||||
 | 
					        this.theme = tableTheme;
 | 
				
			||||||
        this.visitor = new TableVisitor(tableTheme);
 | 
					        this.visitor = new TableVisitor(tableTheme);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @NonNull
 | 
				
			||||||
 | 
					    public TableTheme theme() {
 | 
				
			||||||
 | 
					        return theme;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public void configureParser(@NonNull Parser.Builder builder) {
 | 
					    public void configureParser(@NonNull Parser.Builder builder) {
 | 
				
			||||||
        builder.extensions(Collections.singleton(TablesExtension.create()));
 | 
					        builder.extensions(Collections.singleton(TablesExtension.create()));
 | 
				
			||||||
 | 
				
			|||||||
@ -13,15 +13,19 @@ public class TableTheme {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    @NonNull
 | 
					    @NonNull
 | 
				
			||||||
    public static TableTheme create(@NonNull Context context) {
 | 
					    public static TableTheme create(@NonNull Context context) {
 | 
				
			||||||
        final Dip dip = Dip.create(context);
 | 
					        return buildWithDefaults(context).build();
 | 
				
			||||||
        return builder()
 | 
					 | 
				
			||||||
                .tableCellPadding(dip.toPx(4))
 | 
					 | 
				
			||||||
                .tableBorderWidth(dip.toPx(1))
 | 
					 | 
				
			||||||
                .build();
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @NonNull
 | 
					    @NonNull
 | 
				
			||||||
    public static Builder builder() {
 | 
					    public static Builder buildWithDefaults(@NonNull Context context) {
 | 
				
			||||||
 | 
					        final Dip dip = Dip.create(context);
 | 
				
			||||||
 | 
					        return emptyBuilder()
 | 
				
			||||||
 | 
					                .tableCellPadding(dip.toPx(4))
 | 
				
			||||||
 | 
					                .tableBorderWidth(dip.toPx(1));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @NonNull
 | 
				
			||||||
 | 
					    public static Builder emptyBuilder() {
 | 
				
			||||||
        return new Builder();
 | 
					        return new Builder();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -58,6 +62,20 @@ public class TableTheme {
 | 
				
			|||||||
        this.tableHeaderRowBackgroundColor = builder.tableHeaderRowBackgroundColor;
 | 
					        this.tableHeaderRowBackgroundColor = builder.tableHeaderRowBackgroundColor;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @since 3.0.0
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    @NonNull
 | 
				
			||||||
 | 
					    public Builder asBuilder() {
 | 
				
			||||||
 | 
					        return new Builder()
 | 
				
			||||||
 | 
					                .tableCellPadding(tableCellPadding)
 | 
				
			||||||
 | 
					                .tableBorderColor(tableBorderColor)
 | 
				
			||||||
 | 
					                .tableBorderWidth(tableBorderWidth)
 | 
				
			||||||
 | 
					                .tableOddRowBackgroundColor(tableOddRowBackgroundColor)
 | 
				
			||||||
 | 
					                .tableEvenRowBackgroundColor(tableEvenRowBackgroundColor)
 | 
				
			||||||
 | 
					                .tableHeaderRowBackgroundColor(tableHeaderRowBackgroundColor);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public int tableCellPadding() {
 | 
					    public int tableCellPadding() {
 | 
				
			||||||
        return tableCellPadding;
 | 
					        return tableCellPadding;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										29
									
								
								markwon-recycler-table/build.gradle
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,29 @@
 | 
				
			|||||||
 | 
					apply plugin: 'com.android.library'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					android {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    compileSdkVersion config['compile-sdk']
 | 
				
			||||||
 | 
					    buildToolsVersion config['build-tools']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    defaultConfig {
 | 
				
			||||||
 | 
					        minSdkVersion config['min-sdk']
 | 
				
			||||||
 | 
					        targetSdkVersion config['target-sdk']
 | 
				
			||||||
 | 
					        versionCode 1
 | 
				
			||||||
 | 
					        versionName version
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					dependencies {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    api project(':markwon-core')
 | 
				
			||||||
 | 
					    api project(':markwon-recycler')
 | 
				
			||||||
 | 
					    api project(':markwon-ext-tables')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    deps['test'].with {
 | 
				
			||||||
 | 
					        testImplementation it['junit']
 | 
				
			||||||
 | 
					        testImplementation it['robolectric']
 | 
				
			||||||
 | 
					        testImplementation it['mockito']
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					registerArtifact(this)
 | 
				
			||||||
							
								
								
									
										4
									
								
								markwon-recycler-table/gradle.properties
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,4 @@
 | 
				
			|||||||
 | 
					POM_NAME=Recycler Table
 | 
				
			||||||
 | 
					POM_ARTIFACT_ID=recycler-table
 | 
				
			||||||
 | 
					POM_DESCRIPTION=Provides MarkwonAdapter.Entry to render TableBlocks inside Android-native TableLayout widget
 | 
				
			||||||
 | 
					POM_PACKAGING=aar
 | 
				
			||||||
							
								
								
									
										1
									
								
								markwon-recycler-table/src/main/AndroidManifest.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					<manifest package="ru.noties.markwon.recycler.table" />
 | 
				
			||||||
@ -0,0 +1,48 @@
 | 
				
			|||||||
 | 
					package ru.noties.markwon.recycler.table;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import android.graphics.Canvas;
 | 
				
			||||||
 | 
					import android.graphics.ColorFilter;
 | 
				
			||||||
 | 
					import android.graphics.Paint;
 | 
				
			||||||
 | 
					import android.graphics.PixelFormat;
 | 
				
			||||||
 | 
					import android.graphics.drawable.Drawable;
 | 
				
			||||||
 | 
					import android.support.annotation.ColorInt;
 | 
				
			||||||
 | 
					import android.support.annotation.NonNull;
 | 
				
			||||||
 | 
					import android.support.annotation.Nullable;
 | 
				
			||||||
 | 
					import android.support.annotation.Px;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TableBorderDrawable extends Drawable {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    TableBorderDrawable() {
 | 
				
			||||||
 | 
					        paint.setStyle(Paint.Style.STROKE);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public void draw(@NonNull Canvas canvas) {
 | 
				
			||||||
 | 
					        if (paint.getStrokeWidth() > 0) {
 | 
				
			||||||
 | 
					            canvas.drawRect(getBounds(), paint);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public void setAlpha(int alpha) {
 | 
				
			||||||
 | 
					        // no op
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public void setColorFilter(@Nullable ColorFilter colorFilter) {
 | 
				
			||||||
 | 
					        // no op
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public int getOpacity() {
 | 
				
			||||||
 | 
					        return PixelFormat.OPAQUE;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void update(@Px int borderWidth, @ColorInt int color) {
 | 
				
			||||||
 | 
					        paint.setStrokeWidth(borderWidth);
 | 
				
			||||||
 | 
					        paint.setColor(color);
 | 
				
			||||||
 | 
					        invalidateSelf();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,530 @@
 | 
				
			|||||||
 | 
					package ru.noties.markwon.recycler.table;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import android.annotation.SuppressLint;
 | 
				
			||||||
 | 
					import android.content.Context;
 | 
				
			||||||
 | 
					import android.content.res.Resources;
 | 
				
			||||||
 | 
					import android.graphics.drawable.Drawable;
 | 
				
			||||||
 | 
					import android.support.annotation.ColorInt;
 | 
				
			||||||
 | 
					import android.support.annotation.IdRes;
 | 
				
			||||||
 | 
					import android.support.annotation.LayoutRes;
 | 
				
			||||||
 | 
					import android.support.annotation.NonNull;
 | 
				
			||||||
 | 
					import android.support.annotation.Px;
 | 
				
			||||||
 | 
					import android.support.annotation.VisibleForTesting;
 | 
				
			||||||
 | 
					import android.view.Gravity;
 | 
				
			||||||
 | 
					import android.view.LayoutInflater;
 | 
				
			||||||
 | 
					import android.view.View;
 | 
				
			||||||
 | 
					import android.view.ViewGroup;
 | 
				
			||||||
 | 
					import android.widget.TableLayout;
 | 
				
			||||||
 | 
					import android.widget.TableRow;
 | 
				
			||||||
 | 
					import android.widget.TextView;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.commonmark.ext.gfm.tables.TableBlock;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.HashMap;
 | 
				
			||||||
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					import java.util.Map;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import ru.noties.markwon.Markwon;
 | 
				
			||||||
 | 
					import ru.noties.markwon.ext.tables.Table;
 | 
				
			||||||
 | 
					import ru.noties.markwon.recycler.MarkwonAdapter;
 | 
				
			||||||
 | 
					import ru.noties.markwon.utils.NoCopySpannableFactory;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @since 3.0.0
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class TableEntry extends MarkwonAdapter.Entry<TableBlock, TableEntry.Holder> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public interface Builder {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /**
 | 
				
			||||||
 | 
					         * @param tableLayoutResId layout with TableLayout
 | 
				
			||||||
 | 
					         * @param tableIdRes       id of the TableLayout inside specified layout
 | 
				
			||||||
 | 
					         * @see #tableLayoutIsRoot(int)
 | 
				
			||||||
 | 
					         */
 | 
				
			||||||
 | 
					        @NonNull
 | 
				
			||||||
 | 
					        Builder tableLayout(@LayoutRes int tableLayoutResId, @IdRes int tableIdRes);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /**
 | 
				
			||||||
 | 
					         * @param tableLayoutResId layout with TableLayout as the root view
 | 
				
			||||||
 | 
					         * @see #tableLayout(int, int)
 | 
				
			||||||
 | 
					         */
 | 
				
			||||||
 | 
					        @NonNull
 | 
				
			||||||
 | 
					        Builder tableLayoutIsRoot(@LayoutRes int tableLayoutResId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /**
 | 
				
			||||||
 | 
					         * @param textLayoutResId layout with TextView
 | 
				
			||||||
 | 
					         * @param textIdRes       id of the TextView inside specified layout
 | 
				
			||||||
 | 
					         * @see #textLayoutIsRoot(int)
 | 
				
			||||||
 | 
					         */
 | 
				
			||||||
 | 
					        @NonNull
 | 
				
			||||||
 | 
					        Builder textLayout(@LayoutRes int textLayoutResId, @IdRes int textIdRes);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /**
 | 
				
			||||||
 | 
					         * @param textLayoutResId layout with TextView as the root view
 | 
				
			||||||
 | 
					         * @see #textLayout(int, int)
 | 
				
			||||||
 | 
					         */
 | 
				
			||||||
 | 
					        @NonNull
 | 
				
			||||||
 | 
					        Builder textLayoutIsRoot(@LayoutRes int textLayoutResId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /**
 | 
				
			||||||
 | 
					         * @param cellTextCenterVertical if text inside a table cell should centered
 | 
				
			||||||
 | 
					         *                               vertically (by default `true`)
 | 
				
			||||||
 | 
					         */
 | 
				
			||||||
 | 
					        @NonNull
 | 
				
			||||||
 | 
					        Builder cellTextCenterVertical(boolean cellTextCenterVertical);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /**
 | 
				
			||||||
 | 
					         * @param isRecyclable flag to set on RecyclerView.ViewHolder (by default `true`)
 | 
				
			||||||
 | 
					         */
 | 
				
			||||||
 | 
					        @NonNull
 | 
				
			||||||
 | 
					        Builder isRecyclable(boolean isRecyclable);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        @NonNull
 | 
				
			||||||
 | 
					        TableEntry build();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public interface BuilderConfigure {
 | 
				
			||||||
 | 
					        void configure(@NonNull Builder builder);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @NonNull
 | 
				
			||||||
 | 
					    public static Builder builder() {
 | 
				
			||||||
 | 
					        return new BuilderImpl();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @NonNull
 | 
				
			||||||
 | 
					    public static TableEntry create(@NonNull BuilderConfigure configure) {
 | 
				
			||||||
 | 
					        final Builder builder = builder();
 | 
				
			||||||
 | 
					        configure.configure(builder);
 | 
				
			||||||
 | 
					        return builder.build();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private final int tableLayoutResId;
 | 
				
			||||||
 | 
					    private final int tableIdRes;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private final int textLayoutResId;
 | 
				
			||||||
 | 
					    private final int textIdRes;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private final boolean isRecyclable;
 | 
				
			||||||
 | 
					    private final boolean cellTextCenterVertical; // by default true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private LayoutInflater inflater;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private final Map<TableBlock, Table> map = new HashMap<>(3);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    TableEntry(
 | 
				
			||||||
 | 
					            @LayoutRes int tableLayoutResId,
 | 
				
			||||||
 | 
					            @IdRes int tableIdRes,
 | 
				
			||||||
 | 
					            @LayoutRes int textLayoutResId,
 | 
				
			||||||
 | 
					            @IdRes int textIdRes,
 | 
				
			||||||
 | 
					            boolean isRecyclable,
 | 
				
			||||||
 | 
					            boolean cellTextCenterVertical) {
 | 
				
			||||||
 | 
					        this.tableLayoutResId = tableLayoutResId;
 | 
				
			||||||
 | 
					        this.tableIdRes = tableIdRes;
 | 
				
			||||||
 | 
					        this.textLayoutResId = textLayoutResId;
 | 
				
			||||||
 | 
					        this.textIdRes = textIdRes;
 | 
				
			||||||
 | 
					        this.isRecyclable = isRecyclable;
 | 
				
			||||||
 | 
					        this.cellTextCenterVertical = cellTextCenterVertical;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @NonNull
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public Holder createHolder(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) {
 | 
				
			||||||
 | 
					        return new Holder(
 | 
				
			||||||
 | 
					                isRecyclable,
 | 
				
			||||||
 | 
					                tableIdRes,
 | 
				
			||||||
 | 
					                inflater.inflate(tableLayoutResId, parent, false));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public void bindHolder(@NonNull Markwon markwon, @NonNull Holder holder, @NonNull TableBlock node) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Table table = map.get(node);
 | 
				
			||||||
 | 
					        if (table == null) {
 | 
				
			||||||
 | 
					            table = Table.parse(markwon, node);
 | 
				
			||||||
 | 
					            map.put(node, table);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // check if this exact TableBlock was already applied
 | 
				
			||||||
 | 
					        // set tag of tableLayoutResId as it's 100% to be present (we still allow 0 as
 | 
				
			||||||
 | 
					        // tableIdRes if tableLayoutResId has TableLayout as root view)
 | 
				
			||||||
 | 
					        final TableLayout layout = holder.tableLayout;
 | 
				
			||||||
 | 
					        if (table == null
 | 
				
			||||||
 | 
					                || table == layout.getTag(tableLayoutResId)) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // set this flag to indicate what table instance we current display
 | 
				
			||||||
 | 
					        layout.setTag(tableLayoutResId, table);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        final TableEntryPlugin plugin = markwon.getPlugin(TableEntryPlugin.class);
 | 
				
			||||||
 | 
					        if (plugin == null) {
 | 
				
			||||||
 | 
					            throw new IllegalStateException("No TableEntryPlugin is found. Make sure that it " +
 | 
				
			||||||
 | 
					                    "is _used_ whilst configuring Markwon instance");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // we must remove unwanted ones (rows and columns)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        final TableEntryTheme theme = plugin.theme();
 | 
				
			||||||
 | 
					        final int borderWidth;
 | 
				
			||||||
 | 
					        final int borderColor;
 | 
				
			||||||
 | 
					        final int cellPadding;
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            final TextView textView = ensureTextView(layout, 0, 0);
 | 
				
			||||||
 | 
					            borderWidth = theme.tableBorderWidth(textView.getPaint());
 | 
				
			||||||
 | 
					            borderColor = theme.tableBorderColor(textView.getPaint());
 | 
				
			||||||
 | 
					            cellPadding = theme.tableCellPadding();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ensureTableBorderBackground(layout, borderWidth, borderColor);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        //noinspection SuspiciousNameCombination
 | 
				
			||||||
 | 
					//        layout.setPadding(borderWidth, borderWidth, borderWidth, borderWidth);
 | 
				
			||||||
 | 
					//        layout.setClipToPadding(borderWidth == 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        final List<Table.Row> rows = table.rows();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        final int rowsSize = rows.size();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // all rows should have equal number of columns
 | 
				
			||||||
 | 
					        final int columnsSize = rowsSize > 0
 | 
				
			||||||
 | 
					                ? rows.get(0).columns().size()
 | 
				
			||||||
 | 
					                : 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Table.Row row;
 | 
				
			||||||
 | 
					        Table.Column column;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        TableRow tableRow;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (int y = 0; y < rowsSize; y++) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            row = rows.get(y);
 | 
				
			||||||
 | 
					            tableRow = ensureRow(layout, y);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            for (int x = 0; x < columnsSize; x++) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                column = row.columns().get(x);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                final TextView textView = ensureTextView(layout, y, x);
 | 
				
			||||||
 | 
					                textView.setGravity(textGravity(column.alignment(), cellTextCenterVertical));
 | 
				
			||||||
 | 
					                textView.getPaint().setFakeBoldText(row.header());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // apply padding only if not specified in theme (otherwise just use the value from layout)
 | 
				
			||||||
 | 
					                if (cellPadding > 0) {
 | 
				
			||||||
 | 
					                    textView.setPadding(cellPadding, cellPadding, cellPadding, cellPadding);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                ensureTableBorderBackground(textView, borderWidth, borderColor);
 | 
				
			||||||
 | 
					                markwon.setParsedMarkdown(textView, column.content());
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // row appearance
 | 
				
			||||||
 | 
					            if (row.header()) {
 | 
				
			||||||
 | 
					                tableRow.setBackgroundColor(theme.tableHeaderRowBackgroundColor());
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                // as we currently have no support for tables without head
 | 
				
			||||||
 | 
					                // we shift even/odd calculation a bit (head should not be included in even/odd calculation)
 | 
				
			||||||
 | 
					                final boolean isEven = (y % 2) == 1;
 | 
				
			||||||
 | 
					                if (isEven) {
 | 
				
			||||||
 | 
					                    tableRow.setBackgroundColor(theme.tableEvenRowBackgroundColor());
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    // just take first
 | 
				
			||||||
 | 
					                    final TextView textView = ensureTextView(layout, y, 0);
 | 
				
			||||||
 | 
					                    tableRow.setBackgroundColor(
 | 
				
			||||||
 | 
					                            theme.tableOddRowBackgroundColor(textView.getPaint()));
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // clean up here of un-used rows and columns
 | 
				
			||||||
 | 
					        removeUnused(layout, rowsSize, columnsSize);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @NonNull
 | 
				
			||||||
 | 
					    private TableRow ensureRow(@NonNull TableLayout layout, int row) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        final int count = layout.getChildCount();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // fill the requested views until we have added the `row` one
 | 
				
			||||||
 | 
					        if (row >= count) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            final Context context = layout.getContext();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            int diff = row - count + 1;
 | 
				
			||||||
 | 
					            while (diff > 0) {
 | 
				
			||||||
 | 
					                layout.addView(new TableRow(context));
 | 
				
			||||||
 | 
					                diff -= 1;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // return requested child (here it always should be the last one)
 | 
				
			||||||
 | 
					        return (TableRow) layout.getChildAt(row);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @NonNull
 | 
				
			||||||
 | 
					    private TextView ensureTextView(@NonNull TableLayout layout, int row, int column) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        final TableRow tableRow = ensureRow(layout, row);
 | 
				
			||||||
 | 
					        final int count = tableRow.getChildCount();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (column >= count) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            final LayoutInflater inflater = ensureInflater(layout.getContext());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            boolean textViewChecked = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            View view;
 | 
				
			||||||
 | 
					            TextView textView;
 | 
				
			||||||
 | 
					            ViewGroup.LayoutParams layoutParams;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            int diff = column - count + 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            while (diff > 0) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                view = inflater.inflate(textLayoutResId, tableRow, false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // we should have `match_parent` as height (important for borders and text-vertical-align)
 | 
				
			||||||
 | 
					                layoutParams = view.getLayoutParams();
 | 
				
			||||||
 | 
					                if (layoutParams.height != ViewGroup.LayoutParams.MATCH_PARENT) {
 | 
				
			||||||
 | 
					                    layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // it will be enough to check only once
 | 
				
			||||||
 | 
					                if (!textViewChecked) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (textIdRes == 0) {
 | 
				
			||||||
 | 
					                        if (!(view instanceof TextView)) {
 | 
				
			||||||
 | 
					                            final String name = layout.getContext().getResources().getResourceName(textLayoutResId);
 | 
				
			||||||
 | 
					                            throw new IllegalStateException(String.format("textLayoutResId(R.layout.%s) " +
 | 
				
			||||||
 | 
					                                    "has other than TextView root view. Specify TextView ID explicitly", name));
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        textView = (TextView) view;
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        textView = view.findViewById(textIdRes);
 | 
				
			||||||
 | 
					                        if (textView == null) {
 | 
				
			||||||
 | 
					                            final Resources r = layout.getContext().getResources();
 | 
				
			||||||
 | 
					                            final String layoutName = r.getResourceName(textLayoutResId);
 | 
				
			||||||
 | 
					                            final String idName = r.getResourceName(textIdRes);
 | 
				
			||||||
 | 
					                            throw new NullPointerException(String.format("textLayoutResId(R.layout.%s) " +
 | 
				
			||||||
 | 
					                                    "has no TextView found by id(R.id.%s): %s", layoutName, idName, view));
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    // mark as checked
 | 
				
			||||||
 | 
					                    textViewChecked = true;
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    if (textIdRes == 0) {
 | 
				
			||||||
 | 
					                        textView = (TextView) view;
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        textView = view.findViewById(textIdRes);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // we should set SpannableFactory during creation (to avoid another setText method)
 | 
				
			||||||
 | 
					                textView.setSpannableFactory(NoCopySpannableFactory.getInstance());
 | 
				
			||||||
 | 
					                tableRow.addView(textView);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                diff -= 1;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // we can skip all the validation here as we have validated our views whilst inflating them
 | 
				
			||||||
 | 
					        final View last = tableRow.getChildAt(column);
 | 
				
			||||||
 | 
					        if (textIdRes == 0) {
 | 
				
			||||||
 | 
					            return (TextView) last;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            return last.findViewById(textIdRes);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private void ensureTableBorderBackground(@NonNull View view, @Px int borderWidth, @ColorInt int borderColor) {
 | 
				
			||||||
 | 
					        if (borderWidth == 0) {
 | 
				
			||||||
 | 
					            view.setBackground(null);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            final Drawable drawable = view.getBackground();
 | 
				
			||||||
 | 
					            if (!(drawable instanceof TableBorderDrawable)) {
 | 
				
			||||||
 | 
					                final TableBorderDrawable borderDrawable = new TableBorderDrawable();
 | 
				
			||||||
 | 
					                borderDrawable.update(borderWidth, borderColor);
 | 
				
			||||||
 | 
					                view.setBackground(borderDrawable);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                ((TableBorderDrawable) drawable).update(borderWidth, borderColor);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @NonNull
 | 
				
			||||||
 | 
					    private LayoutInflater ensureInflater(@NonNull Context context) {
 | 
				
			||||||
 | 
					        if (inflater == null) {
 | 
				
			||||||
 | 
					            inflater = LayoutInflater.from(context);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return inflater;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @SuppressWarnings("WeakerAccess")
 | 
				
			||||||
 | 
					    @VisibleForTesting
 | 
				
			||||||
 | 
					    static void removeUnused(@NonNull TableLayout layout, int usedRows, int usedColumns) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // clean up rows
 | 
				
			||||||
 | 
					        final int rowsCount = layout.getChildCount();
 | 
				
			||||||
 | 
					        if (rowsCount > usedRows) {
 | 
				
			||||||
 | 
					            layout.removeViews(usedRows, (rowsCount - usedRows));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // validate columns
 | 
				
			||||||
 | 
					        // here we can use usedRows as children count
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        TableRow tableRow;
 | 
				
			||||||
 | 
					        int columnCount;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (int i = 0; i < usedRows; i++) {
 | 
				
			||||||
 | 
					            tableRow = (TableRow) layout.getChildAt(i);
 | 
				
			||||||
 | 
					            columnCount = tableRow.getChildCount();
 | 
				
			||||||
 | 
					            if (columnCount > usedColumns) {
 | 
				
			||||||
 | 
					                tableRow.removeViews(usedColumns, (columnCount - usedColumns));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public void clear() {
 | 
				
			||||||
 | 
					        map.clear();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static class Holder extends MarkwonAdapter.Holder {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        final TableLayout tableLayout;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public Holder(boolean isRecyclable, @IdRes int tableLayoutIdRes, @NonNull View itemView) {
 | 
				
			||||||
 | 
					            super(itemView);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // we must call this method only once (it's somehow _paired_ inside, so
 | 
				
			||||||
 | 
					            // any call in `onCreateViewHolder` or `onBindViewHolder` will log an error
 | 
				
			||||||
 | 
					            // `isRecyclable decremented below 0` which make little sense here)
 | 
				
			||||||
 | 
					            setIsRecyclable(isRecyclable);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            final TableLayout tableLayout;
 | 
				
			||||||
 | 
					            if (tableLayoutIdRes == 0) {
 | 
				
			||||||
 | 
					                // try to cast directly
 | 
				
			||||||
 | 
					                if (!(itemView instanceof TableLayout)) {
 | 
				
			||||||
 | 
					                    throw new IllegalStateException("Root view is not TableLayout. Please provide " +
 | 
				
			||||||
 | 
					                            "TableLayout ID explicitly");
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                tableLayout = (TableLayout) itemView;
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                tableLayout = requireView(tableLayoutIdRes);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            this.tableLayout = tableLayout;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // we will use gravity instead of textAlignment because min sdk is 16 (textAlignment starts at 17)
 | 
				
			||||||
 | 
					    @SuppressWarnings("WeakerAccess")
 | 
				
			||||||
 | 
					    @SuppressLint("RtlHardcoded")
 | 
				
			||||||
 | 
					    @VisibleForTesting
 | 
				
			||||||
 | 
					    static int textGravity(@NonNull Table.Alignment alignment, boolean cellTextCenterVertical) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        final int gravity;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        switch (alignment) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            case LEFT:
 | 
				
			||||||
 | 
					                gravity = Gravity.LEFT;
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            case CENTER:
 | 
				
			||||||
 | 
					                gravity = Gravity.CENTER_HORIZONTAL;
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            case RIGHT:
 | 
				
			||||||
 | 
					                gravity = Gravity.RIGHT;
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            default:
 | 
				
			||||||
 | 
					                throw new IllegalStateException("Unknown table alignment: " + alignment);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (cellTextCenterVertical) {
 | 
				
			||||||
 | 
					            return gravity | Gravity.CENTER_VERTICAL;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // do not center vertically
 | 
				
			||||||
 | 
					        return gravity;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    static class BuilderImpl implements Builder {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private int tableLayoutResId;
 | 
				
			||||||
 | 
					        private int tableIdRes;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private int textLayoutResId;
 | 
				
			||||||
 | 
					        private int textIdRes;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private boolean cellTextCenterVertical = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private boolean isRecyclable = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        @NonNull
 | 
				
			||||||
 | 
					        @Override
 | 
				
			||||||
 | 
					        public Builder tableLayout(int tableLayoutResId, int tableIdRes) {
 | 
				
			||||||
 | 
					            this.tableLayoutResId = tableLayoutResId;
 | 
				
			||||||
 | 
					            this.tableIdRes = tableIdRes;
 | 
				
			||||||
 | 
					            return this;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        @NonNull
 | 
				
			||||||
 | 
					        @Override
 | 
				
			||||||
 | 
					        public Builder tableLayoutIsRoot(int tableLayoutResId) {
 | 
				
			||||||
 | 
					            this.tableLayoutResId = tableLayoutResId;
 | 
				
			||||||
 | 
					            this.tableIdRes = 0;
 | 
				
			||||||
 | 
					            return this;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        @NonNull
 | 
				
			||||||
 | 
					        @Override
 | 
				
			||||||
 | 
					        public Builder textLayout(int textLayoutResId, int textIdRes) {
 | 
				
			||||||
 | 
					            this.textLayoutResId = textLayoutResId;
 | 
				
			||||||
 | 
					            this.textIdRes = textIdRes;
 | 
				
			||||||
 | 
					            return this;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        @NonNull
 | 
				
			||||||
 | 
					        @Override
 | 
				
			||||||
 | 
					        public Builder textLayoutIsRoot(int textLayoutResId) {
 | 
				
			||||||
 | 
					            this.textLayoutResId = textLayoutResId;
 | 
				
			||||||
 | 
					            this.textIdRes = 0;
 | 
				
			||||||
 | 
					            return this;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        @NonNull
 | 
				
			||||||
 | 
					        @Override
 | 
				
			||||||
 | 
					        public Builder cellTextCenterVertical(boolean cellTextCenterVertical) {
 | 
				
			||||||
 | 
					            this.cellTextCenterVertical = cellTextCenterVertical;
 | 
				
			||||||
 | 
					            return this;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        @NonNull
 | 
				
			||||||
 | 
					        @Override
 | 
				
			||||||
 | 
					        public Builder isRecyclable(boolean isRecyclable) {
 | 
				
			||||||
 | 
					            this.isRecyclable = isRecyclable;
 | 
				
			||||||
 | 
					            return this;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        @NonNull
 | 
				
			||||||
 | 
					        @Override
 | 
				
			||||||
 | 
					        public TableEntry build() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (tableLayoutResId == 0) {
 | 
				
			||||||
 | 
					                throw new IllegalStateException("`tableLayoutResId` argument is required");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (textLayoutResId == 0) {
 | 
				
			||||||
 | 
					                throw new IllegalStateException("`textLayoutResId` argument is required");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return new TableEntry(
 | 
				
			||||||
 | 
					                    tableLayoutResId, tableIdRes,
 | 
				
			||||||
 | 
					                    textLayoutResId, textIdRes,
 | 
				
			||||||
 | 
					                    isRecyclable, cellTextCenterVertical
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,65 @@
 | 
				
			|||||||
 | 
					package ru.noties.markwon.recycler.table;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import android.content.Context;
 | 
				
			||||||
 | 
					import android.support.annotation.NonNull;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.commonmark.ext.gfm.tables.TablesExtension;
 | 
				
			||||||
 | 
					import org.commonmark.parser.Parser;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.Collections;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import ru.noties.markwon.AbstractMarkwonPlugin;
 | 
				
			||||||
 | 
					import ru.noties.markwon.ext.tables.TablePlugin;
 | 
				
			||||||
 | 
					import ru.noties.markwon.ext.tables.TableTheme;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * This plugin must be used instead of {@link ru.noties.markwon.ext.tables.TablePlugin} when a markdown
 | 
				
			||||||
 | 
					 * table is intended to be used in a RecyclerView via {@link TableEntry}. This is required
 | 
				
			||||||
 | 
					 * because TablePlugin additionally processes markdown tables to be displayed in <em>limited</em>
 | 
				
			||||||
 | 
					 * context of a TextView. If TablePlugin will be used, {@link TableEntry} will display table,
 | 
				
			||||||
 | 
					 * but no content will be present
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @since 3.0.0
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class TableEntryPlugin extends AbstractMarkwonPlugin {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @NonNull
 | 
				
			||||||
 | 
					    public static TableEntryPlugin create(@NonNull Context context) {
 | 
				
			||||||
 | 
					        final TableTheme tableTheme = TableTheme.create(context);
 | 
				
			||||||
 | 
					        return create(tableTheme);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @NonNull
 | 
				
			||||||
 | 
					    public static TableEntryPlugin create(@NonNull TableTheme tableTheme) {
 | 
				
			||||||
 | 
					        return new TableEntryPlugin(TableEntryTheme.create(tableTheme));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @NonNull
 | 
				
			||||||
 | 
					    public static TableEntryPlugin create(@NonNull TablePlugin.ThemeConfigure themeConfigure) {
 | 
				
			||||||
 | 
					        final TableTheme.Builder builder = new TableTheme.Builder();
 | 
				
			||||||
 | 
					        themeConfigure.configureTheme(builder);
 | 
				
			||||||
 | 
					        return new TableEntryPlugin(new TableEntryTheme(builder));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @NonNull
 | 
				
			||||||
 | 
					    public static TableEntryPlugin create(@NonNull TablePlugin plugin) {
 | 
				
			||||||
 | 
					        return create(plugin.theme());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private final TableEntryTheme theme;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @SuppressWarnings("WeakerAccess")
 | 
				
			||||||
 | 
					    TableEntryPlugin(@NonNull TableEntryTheme tableTheme) {
 | 
				
			||||||
 | 
					        this.theme = tableTheme;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @NonNull
 | 
				
			||||||
 | 
					    public TableEntryTheme theme() {
 | 
				
			||||||
 | 
					        return theme;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public void configureParser(@NonNull Parser.Builder builder) {
 | 
				
			||||||
 | 
					        builder.extensions(Collections.singleton(TablesExtension.create()));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,67 @@
 | 
				
			|||||||
 | 
					package ru.noties.markwon.recycler.table;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import android.graphics.Paint;
 | 
				
			||||||
 | 
					import android.support.annotation.ColorInt;
 | 
				
			||||||
 | 
					import android.support.annotation.NonNull;
 | 
				
			||||||
 | 
					import android.support.annotation.Px;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import ru.noties.markwon.ext.tables.TableTheme;
 | 
				
			||||||
 | 
					import ru.noties.markwon.utils.ColorUtils;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Mimics TableTheme to allow uniform table customization
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @see #create(TableTheme)
 | 
				
			||||||
 | 
					 * @see TableEntryPlugin
 | 
				
			||||||
 | 
					 * @since 3.0.0
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@SuppressWarnings("WeakerAccess")
 | 
				
			||||||
 | 
					public class TableEntryTheme extends TableTheme {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @NonNull
 | 
				
			||||||
 | 
					    public static TableEntryTheme create(@NonNull TableTheme tableTheme) {
 | 
				
			||||||
 | 
					        return new TableEntryTheme(tableTheme.asBuilder());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected TableEntryTheme(@NonNull Builder builder) {
 | 
				
			||||||
 | 
					        super(builder);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Px
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public int tableCellPadding() {
 | 
				
			||||||
 | 
					        return tableCellPadding;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @ColorInt
 | 
				
			||||||
 | 
					    public int tableBorderColor(@NonNull Paint paint) {
 | 
				
			||||||
 | 
					        return tableBorderColor == 0
 | 
				
			||||||
 | 
					                ? ColorUtils.applyAlpha(paint.getColor(), TABLE_BORDER_DEF_ALPHA)
 | 
				
			||||||
 | 
					                : tableBorderColor;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Px
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public int tableBorderWidth(@NonNull Paint paint) {
 | 
				
			||||||
 | 
					        return tableBorderWidth < 0
 | 
				
			||||||
 | 
					                ? (int) (paint.getStrokeWidth() + .5F)
 | 
				
			||||||
 | 
					                : tableBorderWidth;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @ColorInt
 | 
				
			||||||
 | 
					    public int tableOddRowBackgroundColor(@NonNull Paint paint) {
 | 
				
			||||||
 | 
					        return tableOddRowBackgroundColor == 0
 | 
				
			||||||
 | 
					                ? ColorUtils.applyAlpha(paint.getColor(), TABLE_ODD_ROW_DEF_ALPHA)
 | 
				
			||||||
 | 
					                : tableOddRowBackgroundColor;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @ColorInt
 | 
				
			||||||
 | 
					    public int tableEvenRowBackgroundColor() {
 | 
				
			||||||
 | 
					        return tableEvenRowBackgroundColor;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @ColorInt
 | 
				
			||||||
 | 
					    public int tableHeaderRowBackgroundColor() {
 | 
				
			||||||
 | 
					        return tableHeaderRowBackgroundColor;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,103 @@
 | 
				
			|||||||
 | 
					package ru.noties.markwon.recycler.table;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import android.content.res.Resources;
 | 
				
			||||||
 | 
					import android.util.Pair;
 | 
				
			||||||
 | 
					import android.view.Gravity;
 | 
				
			||||||
 | 
					import android.view.View;
 | 
				
			||||||
 | 
					import android.widget.TableLayout;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.junit.Test;
 | 
				
			||||||
 | 
					import org.junit.runner.RunWith;
 | 
				
			||||||
 | 
					import org.robolectric.RobolectricTestRunner;
 | 
				
			||||||
 | 
					import org.robolectric.annotation.Config;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.Arrays;
 | 
				
			||||||
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import ru.noties.markwon.ext.tables.Table;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import static org.junit.Assert.assertEquals;
 | 
				
			||||||
 | 
					import static org.junit.Assert.assertTrue;
 | 
				
			||||||
 | 
					import static org.junit.Assert.fail;
 | 
				
			||||||
 | 
					import static org.mockito.Mockito.mock;
 | 
				
			||||||
 | 
					import static org.mockito.Mockito.when;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@RunWith(RobolectricTestRunner.class)
 | 
				
			||||||
 | 
					@Config(manifest = Config.NONE)
 | 
				
			||||||
 | 
					public class TableEntryTest {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Test
 | 
				
			||||||
 | 
					    public void gravity() {
 | 
				
			||||||
 | 
					        // test textGravity is calculated correctly
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        final List<Pair<Table.Alignment, Integer>> noVerticalAlign = Arrays.asList(
 | 
				
			||||||
 | 
					                new Pair<Table.Alignment, Integer>(Table.Alignment.LEFT, Gravity.LEFT),
 | 
				
			||||||
 | 
					                new Pair<Table.Alignment, Integer>(Table.Alignment.CENTER, Gravity.CENTER_HORIZONTAL),
 | 
				
			||||||
 | 
					                new Pair<Table.Alignment, Integer>(Table.Alignment.RIGHT, Gravity.RIGHT)
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        final List<Pair<Table.Alignment, Integer>> withVerticalAlign = Arrays.asList(
 | 
				
			||||||
 | 
					                new Pair<Table.Alignment, Integer>(Table.Alignment.LEFT, Gravity.LEFT | Gravity.CENTER_VERTICAL),
 | 
				
			||||||
 | 
					                new Pair<Table.Alignment, Integer>(Table.Alignment.CENTER, Gravity.CENTER_HORIZONTAL | Gravity.CENTER_VERTICAL),
 | 
				
			||||||
 | 
					                new Pair<Table.Alignment, Integer>(Table.Alignment.RIGHT, Gravity.RIGHT | Gravity.CENTER_VERTICAL)
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (Pair<Table.Alignment, Integer> pair : noVerticalAlign) {
 | 
				
			||||||
 | 
					            assertEquals(pair.first.name(), pair.second.intValue(), TableEntry.textGravity(pair.first, false));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (Pair<Table.Alignment, Integer> pair : withVerticalAlign) {
 | 
				
			||||||
 | 
					            assertEquals(pair.first.name(), pair.second.intValue(), TableEntry.textGravity(pair.first, true));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Test
 | 
				
			||||||
 | 
					    public void holder_no_table_layout_id() {
 | 
				
			||||||
 | 
					        // validate that holder correctly obtains TableLayout instance casting root view
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // root is not TableLayout
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            new TableEntry.Holder(false, 0, mock(View.class));
 | 
				
			||||||
 | 
					            fail();
 | 
				
			||||||
 | 
					        } catch (IllegalStateException e) {
 | 
				
			||||||
 | 
					            assertTrue(e.getMessage().contains("Root view is not TableLayout"));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // root is TableLayout
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            final TableLayout tableLayout = mock(TableLayout.class);
 | 
				
			||||||
 | 
					            final TableEntry.Holder h = new TableEntry.Holder(false, 0, tableLayout);
 | 
				
			||||||
 | 
					            assertEquals(tableLayout, h.tableLayout);
 | 
				
			||||||
 | 
					        } catch (IllegalStateException e) {
 | 
				
			||||||
 | 
					            fail(e.getMessage());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Test
 | 
				
			||||||
 | 
					    public void holder_with_table_layout_id() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // not found
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            final View view = mock(View.class);
 | 
				
			||||||
 | 
					            // resources are used to obtain id name for proper error message
 | 
				
			||||||
 | 
					            when(view.getResources()).thenReturn(mock(Resources.class));
 | 
				
			||||||
 | 
					            new TableEntry.Holder(false, 1, view);
 | 
				
			||||||
 | 
					            fail();
 | 
				
			||||||
 | 
					        } catch (NullPointerException e) {
 | 
				
			||||||
 | 
					            assertTrue(e.getMessage(), e.getMessage().contains("No view with id"));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // found
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            final TableLayout tableLayout = mock(TableLayout.class);
 | 
				
			||||||
 | 
					            final View view = mock(View.class);
 | 
				
			||||||
 | 
					            when(view.findViewById(3)).thenReturn(tableLayout);
 | 
				
			||||||
 | 
					            final TableEntry.Holder holder = new TableEntry.Holder(false, 3, view);
 | 
				
			||||||
 | 
					            assertEquals(tableLayout, holder.tableLayout);
 | 
				
			||||||
 | 
					        } catch (NullPointerException e) {
 | 
				
			||||||
 | 
					            e.printStackTrace();
 | 
				
			||||||
 | 
					            fail(e.getMessage());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -18,17 +18,13 @@ import ru.noties.markwon.MarkwonReducer;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Adapter to display markdown in a RecyclerView. It is done by extracting root blocks from a
 | 
					 * Adapter to display markdown in a RecyclerView. It is done by extracting root blocks from a
 | 
				
			||||||
 * parsed markdown document and rendering each block in a standalone RecyclerView entry. Provides
 | 
					 * parsed markdown document (via {@link MarkwonReducer} and rendering each block in a standalone RecyclerView entry. Provides
 | 
				
			||||||
 * ability to customize rendering of blocks. For example display certain blocks in a horizontal
 | 
					 * ability to customize rendering of blocks. For example display certain blocks in a horizontal
 | 
				
			||||||
 * scrolling container or display tables in a specific widget designed for it ({@link Builder#include(Class, Entry)}).
 | 
					 * scrolling container or display tables in a specific widget designed for it ({@link Builder#include(Class, Entry)}).
 | 
				
			||||||
 * <p>
 | 
					 | 
				
			||||||
 * By default each node will be rendered in a TextView provided by this artifact. It has no styling
 | 
					 | 
				
			||||||
 * information and thus must be replaced with your own layout ({@link Builder#defaultEntry(int)} or
 | 
					 | 
				
			||||||
 * {@link Builder#defaultEntry(Entry)}).
 | 
					 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @see #builder()
 | 
					 * @see #builder(int, int)
 | 
				
			||||||
 * @see #create()
 | 
					 * @see #builder(Entry)
 | 
				
			||||||
 * @see #create(int)
 | 
					 * @see #create(int, int)
 | 
				
			||||||
 * @see #create(Entry)
 | 
					 * @see #create(Entry)
 | 
				
			||||||
 * @see #setMarkdown(Markwon, String)
 | 
					 * @see #setMarkdown(Markwon, String)
 | 
				
			||||||
 * @see #setParsedMarkdown(Markwon, Node)
 | 
					 * @see #setParsedMarkdown(Markwon, Node)
 | 
				
			||||||
@ -37,14 +33,34 @@ import ru.noties.markwon.MarkwonReducer;
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
public abstract class MarkwonAdapter extends RecyclerView.Adapter<MarkwonAdapter.Holder> {
 | 
					public abstract class MarkwonAdapter extends RecyclerView.Adapter<MarkwonAdapter.Holder> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @NonNull
 | 
				
			||||||
 | 
					    public static Builder builderTextViewIsRoot(@LayoutRes int defaultEntryLayoutResId) {
 | 
				
			||||||
 | 
					        return builder(SimpleEntry.createTextViewIsRoot(defaultEntryLayoutResId));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Factory method to obtain {@link Builder} instance.
 | 
					     * Factory method to obtain {@link Builder} instance.
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
     * @see Builder
 | 
					     * @see Builder
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    @NonNull
 | 
					    @NonNull
 | 
				
			||||||
    public static Builder builder() {
 | 
					    public static Builder builder(
 | 
				
			||||||
        return new MarkwonAdapterImpl.BuilderImpl();
 | 
					            @LayoutRes int defaultEntryLayoutResId,
 | 
				
			||||||
 | 
					            @IdRes int defaultEntryTextViewResId
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        return builder(SimpleEntry.create(defaultEntryLayoutResId, defaultEntryTextViewResId));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @NonNull
 | 
				
			||||||
 | 
					    public static Builder builder(@NonNull Entry<? extends Node, ? extends Holder> defaultEntry) {
 | 
				
			||||||
 | 
					        //noinspection unchecked
 | 
				
			||||||
 | 
					        return new MarkwonAdapterImpl.BuilderImpl((Entry<Node, Holder>) defaultEntry);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @NonNull
 | 
				
			||||||
 | 
					    public static MarkwonAdapter createTextViewIsRoot(@LayoutRes int defaultEntryLayoutResId) {
 | 
				
			||||||
 | 
					        return builderTextViewIsRoot(defaultEntryLayoutResId)
 | 
				
			||||||
 | 
					                .build();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@ -52,12 +68,16 @@ public abstract class MarkwonAdapter extends RecyclerView.Adapter<MarkwonAdapter
 | 
				
			|||||||
     * adapter will use default layout for all blocks. Default layout has no styling and should
 | 
					     * adapter will use default layout for all blocks. Default layout has no styling and should
 | 
				
			||||||
     * be specified explicitly.
 | 
					     * be specified explicitly.
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
     * @see #create(int)
 | 
					 | 
				
			||||||
     * @see #create(Entry)
 | 
					     * @see #create(Entry)
 | 
				
			||||||
 | 
					     * @see #builder(int, int)
 | 
				
			||||||
 | 
					     * @see SimpleEntry
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    @NonNull
 | 
					    @NonNull
 | 
				
			||||||
    public static MarkwonAdapter create() {
 | 
					    public static MarkwonAdapter create(
 | 
				
			||||||
        return new MarkwonAdapterImpl.BuilderImpl().build();
 | 
					            @LayoutRes int defaultEntryLayoutResId,
 | 
				
			||||||
 | 
					            @IdRes int defaultEntryTextViewResId
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        return builder(defaultEntryLayoutResId, defaultEntryTextViewResId).build();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@ -65,35 +85,19 @@ public abstract class MarkwonAdapter extends RecyclerView.Adapter<MarkwonAdapter
 | 
				
			|||||||
     * nodes.
 | 
					     * nodes.
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
     * @param defaultEntry {@link Entry} to be used for node rendering
 | 
					     * @param defaultEntry {@link Entry} to be used for node rendering
 | 
				
			||||||
     * @see SimpleEntry
 | 
					     * @see #builder(Entry)
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    @NonNull
 | 
					    @NonNull
 | 
				
			||||||
    public static MarkwonAdapter create(@NonNull Entry<? extends Node, ? extends Holder> defaultEntry) {
 | 
					    public static MarkwonAdapter create(@NonNull Entry<? extends Node, ? extends Holder> defaultEntry) {
 | 
				
			||||||
        return new MarkwonAdapterImpl.BuilderImpl().defaultEntry(defaultEntry).build();
 | 
					        return builder(defaultEntry).build();
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Factory method to create a {@link MarkwonAdapter} that will use supplied layoutResId view
 | 
					 | 
				
			||||||
     * to display all nodes.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * <strong>Please note</strong> that supplied layout must have a TextView inside
 | 
					 | 
				
			||||||
     * with {@code android:id="@+id/text"}
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param layoutResId layout to be used to display all nodes
 | 
					 | 
				
			||||||
     * @see SimpleEntry
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @NonNull
 | 
					 | 
				
			||||||
    public static MarkwonAdapter create(@LayoutRes int layoutResId) {
 | 
					 | 
				
			||||||
        return new MarkwonAdapterImpl.BuilderImpl().defaultEntry(layoutResId).build();
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Builder to create an instance of {@link MarkwonAdapter}
 | 
					     * Builder to create an instance of {@link MarkwonAdapter}
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
     * @see #include(Class, Entry)
 | 
					     * @see #include(Class, Entry)
 | 
				
			||||||
     * @see #defaultEntry(int)
 | 
					 | 
				
			||||||
     * @see #defaultEntry(Entry)
 | 
					 | 
				
			||||||
     * @see #reducer(MarkwonReducer)
 | 
					     * @see #reducer(MarkwonReducer)
 | 
				
			||||||
 | 
					     * @see #build()
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public interface Builder {
 | 
					    public interface Builder {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -112,29 +116,6 @@ public abstract class MarkwonAdapter extends RecyclerView.Adapter<MarkwonAdapter
 | 
				
			|||||||
                @NonNull Class<N> node,
 | 
					                @NonNull Class<N> node,
 | 
				
			||||||
                @NonNull Entry<? super N, ? extends Holder> entry);
 | 
					                @NonNull Entry<? super N, ? extends Holder> entry);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /**
 | 
					 | 
				
			||||||
         * Specify which {@link Entry} to use for all non-explicitly registered nodes
 | 
					 | 
				
			||||||
         *
 | 
					 | 
				
			||||||
         * @param defaultEntry {@link Entry}
 | 
					 | 
				
			||||||
         * @return self
 | 
					 | 
				
			||||||
         * @see SimpleEntry
 | 
					 | 
				
			||||||
         */
 | 
					 | 
				
			||||||
        @NonNull
 | 
					 | 
				
			||||||
        Builder defaultEntry(@NonNull Entry<? extends Node, ? extends Holder> defaultEntry);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /**
 | 
					 | 
				
			||||||
         * Specify which layout {@link SimpleEntry} will use to render all non-explicitly
 | 
					 | 
				
			||||||
         * registered nodes.
 | 
					 | 
				
			||||||
         *
 | 
					 | 
				
			||||||
         * <strong>Please note</strong> that supplied layout must have a TextView inside
 | 
					 | 
				
			||||||
         * with {@code android:id="@+id/text"}
 | 
					 | 
				
			||||||
         *
 | 
					 | 
				
			||||||
         * @return self
 | 
					 | 
				
			||||||
         * @see SimpleEntry
 | 
					 | 
				
			||||||
         */
 | 
					 | 
				
			||||||
        @NonNull
 | 
					 | 
				
			||||||
        Builder defaultEntry(@LayoutRes int layoutResId);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /**
 | 
					        /**
 | 
				
			||||||
         * Specify how root Node will be <em>reduced</em> to a list of nodes. There is a default
 | 
					         * Specify how root Node will be <em>reduced</em> to a list of nodes. There is a default
 | 
				
			||||||
         * {@link MarkwonReducer} that will be used if not provided explicitly (there is no need to
 | 
					         * {@link MarkwonReducer} that will be used if not provided explicitly (there is no need to
 | 
				
			||||||
@ -157,17 +138,27 @@ public abstract class MarkwonAdapter extends RecyclerView.Adapter<MarkwonAdapter
 | 
				
			|||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * @see SimpleEntry
 | 
					     * @see SimpleEntry
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public interface Entry<N extends Node, H extends Holder> {
 | 
					    public static abstract class Entry<N extends Node, H extends Holder> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        @NonNull
 | 
					        @NonNull
 | 
				
			||||||
        H createHolder(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent);
 | 
					        public abstract H createHolder(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        void bindHolder(@NonNull Markwon markwon, @NonNull H holder, @NonNull N node);
 | 
					        public abstract void bindHolder(@NonNull Markwon markwon, @NonNull H holder, @NonNull N node);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        long id(@NonNull N node);
 | 
					        /**
 | 
				
			||||||
 | 
					         * Will be called when new content is available (clear internal cache if any)
 | 
				
			||||||
 | 
					         */
 | 
				
			||||||
 | 
					        public void clear() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // will be called when new content is available (clear internal cache if any)
 | 
					        }
 | 
				
			||||||
        void clear();
 | 
					
 | 
				
			||||||
 | 
					        public long id(@NonNull N node) {
 | 
				
			||||||
 | 
					            return node.hashCode();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public void onViewRecycled(@NonNull H holder) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public abstract void setMarkdown(@NonNull Markwon markwon, @NonNull String markdown);
 | 
					    public abstract void setMarkdown(@NonNull Markwon markwon, @NonNull String markdown);
 | 
				
			||||||
@ -196,7 +187,15 @@ public abstract class MarkwonAdapter extends RecyclerView.Adapter<MarkwonAdapter
 | 
				
			|||||||
        protected <V extends View> V requireView(@IdRes int id) {
 | 
					        protected <V extends View> V requireView(@IdRes int id) {
 | 
				
			||||||
            final V v = itemView.findViewById(id);
 | 
					            final V v = itemView.findViewById(id);
 | 
				
			||||||
            if (v == null) {
 | 
					            if (v == null) {
 | 
				
			||||||
                throw new NullPointerException();
 | 
					                final String name;
 | 
				
			||||||
 | 
					                if (id == 0
 | 
				
			||||||
 | 
					                        || id == View.NO_ID) {
 | 
				
			||||||
 | 
					                    name = String.valueOf(id);
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    name = "R.id." + itemView.getResources().getResourceName(id);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                throw new NullPointerException(String.format("No view with id(R.id.%s) is found " +
 | 
				
			||||||
 | 
					                        "in layout: %s", name, itemView));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            return v;
 | 
					            return v;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -32,6 +32,7 @@ class MarkwonAdapterImpl extends MarkwonAdapter {
 | 
				
			|||||||
        this.entries = entries;
 | 
					        this.entries = entries;
 | 
				
			||||||
        this.defaultEntry = defaultEntry;
 | 
					        this.defaultEntry = defaultEntry;
 | 
				
			||||||
        this.reducer = reducer;
 | 
					        this.reducer = reducer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        setHasStableIds(true);
 | 
					        setHasStableIds(true);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -90,6 +91,14 @@ class MarkwonAdapterImpl extends MarkwonAdapter {
 | 
				
			|||||||
                : 0;
 | 
					                : 0;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public void onViewRecycled(@NonNull Holder holder) {
 | 
				
			||||||
 | 
					        super.onViewRecycled(holder);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        final Entry<Node, Holder> entry = getEntry(holder.getItemViewType());
 | 
				
			||||||
 | 
					        entry.onViewRecycled(holder);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @SuppressWarnings("unused")
 | 
					    @SuppressWarnings("unused")
 | 
				
			||||||
    @NonNull
 | 
					    @NonNull
 | 
				
			||||||
    public List<Node> getItems() {
 | 
					    public List<Node> getItems() {
 | 
				
			||||||
@ -132,9 +141,14 @@ class MarkwonAdapterImpl extends MarkwonAdapter {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        private final SparseArray<Entry<Node, Holder>> entries = new SparseArray<>(3);
 | 
					        private final SparseArray<Entry<Node, Holder>> entries = new SparseArray<>(3);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private Entry<Node, Holder> defaultEntry;
 | 
					        private final Entry<Node, Holder> defaultEntry;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private MarkwonReducer reducer;
 | 
					        private MarkwonReducer reducer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        BuilderImpl(@NonNull Entry<Node, Holder> defaultEntry) {
 | 
				
			||||||
 | 
					            this.defaultEntry = defaultEntry;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        @NonNull
 | 
					        @NonNull
 | 
				
			||||||
        @Override
 | 
					        @Override
 | 
				
			||||||
        public <N extends Node> Builder include(
 | 
					        public <N extends Node> Builder include(
 | 
				
			||||||
@ -145,22 +159,6 @@ class MarkwonAdapterImpl extends MarkwonAdapter {
 | 
				
			|||||||
            return this;
 | 
					            return this;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        @NonNull
 | 
					 | 
				
			||||||
        @Override
 | 
					 | 
				
			||||||
        public Builder defaultEntry(@NonNull Entry<? extends Node, ? extends Holder> defaultEntry) {
 | 
					 | 
				
			||||||
            //noinspection unchecked
 | 
					 | 
				
			||||||
            this.defaultEntry = (Entry<Node, Holder>) defaultEntry;
 | 
					 | 
				
			||||||
            return this;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        @NonNull
 | 
					 | 
				
			||||||
        @Override
 | 
					 | 
				
			||||||
        public Builder defaultEntry(int layoutResId) {
 | 
					 | 
				
			||||||
            //noinspection unchecked
 | 
					 | 
				
			||||||
            this.defaultEntry = (Entry<Node, Holder>) (Entry) new SimpleEntry(layoutResId);
 | 
					 | 
				
			||||||
            return this;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        @NonNull
 | 
					        @NonNull
 | 
				
			||||||
        @Override
 | 
					        @Override
 | 
				
			||||||
        public Builder reducer(@NonNull MarkwonReducer reducer) {
 | 
					        public Builder reducer(@NonNull MarkwonReducer reducer) {
 | 
				
			||||||
@ -172,11 +170,6 @@ class MarkwonAdapterImpl extends MarkwonAdapter {
 | 
				
			|||||||
        @Override
 | 
					        @Override
 | 
				
			||||||
        public MarkwonAdapter build() {
 | 
					        public MarkwonAdapter build() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (defaultEntry == null) {
 | 
					 | 
				
			||||||
                //noinspection unchecked
 | 
					 | 
				
			||||||
                defaultEntry = (Entry<Node, Holder>) (Entry) new SimpleEntry();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (reducer == null) {
 | 
					            if (reducer == null) {
 | 
				
			||||||
                reducer = MarkwonReducer.directChildren();
 | 
					                reducer = MarkwonReducer.directChildren();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
				
			|||||||
@ -1,9 +1,8 @@
 | 
				
			|||||||
package ru.noties.markwon.recycler;
 | 
					package ru.noties.markwon.recycler;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import android.support.annotation.IdRes;
 | 
				
			||||||
import android.support.annotation.LayoutRes;
 | 
					import android.support.annotation.LayoutRes;
 | 
				
			||||||
import android.support.annotation.NonNull;
 | 
					import android.support.annotation.NonNull;
 | 
				
			||||||
import android.text.Spannable;
 | 
					 | 
				
			||||||
import android.text.SpannableString;
 | 
					 | 
				
			||||||
import android.text.Spanned;
 | 
					import android.text.Spanned;
 | 
				
			||||||
import android.view.LayoutInflater;
 | 
					import android.view.LayoutInflater;
 | 
				
			||||||
import android.view.View;
 | 
					import android.view.View;
 | 
				
			||||||
@ -16,32 +15,43 @@ import java.util.HashMap;
 | 
				
			|||||||
import java.util.Map;
 | 
					import java.util.Map;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import ru.noties.markwon.Markwon;
 | 
					import ru.noties.markwon.Markwon;
 | 
				
			||||||
 | 
					import ru.noties.markwon.utils.NoCopySpannableFactory;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * @since 3.0.0
 | 
					 * @since 3.0.0
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
@SuppressWarnings("WeakerAccess")
 | 
					@SuppressWarnings("WeakerAccess")
 | 
				
			||||||
public class SimpleEntry implements MarkwonAdapter.Entry<Node, SimpleEntry.Holder> {
 | 
					public class SimpleEntry extends MarkwonAdapter.Entry<Node, SimpleEntry.Holder> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static final Spannable.Factory NO_COPY_SPANNABLE_FACTORY = new NoCopySpannableFactory();
 | 
					    /**
 | 
				
			||||||
 | 
					     * Create {@link SimpleEntry} that has TextView as the root view of
 | 
				
			||||||
 | 
					     * specified layout.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    @NonNull
 | 
				
			||||||
 | 
					    public static SimpleEntry createTextViewIsRoot(@LayoutRes int layoutResId) {
 | 
				
			||||||
 | 
					        return new SimpleEntry(layoutResId, 0);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @NonNull
 | 
				
			||||||
 | 
					    public static SimpleEntry create(@LayoutRes int layoutResId, @IdRes int textViewIdRes) {
 | 
				
			||||||
 | 
					        return new SimpleEntry(layoutResId, textViewIdRes);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // small cache for already rendered nodes
 | 
					    // small cache for already rendered nodes
 | 
				
			||||||
    private final Map<Node, Spanned> cache = new HashMap<>();
 | 
					    private final Map<Node, Spanned> cache = new HashMap<>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private final int layoutResId;
 | 
					    private final int layoutResId;
 | 
				
			||||||
 | 
					    private final int textViewIdRes;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public SimpleEntry() {
 | 
					    public SimpleEntry(@LayoutRes int layoutResId, @IdRes int textViewIdRes) {
 | 
				
			||||||
        this(R.layout.markwon_adapter_simple_entry);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public SimpleEntry(@LayoutRes int layoutResId) {
 | 
					 | 
				
			||||||
        this.layoutResId = layoutResId;
 | 
					        this.layoutResId = layoutResId;
 | 
				
			||||||
 | 
					        this.textViewIdRes = textViewIdRes;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @NonNull
 | 
					    @NonNull
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public Holder createHolder(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) {
 | 
					    public Holder createHolder(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) {
 | 
				
			||||||
        return new Holder(inflater.inflate(layoutResId, parent, false));
 | 
					        return new Holder(textViewIdRes, inflater.inflate(layoutResId, parent, false));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
@ -54,11 +64,6 @@ public class SimpleEntry implements MarkwonAdapter.Entry<Node, SimpleEntry.Holde
 | 
				
			|||||||
        markwon.setParsedMarkdown(holder.textView, spanned);
 | 
					        markwon.setParsedMarkdown(holder.textView, spanned);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public long id(@NonNull Node node) {
 | 
					 | 
				
			||||||
        return node.hashCode();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public void clear() {
 | 
					    public void clear() {
 | 
				
			||||||
        cache.clear();
 | 
					        cache.clear();
 | 
				
			||||||
@ -68,21 +73,21 @@ public class SimpleEntry implements MarkwonAdapter.Entry<Node, SimpleEntry.Holde
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        final TextView textView;
 | 
					        final TextView textView;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected Holder(@NonNull View itemView) {
 | 
					        protected Holder(@IdRes int textViewIdRes, @NonNull View itemView) {
 | 
				
			||||||
            super(itemView);
 | 
					            super(itemView);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            this.textView = requireView(R.id.text);
 | 
					            final TextView textView;
 | 
				
			||||||
            this.textView.setSpannableFactory(NO_COPY_SPANNABLE_FACTORY);
 | 
					            if (textViewIdRes == 0) {
 | 
				
			||||||
        }
 | 
					                if (!(itemView instanceof TextView)) {
 | 
				
			||||||
    }
 | 
					                    throw new IllegalStateException("TextView is not root of layout " +
 | 
				
			||||||
 | 
					                            "(specify TextView ID explicitly): " + itemView);
 | 
				
			||||||
    private static class NoCopySpannableFactory extends Spannable.Factory {
 | 
					                }
 | 
				
			||||||
 | 
					                textView = (TextView) itemView;
 | 
				
			||||||
        @Override
 | 
					            } else {
 | 
				
			||||||
        public Spannable newSpannable(CharSequence source) {
 | 
					                textView = requireView(textViewIdRes);
 | 
				
			||||||
            return source instanceof Spannable
 | 
					            }
 | 
				
			||||||
                    ? (Spannable) source
 | 
					            this.textView = textView;
 | 
				
			||||||
                    : new SpannableString(source);
 | 
					            this.textView.setSpannableFactory(NoCopySpannableFactory.getInstance());
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,9 +0,0 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="utf-8"?>
 | 
					 | 
				
			||||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
 | 
					 | 
				
			||||||
    xmlns:tools="http://schemas.android.com/tools"
 | 
					 | 
				
			||||||
    android:id="@+id/text"
 | 
					 | 
				
			||||||
    android:layout_width="match_parent"
 | 
					 | 
				
			||||||
    android:layout_height="wrap_content"
 | 
					 | 
				
			||||||
    android:lineSpacingExtra="2dip"
 | 
					 | 
				
			||||||
    android:textAppearance="?android:attr/textAppearanceMedium"
 | 
					 | 
				
			||||||
    tools:text="# Hello there!" />
 | 
					 | 
				
			||||||
@ -29,14 +29,6 @@ android {
 | 
				
			|||||||
        targetCompatibility JavaVersion.VERSION_1_8
 | 
					        targetCompatibility JavaVersion.VERSION_1_8
 | 
				
			||||||
        sourceCompatibility JavaVersion.VERSION_1_8
 | 
					        sourceCompatibility JavaVersion.VERSION_1_8
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
    sourceSets {
 | 
					 | 
				
			||||||
        main {
 | 
					 | 
				
			||||||
            // let's use different res directory so sample will have _isolated_ resources from others
 | 
					 | 
				
			||||||
            res.srcDirs += [ './src/main/recycler/res' ]
 | 
					 | 
				
			||||||
            assets.srcDirs += ['./src/main/recycler/assets']
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
dependencies {
 | 
					dependencies {
 | 
				
			||||||
@ -51,6 +43,7 @@ dependencies {
 | 
				
			|||||||
    implementation project(':markwon-image-svg')
 | 
					    implementation project(':markwon-image-svg')
 | 
				
			||||||
    implementation project(':markwon-syntax-highlight')
 | 
					    implementation project(':markwon-syntax-highlight')
 | 
				
			||||||
    implementation project(':markwon-recycler')
 | 
					    implementation project(':markwon-recycler')
 | 
				
			||||||
 | 
					    implementation project(':markwon-recycler-table')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    deps.with {
 | 
					    deps.with {
 | 
				
			||||||
        implementation it['support-recycler-view']
 | 
					        implementation it['support-recycler-view']
 | 
				
			||||||
 | 
				
			|||||||
@ -7,11 +7,9 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    <application
 | 
					    <application
 | 
				
			||||||
        android:allowBackup="true"
 | 
					        android:allowBackup="true"
 | 
				
			||||||
        android:icon="@mipmap/ic_launcher"
 | 
					 | 
				
			||||||
        android:label="@string/app_name"
 | 
					        android:label="@string/app_name"
 | 
				
			||||||
        android:roundIcon="@mipmap/ic_launcher"
 | 
					 | 
				
			||||||
        android:theme="@style/AppTheme"
 | 
					        android:theme="@style/AppTheme"
 | 
				
			||||||
        tools:ignore="AllowBackup,GoogleAppIndexingWarning">
 | 
					        tools:ignore="AllowBackup,GoogleAppIndexingWarning,MissingApplicationIcon">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <activity android:name=".MainActivity">
 | 
					        <activity android:name=".MainActivity">
 | 
				
			||||||
            <intent-filter>
 | 
					            <intent-filter>
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										1
									
								
								sample/src/main/assets/README.md
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					../../../../README.md
 | 
				
			||||||
@ -11,15 +11,12 @@ import android.support.v7.widget.RecyclerView;
 | 
				
			|||||||
import android.text.TextUtils;
 | 
					import android.text.TextUtils;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import org.commonmark.ext.gfm.tables.TableBlock;
 | 
					import org.commonmark.ext.gfm.tables.TableBlock;
 | 
				
			||||||
import org.commonmark.ext.gfm.tables.TablesExtension;
 | 
					 | 
				
			||||||
import org.commonmark.node.FencedCodeBlock;
 | 
					import org.commonmark.node.FencedCodeBlock;
 | 
				
			||||||
import org.commonmark.parser.Parser;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.io.BufferedReader;
 | 
					import java.io.BufferedReader;
 | 
				
			||||||
import java.io.IOException;
 | 
					import java.io.IOException;
 | 
				
			||||||
import java.io.InputStream;
 | 
					import java.io.InputStream;
 | 
				
			||||||
import java.io.InputStreamReader;
 | 
					import java.io.InputStreamReader;
 | 
				
			||||||
import java.util.Collections;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import ru.noties.debug.AndroidLogDebugOutput;
 | 
					import ru.noties.debug.AndroidLogDebugOutput;
 | 
				
			||||||
import ru.noties.debug.Debug;
 | 
					import ru.noties.debug.Debug;
 | 
				
			||||||
@ -33,6 +30,8 @@ import ru.noties.markwon.image.ImagesPlugin;
 | 
				
			|||||||
import ru.noties.markwon.image.svg.SvgPlugin;
 | 
					import ru.noties.markwon.image.svg.SvgPlugin;
 | 
				
			||||||
import ru.noties.markwon.recycler.MarkwonAdapter;
 | 
					import ru.noties.markwon.recycler.MarkwonAdapter;
 | 
				
			||||||
import ru.noties.markwon.recycler.SimpleEntry;
 | 
					import ru.noties.markwon.recycler.SimpleEntry;
 | 
				
			||||||
 | 
					import ru.noties.markwon.recycler.table.TableEntry;
 | 
				
			||||||
 | 
					import ru.noties.markwon.recycler.table.TableEntryPlugin;
 | 
				
			||||||
import ru.noties.markwon.sample.R;
 | 
					import ru.noties.markwon.sample.R;
 | 
				
			||||||
import ru.noties.markwon.urlprocessor.UrlProcessor;
 | 
					import ru.noties.markwon.urlprocessor.UrlProcessor;
 | 
				
			||||||
import ru.noties.markwon.urlprocessor.UrlProcessorRelativeToAbsolute;
 | 
					import ru.noties.markwon.urlprocessor.UrlProcessorRelativeToAbsolute;
 | 
				
			||||||
@ -51,14 +50,12 @@ public class RecyclerActivity extends Activity {
 | 
				
			|||||||
        // create MarkwonAdapter and register two blocks that will be rendered differently
 | 
					        // create MarkwonAdapter and register two blocks that will be rendered differently
 | 
				
			||||||
        // * fenced code block (can also specify the same Entry for indended code block)
 | 
					        // * fenced code block (can also specify the same Entry for indended code block)
 | 
				
			||||||
        // * table block
 | 
					        // * table block
 | 
				
			||||||
        final MarkwonAdapter adapter = MarkwonAdapter.builder()
 | 
					        final MarkwonAdapter adapter = MarkwonAdapter.builder(R.layout.adapter_default_entry, R.id.text)
 | 
				
			||||||
                // we can simply use bundled SimpleEntry, that will lookup a TextView
 | 
					                // we can simply use bundled SimpleEntry
 | 
				
			||||||
                // with `@+id/text` id
 | 
					                .include(FencedCodeBlock.class, SimpleEntry.create(R.layout.adapter_fenced_code_block, R.id.text))
 | 
				
			||||||
                .include(FencedCodeBlock.class, new SimpleEntry(R.layout.adapter_fenced_code_block))
 | 
					                .include(TableBlock.class, TableEntry.create(builder -> builder
 | 
				
			||||||
                // create own implementation of entry for different rendering
 | 
					                        .tableLayout(R.layout.adapter_table_block, R.id.table_layout)
 | 
				
			||||||
                .include(TableBlock.class, new TableEntry2())
 | 
					                        .textLayoutIsRoot(R.layout.view_table_entry_cell)))
 | 
				
			||||||
                // specify default entry (for all other blocks)
 | 
					 | 
				
			||||||
                .defaultEntry(new SimpleEntry(R.layout.adapter_default_entry))
 | 
					 | 
				
			||||||
                .build();
 | 
					                .build();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        final RecyclerView recyclerView = findViewById(R.id.recycler_view);
 | 
					        final RecyclerView recyclerView = findViewById(R.id.recycler_view);
 | 
				
			||||||
@ -71,10 +68,6 @@ public class RecyclerActivity extends Activity {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        // please note that we should notify updates (adapter doesn't do it implicitly)
 | 
					        // please note that we should notify updates (adapter doesn't do it implicitly)
 | 
				
			||||||
        adapter.notifyDataSetChanged();
 | 
					        adapter.notifyDataSetChanged();
 | 
				
			||||||
 | 
					 | 
				
			||||||
        // NB, there is no currently available widget to render tables gracefully
 | 
					 | 
				
			||||||
        // TableEntryView is here for demonstration purposes only (to show that rendering
 | 
					 | 
				
			||||||
        // tables
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @NonNull
 | 
					    @NonNull
 | 
				
			||||||
@ -83,14 +76,8 @@ public class RecyclerActivity extends Activity {
 | 
				
			|||||||
                .usePlugin(CorePlugin.create())
 | 
					                .usePlugin(CorePlugin.create())
 | 
				
			||||||
                .usePlugin(ImagesPlugin.createWithAssets(context))
 | 
					                .usePlugin(ImagesPlugin.createWithAssets(context))
 | 
				
			||||||
                .usePlugin(SvgPlugin.create(context.getResources()))
 | 
					                .usePlugin(SvgPlugin.create(context.getResources()))
 | 
				
			||||||
                .usePlugin(new AbstractMarkwonPlugin() {
 | 
					                // important to use TableEntryPlugin instead of TablePlugin
 | 
				
			||||||
                    @Override
 | 
					                .usePlugin(TableEntryPlugin.create(context))
 | 
				
			||||||
                    public void configureParser(@NonNull Parser.Builder builder) {
 | 
					 | 
				
			||||||
                        // it's important NOT to use TablePlugin
 | 
					 | 
				
			||||||
                        // the only thing we want from it is commonmark-java parser extension
 | 
					 | 
				
			||||||
                        builder.extensions(Collections.singleton(TablesExtension.create()));
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                })
 | 
					 | 
				
			||||||
                .usePlugin(HtmlPlugin.create())
 | 
					                .usePlugin(HtmlPlugin.create())
 | 
				
			||||||
//                .usePlugin(SyntaxHighlightPlugin.create())
 | 
					//                .usePlugin(SyntaxHighlightPlugin.create())
 | 
				
			||||||
                .usePlugin(new AbstractMarkwonPlugin() {
 | 
					                .usePlugin(new AbstractMarkwonPlugin() {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,67 +0,0 @@
 | 
				
			|||||||
package ru.noties.markwon.sample.recycler;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import android.support.annotation.NonNull;
 | 
					 | 
				
			||||||
import android.view.LayoutInflater;
 | 
					 | 
				
			||||||
import android.view.View;
 | 
					 | 
				
			||||||
import android.view.ViewGroup;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import org.commonmark.ext.gfm.tables.TableBlock;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import java.util.HashMap;
 | 
					 | 
				
			||||||
import java.util.Map;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import ru.noties.debug.Debug;
 | 
					 | 
				
			||||||
import ru.noties.markwon.Markwon;
 | 
					 | 
				
			||||||
import ru.noties.markwon.ext.tables.Table;
 | 
					 | 
				
			||||||
import ru.noties.markwon.recycler.MarkwonAdapter;
 | 
					 | 
				
			||||||
import ru.noties.markwon.sample.R;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// do not use in real applications, this is just a showcase
 | 
					 | 
				
			||||||
public class TableEntry implements MarkwonAdapter.Entry<TableBlock, TableEntry.TableNodeHolder> {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private final Map<TableBlock, Table> cache = new HashMap<>(2);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @NonNull
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public TableNodeHolder createHolder(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) {
 | 
					 | 
				
			||||||
        return new TableNodeHolder(inflater.inflate(R.layout.adapter_table_block, parent, false));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public void bindHolder(@NonNull Markwon markwon, @NonNull TableNodeHolder holder, @NonNull TableBlock node) {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Table table = cache.get(node);
 | 
					 | 
				
			||||||
        if (table == null) {
 | 
					 | 
				
			||||||
            table = Table.parse(markwon, node);
 | 
					 | 
				
			||||||
            cache.put(node, table);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Debug.i(table);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (table != null) {
 | 
					 | 
				
			||||||
            holder.tableEntryView.setTable(table);
 | 
					 | 
				
			||||||
            // render table
 | 
					 | 
				
			||||||
        } // we need to do something with null table...
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public long id(@NonNull TableBlock node) {
 | 
					 | 
				
			||||||
        return node.hashCode();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public void clear() {
 | 
					 | 
				
			||||||
        cache.clear();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    static class TableNodeHolder extends MarkwonAdapter.Holder {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        final TableEntryView tableEntryView;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        TableNodeHolder(@NonNull View itemView) {
 | 
					 | 
				
			||||||
            super(itemView);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            this.tableEntryView = requireView(R.id.table_entry);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,121 +0,0 @@
 | 
				
			|||||||
package ru.noties.markwon.sample.recycler;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import android.annotation.SuppressLint;
 | 
					 | 
				
			||||||
import android.content.Context;
 | 
					 | 
				
			||||||
import android.support.annotation.NonNull;
 | 
					 | 
				
			||||||
import android.view.Gravity;
 | 
					 | 
				
			||||||
import android.view.LayoutInflater;
 | 
					 | 
				
			||||||
import android.view.View;
 | 
					 | 
				
			||||||
import android.view.ViewGroup;
 | 
					 | 
				
			||||||
import android.widget.TableLayout;
 | 
					 | 
				
			||||||
import android.widget.TableRow;
 | 
					 | 
				
			||||||
import android.widget.TextView;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import org.commonmark.ext.gfm.tables.TableBlock;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import java.util.HashMap;
 | 
					 | 
				
			||||||
import java.util.Map;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import ru.noties.markwon.Markwon;
 | 
					 | 
				
			||||||
import ru.noties.markwon.ext.tables.Table;
 | 
					 | 
				
			||||||
import ru.noties.markwon.recycler.MarkwonAdapter;
 | 
					 | 
				
			||||||
import ru.noties.markwon.sample.R;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
public class TableEntry2 implements MarkwonAdapter.Entry<TableBlock, TableEntry2.TableHolder> {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private final Map<TableBlock, Table> map = new HashMap<>(3);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @NonNull
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public TableHolder createHolder(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) {
 | 
					 | 
				
			||||||
        return new TableHolder(inflater.inflate(R.layout.adapter_table_block_2, parent, false));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public void bindHolder(@NonNull Markwon markwon, @NonNull TableHolder holder, @NonNull TableBlock node) {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Table table = map.get(node);
 | 
					 | 
				
			||||||
        if (table == null) {
 | 
					 | 
				
			||||||
            table = Table.parse(markwon, node);
 | 
					 | 
				
			||||||
            map.put(node, table);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // check if this exact TableBlock was already
 | 
					 | 
				
			||||||
        final TableLayout layout = holder.layout;
 | 
					 | 
				
			||||||
        if (table == null
 | 
					 | 
				
			||||||
                || table == layout.getTag(R.id.table_layout)) {
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        layout.setTag(R.id.table_layout, table);
 | 
					 | 
				
			||||||
        layout.removeAllViews();
 | 
					 | 
				
			||||||
        layout.setBackgroundResource(R.drawable.bg_table_cell);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        final Context context = layout.getContext();
 | 
					 | 
				
			||||||
        final LayoutInflater inflater = LayoutInflater.from(context);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        TableRow tableRow;
 | 
					 | 
				
			||||||
        TextView textView;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for (Table.Row row : table.rows()) {
 | 
					 | 
				
			||||||
            tableRow = new TableRow(context);
 | 
					 | 
				
			||||||
            for (Table.Column column : row.columns()) {
 | 
					 | 
				
			||||||
                textView = (TextView) inflater.inflate(R.layout.view_table_entry_cell, tableRow, false);
 | 
					 | 
				
			||||||
                textView.setGravity(textGravity(column.alignment()));
 | 
					 | 
				
			||||||
                markwon.setParsedMarkdown(textView, column.content());
 | 
					 | 
				
			||||||
                textView.getPaint().setFakeBoldText(row.header());
 | 
					 | 
				
			||||||
                textView.setBackgroundResource(R.drawable.bg_table_cell);
 | 
					 | 
				
			||||||
                tableRow.addView(textView);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            layout.addView(tableRow);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public long id(@NonNull TableBlock node) {
 | 
					 | 
				
			||||||
        return node.hashCode();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public void clear() {
 | 
					 | 
				
			||||||
        map.clear();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    static class TableHolder extends MarkwonAdapter.Holder {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        final TableLayout layout;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        TableHolder(@NonNull View itemView) {
 | 
					 | 
				
			||||||
            super(itemView);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            this.layout = requireView(R.id.table_layout);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // we will use gravity instead of textAlignment because min sdk is 16 (textAlignment starts at 17)
 | 
					 | 
				
			||||||
    @SuppressLint("RtlHardcoded")
 | 
					 | 
				
			||||||
    private static int textGravity(@NonNull Table.Alignment alignment) {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        final int gravity;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        switch (alignment) {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            case LEFT:
 | 
					 | 
				
			||||||
                gravity = Gravity.LEFT;
 | 
					 | 
				
			||||||
                break;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            case CENTER:
 | 
					 | 
				
			||||||
                gravity = Gravity.CENTER_HORIZONTAL;
 | 
					 | 
				
			||||||
                break;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            case RIGHT:
 | 
					 | 
				
			||||||
                gravity = Gravity.RIGHT;
 | 
					 | 
				
			||||||
                break;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            default:
 | 
					 | 
				
			||||||
                throw new IllegalStateException("Unknown table alignment: " + alignment);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return gravity | Gravity.CENTER_VERTICAL;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,219 +0,0 @@
 | 
				
			|||||||
package ru.noties.markwon.sample.recycler;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import android.annotation.SuppressLint;
 | 
					 | 
				
			||||||
import android.content.Context;
 | 
					 | 
				
			||||||
import android.content.res.TypedArray;
 | 
					 | 
				
			||||||
import android.graphics.Canvas;
 | 
					 | 
				
			||||||
import android.graphics.Color;
 | 
					 | 
				
			||||||
import android.graphics.Paint;
 | 
					 | 
				
			||||||
import android.graphics.Rect;
 | 
					 | 
				
			||||||
import android.support.annotation.NonNull;
 | 
					 | 
				
			||||||
import android.support.annotation.Nullable;
 | 
					 | 
				
			||||||
import android.text.SpannedString;
 | 
					 | 
				
			||||||
import android.util.AttributeSet;
 | 
					 | 
				
			||||||
import android.view.Gravity;
 | 
					 | 
				
			||||||
import android.view.LayoutInflater;
 | 
					 | 
				
			||||||
import android.view.View;
 | 
					 | 
				
			||||||
import android.view.ViewGroup;
 | 
					 | 
				
			||||||
import android.widget.LinearLayout;
 | 
					 | 
				
			||||||
import android.widget.TextView;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import java.util.ArrayList;
 | 
					 | 
				
			||||||
import java.util.List;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import ru.noties.markwon.ext.tables.Table;
 | 
					 | 
				
			||||||
import ru.noties.markwon.sample.R;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
public class TableEntryView extends LinearLayout {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // paint and rect to draw borders
 | 
					 | 
				
			||||||
    private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
 | 
					 | 
				
			||||||
    private final Rect rect = new Rect();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private LayoutInflater inflater;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private int rowEvenBackgroundColor;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public TableEntryView(Context context) {
 | 
					 | 
				
			||||||
        super(context);
 | 
					 | 
				
			||||||
        init(context, null);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public TableEntryView(Context context, AttributeSet attrs) {
 | 
					 | 
				
			||||||
        super(context, attrs);
 | 
					 | 
				
			||||||
        init(context, attrs);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private void init(Context context, @Nullable AttributeSet attrs) {
 | 
					 | 
				
			||||||
        inflater = LayoutInflater.from(context);
 | 
					 | 
				
			||||||
        setOrientation(VERTICAL);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (attrs != null) {
 | 
					 | 
				
			||||||
            final TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.TableEntryView);
 | 
					 | 
				
			||||||
            try {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                rowEvenBackgroundColor = array.getColor(R.styleable.TableEntryView_tev_rowEvenBackgroundColor, 0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                final int stroke = array.getDimensionPixelSize(R.styleable.TableEntryView_tev_borderWidth, 0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                // half of requested
 | 
					 | 
				
			||||||
                final float strokeWidth = stroke > 0
 | 
					 | 
				
			||||||
                        ? stroke / 2.F
 | 
					 | 
				
			||||||
                        : context.getResources().getDisplayMetrics().density / 2.F;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                paint.setStyle(Paint.Style.STROKE);
 | 
					 | 
				
			||||||
                paint.setStrokeWidth(strokeWidth);
 | 
					 | 
				
			||||||
                paint.setColor(array.getColor(R.styleable.TableEntryView_tev_borderColor, Color.BLACK));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if (isInEditMode()) {
 | 
					 | 
				
			||||||
                    final String data = array.getString(R.styleable.TableEntryView_tev_debugData);
 | 
					 | 
				
			||||||
                    if (data != null) {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        boolean first = true;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        final List<Table.Row> rows = new ArrayList<>();
 | 
					 | 
				
			||||||
                        for (String row : data.split("\\|")) {
 | 
					 | 
				
			||||||
                            final List<Table.Column> columns = new ArrayList<>();
 | 
					 | 
				
			||||||
                            for (String column : row.split(",")) {
 | 
					 | 
				
			||||||
                                columns.add(new Table.Column(Table.Alignment.LEFT, new SpannedString(column)));
 | 
					 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
                            final boolean header = first;
 | 
					 | 
				
			||||||
                            first = false;
 | 
					 | 
				
			||||||
                            rows.add(new Table.Row(header, columns));
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                        final Table table = new Table(rows);
 | 
					 | 
				
			||||||
                        setTable(table);
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            } finally {
 | 
					 | 
				
			||||||
                array.recycle();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        setWillNotDraw(false);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public void setTable(@NonNull Table table) {
 | 
					 | 
				
			||||||
        final List<Table.Row> rows = table.rows();
 | 
					 | 
				
			||||||
        for (int i = 0, size = rows.size(); i < size; i++) {
 | 
					 | 
				
			||||||
            addRow(i, rows.get(i));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        requestLayout();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private void addRow(int index, @NonNull Table.Row row) {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        final ViewGroup group = ensureRow(index);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        final int backgroundColor = !row.header() && (index % 2) == 0
 | 
					 | 
				
			||||||
                ? rowEvenBackgroundColor
 | 
					 | 
				
			||||||
                : 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        group.setBackgroundColor(backgroundColor);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        final List<Table.Column> columns = row.columns();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        TextView textView;
 | 
					 | 
				
			||||||
        Table.Column column;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for (int i = 0, size = columns.size(); i < size; i++) {
 | 
					 | 
				
			||||||
            textView = ensureCell(group, i);
 | 
					 | 
				
			||||||
            column = columns.get(i);
 | 
					 | 
				
			||||||
            textView.setGravity(textGravity(column.alignment()));
 | 
					 | 
				
			||||||
            textView.setText(column.content());
 | 
					 | 
				
			||||||
            textView.getPaint().setFakeBoldText(row.header());
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        group.requestLayout();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @NonNull
 | 
					 | 
				
			||||||
    private ViewGroup ensureRow(int index) {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        final int count = getChildCount();
 | 
					 | 
				
			||||||
        if (index >= count) {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // count=0,index=1, diff=2
 | 
					 | 
				
			||||||
            // count=0,index=5, diff=6
 | 
					 | 
				
			||||||
            // count=1,index=2, diff=2
 | 
					 | 
				
			||||||
            int diff = index - count + 1;
 | 
					 | 
				
			||||||
            while (diff > 0) {
 | 
					 | 
				
			||||||
                addView(inflater.inflate(R.layout.view_table_entry_row, this, false));
 | 
					 | 
				
			||||||
                diff -= 1;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return (ViewGroup) getChildAt(index);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @NonNull
 | 
					 | 
				
			||||||
    private TextView ensureCell(@NonNull ViewGroup group, int index) {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        final int count = group.getChildCount();
 | 
					 | 
				
			||||||
        if (index >= count) {
 | 
					 | 
				
			||||||
            int diff = index - count + 1;
 | 
					 | 
				
			||||||
            while (diff > 0) {
 | 
					 | 
				
			||||||
                group.addView(inflater.inflate(R.layout.view_table_entry_cell, group, false));
 | 
					 | 
				
			||||||
                diff -= 1;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return (TextView) group.getChildAt(index);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    protected void onDraw(Canvas canvas) {
 | 
					 | 
				
			||||||
        super.onDraw(canvas);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        final int rows = getChildCount();
 | 
					 | 
				
			||||||
        if (rows == 0) {
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // first draw the whole border
 | 
					 | 
				
			||||||
        rect.set(0, 0, getWidth(), getHeight());
 | 
					 | 
				
			||||||
        canvas.drawRect(rect, paint);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        ViewGroup group;
 | 
					 | 
				
			||||||
        View view;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        int top;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for (int row = 0; row < rows; row++) {
 | 
					 | 
				
			||||||
            group = (ViewGroup) getChildAt(row);
 | 
					 | 
				
			||||||
            top = group.getTop();
 | 
					 | 
				
			||||||
            for (int col = 0, cols = group.getChildCount(); col < cols; col++) {
 | 
					 | 
				
			||||||
                view = group.getChildAt(col);
 | 
					 | 
				
			||||||
                rect.set(view.getLeft(), top + view.getTop(), view.getRight(), top + view.getBottom());
 | 
					 | 
				
			||||||
                canvas.drawRect(rect, paint);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // we will use gravity instead of textAlignment because min sdk is 16 (textAlignment starts at 17)
 | 
					 | 
				
			||||||
    @SuppressLint("RtlHardcoded")
 | 
					 | 
				
			||||||
    private static int textGravity(@NonNull Table.Alignment alignment) {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        final int gravity;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        switch (alignment) {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            case LEFT:
 | 
					 | 
				
			||||||
                gravity = Gravity.LEFT;
 | 
					 | 
				
			||||||
                break;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            case CENTER:
 | 
					 | 
				
			||||||
                gravity = Gravity.CENTER_HORIZONTAL;
 | 
					 | 
				
			||||||
                break;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            case RIGHT:
 | 
					 | 
				
			||||||
                gravity = Gravity.RIGHT;
 | 
					 | 
				
			||||||
                break;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            default:
 | 
					 | 
				
			||||||
                throw new IllegalStateException("Unknown table alignment: " + alignment);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return gravity;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1 +0,0 @@
 | 
				
			|||||||
../../../../../README.md
 | 
					 | 
				
			||||||
@ -1,21 +0,0 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="utf-8"?>
 | 
					 | 
				
			||||||
<HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android"
 | 
					 | 
				
			||||||
    xmlns:app="http://schemas.android.com/apk/res-auto"
 | 
					 | 
				
			||||||
    android:layout_width="match_parent"
 | 
					 | 
				
			||||||
    android:layout_height="wrap_content"
 | 
					 | 
				
			||||||
    android:clipChildren="false"
 | 
					 | 
				
			||||||
    android:clipToPadding="false"
 | 
					 | 
				
			||||||
    android:paddingLeft="16dip"
 | 
					 | 
				
			||||||
    android:paddingTop="8dip"
 | 
					 | 
				
			||||||
    android:paddingRight="16dip"
 | 
					 | 
				
			||||||
    android:paddingBottom="8dip"
 | 
					 | 
				
			||||||
    android:scrollbarStyle="outsideInset">
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    <ru.noties.markwon.sample.recycler.TableEntryView
 | 
					 | 
				
			||||||
        android:id="@+id/table_entry"
 | 
					 | 
				
			||||||
        android:layout_width="wrap_content"
 | 
					 | 
				
			||||||
        android:layout_height="wrap_content"
 | 
					 | 
				
			||||||
        app:tev_debugData="head1,head2,head3|col1,col2,col3|col1,col2,col3|col1,col2,col3"
 | 
					 | 
				
			||||||
        app:tev_rowEvenBackgroundColor="#40ff0000" />
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
</HorizontalScrollView>
 | 
					 | 
				
			||||||
@ -1,9 +0,0 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="utf-8"?>
 | 
					 | 
				
			||||||
<resources>
 | 
					 | 
				
			||||||
    <declare-styleable name="TableEntryView">
 | 
					 | 
				
			||||||
        <attr name="tev_rowEvenBackgroundColor" format="color" />
 | 
					 | 
				
			||||||
        <attr name="tev_borderColor" format="color" />
 | 
					 | 
				
			||||||
        <attr name="tev_borderWidth" format="dimension" />
 | 
					 | 
				
			||||||
        <attr name="tev_debugData" format="string" />
 | 
					 | 
				
			||||||
    </declare-styleable>
 | 
					 | 
				
			||||||
</resources>
 | 
					 | 
				
			||||||
@ -1,24 +0,0 @@
 | 
				
			|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
 | 
					 | 
				
			||||||
        android:width="108dp"
 | 
					 | 
				
			||||||
        android:height="108dp"
 | 
					 | 
				
			||||||
        android:viewportWidth="512"
 | 
					 | 
				
			||||||
        android:viewportHeight="512">
 | 
					 | 
				
			||||||
  <path
 | 
					 | 
				
			||||||
      android:pathData="M0,0h512v256h-512z"
 | 
					 | 
				
			||||||
      android:strokeAlpha="0.94117647"
 | 
					 | 
				
			||||||
      android:strokeWidth="0.40000001"
 | 
					 | 
				
			||||||
      android:fillColor="#000000"
 | 
					 | 
				
			||||||
      android:strokeColor="#00000000"
 | 
					 | 
				
			||||||
      android:fillType="nonZero"
 | 
					 | 
				
			||||||
      android:fillAlpha="1"
 | 
					 | 
				
			||||||
      android:strokeLineCap="butt"/>
 | 
					 | 
				
			||||||
  <path
 | 
					 | 
				
			||||||
      android:pathData="M0,256h512v256h-512z"
 | 
					 | 
				
			||||||
      android:strokeAlpha="0.94117647"
 | 
					 | 
				
			||||||
      android:strokeWidth="0.40000001"
 | 
					 | 
				
			||||||
      android:fillColor="#333333"
 | 
					 | 
				
			||||||
      android:strokeColor="#00000000"
 | 
					 | 
				
			||||||
      android:fillType="nonZero"
 | 
					 | 
				
			||||||
      android:fillAlpha="1"
 | 
					 | 
				
			||||||
      android:strokeLineCap="butt"/>
 | 
					 | 
				
			||||||
</vector>
 | 
					 | 
				
			||||||
@ -13,6 +13,7 @@
 | 
				
			|||||||
    <TableLayout
 | 
					    <TableLayout
 | 
				
			||||||
        android:id="@+id/table_layout"
 | 
					        android:id="@+id/table_layout"
 | 
				
			||||||
        android:layout_width="wrap_content"
 | 
					        android:layout_width="wrap_content"
 | 
				
			||||||
        android:layout_height="wrap_content" />
 | 
					        android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					        android:stretchColumns="*" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
</HorizontalScrollView>
 | 
					</HorizontalScrollView>
 | 
				
			||||||
@ -2,9 +2,7 @@
 | 
				
			|||||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
 | 
					<TextView xmlns:android="http://schemas.android.com/apk/res/android"
 | 
				
			||||||
    xmlns:tools="http://schemas.android.com/tools"
 | 
					    xmlns:tools="http://schemas.android.com/tools"
 | 
				
			||||||
    android:layout_width="wrap_content"
 | 
					    android:layout_width="wrap_content"
 | 
				
			||||||
    android:layout_height="match_parent"
 | 
					    android:layout_height="wrap_content"
 | 
				
			||||||
    android:gravity="center_vertical"
 | 
					 | 
				
			||||||
    android:padding="8dip"
 | 
					 | 
				
			||||||
    android:textAppearance="?android:attr/textAppearanceMedium"
 | 
					    android:textAppearance="?android:attr/textAppearanceMedium"
 | 
				
			||||||
    android:textColor="#000"
 | 
					    android:textColor="#000"
 | 
				
			||||||
    android:textSize="16sp"
 | 
					    android:textSize="16sp"
 | 
				
			||||||
@ -1,5 +0,0 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="utf-8"?>
 | 
					 | 
				
			||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
 | 
					 | 
				
			||||||
    <background android:drawable="@drawable/ic_launcher_background"/>
 | 
					 | 
				
			||||||
    <foreground android:drawable="@mipmap/ic_launcher_foreground"/>
 | 
					 | 
				
			||||||
</adaptive-icon>
 | 
					 | 
				
			||||||
| 
		 Before Width: | Height: | Size: 3.3 KiB  | 
| 
		 Before Width: | Height: | Size: 2.3 KiB  | 
| 
		 Before Width: | Height: | Size: 4.6 KiB  | 
| 
		 Before Width: | Height: | Size: 3.2 KiB  | 
| 
		 Before Width: | Height: | Size: 7.6 KiB  | 
| 
		 Before Width: | Height: | Size: 5.0 KiB  | 
| 
		 Before Width: | Height: | Size: 11 KiB  | 
| 
		 Before Width: | Height: | Size: 6.6 KiB  | 
@ -10,5 +10,6 @@ include ':app', ':sample',
 | 
				
			|||||||
        ':markwon-image-okhttp',
 | 
					        ':markwon-image-okhttp',
 | 
				
			||||||
        ':markwon-image-svg',
 | 
					        ':markwon-image-svg',
 | 
				
			||||||
        ':markwon-recycler',
 | 
					        ':markwon-recycler',
 | 
				
			||||||
 | 
					        ':markwon-recycler-table',
 | 
				
			||||||
        ':markwon-syntax-highlight',
 | 
					        ':markwon-syntax-highlight',
 | 
				
			||||||
        ':markwon-test-span'
 | 
					        ':markwon-test-span'
 | 
				
			||||||
 | 
				
			|||||||