Sample app readme functionality

This commit is contained in:
Dimitry Ivanov 2020-06-26 17:12:13 +03:00
parent 860d70d6d1
commit 45d205ba8c
43 changed files with 638 additions and 115 deletions

@ -45,19 +45,46 @@ androidExtensions {
features = ["parcelize"]
}
configurations.all {
exclude group: 'org.jetbrains', module: 'annotations-java5'
}
dependencies {
kapt project(':sample-utils:processor')
deps['annotationProcessor'].with {
kapt it['prism4j-bundler']
}
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation project(':markwon-core')
implementation project(':markwon-editor')
implementation project(':markwon-ext-latex')
implementation project(':markwon-ext-strikethrough')
implementation project(':markwon-ext-tables')
implementation project(':markwon-ext-tasklist')
implementation project(':markwon-html')
implementation project(':markwon-image')
implementation project(':markwon-inline-parser')
implementation project(':markwon-linkify')
implementation project(':markwon-recycler')
implementation project(':markwon-recycler-table')
implementation project(':markwon-simple-ext')
implementation project(':markwon-syntax-highlight')
implementation project(':markwon-image-picasso')
implementation project(':markwon-image-glide')
deps.with {
implementation it['x-recycler-view']
implementation it['x-cardview']
implementation it['x-fragment']
implementation it['okhttp']
implementation it['prism4j']
implementation it['gson']
implementation it['adapt']
implementation it['debug']
implementation it['android-svg']
implementation it['android-gif']
}
}

@ -13,7 +13,7 @@
android:layout_height="wrap_content"
android:text="Hey!" />
<io.noties.markwon.app.base.FlowLayout
<io.noties.markwon.app.widget.FlowLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/content_padding"
@ -86,9 +86,9 @@
android:background="@drawable/bg_artifact"
android:text="core" />
</io.noties.markwon.app.base.FlowLayout>
</io.noties.markwon.app.widget.FlowLayout>
<io.noties.markwon.app.base.FlowLayout
<io.noties.markwon.app.widget.FlowLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/content_padding"
@ -161,6 +161,6 @@
android:background="@drawable/bg_tag"
android:text="core" />
</io.noties.markwon.app.base.FlowLayout>
</io.noties.markwon.app.widget.FlowLayout>
</LinearLayout>

@ -3,6 +3,8 @@
xmlns:tools="http://schemas.android.com/tools"
package="io.noties.markwon.app">
<uses-permission android:name="android.permission.INTERNET" />
<application
android:name=".App"
android:allowBackup="true"
@ -13,12 +15,32 @@
android:theme="@style/AppTheme"
tools:ignore="AllowBackup,GoogleAppIndexingWarning">
<activity android:name=".MainActivity">
<activity android:name=".sample.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".readme.ReadMeActivity"
android:exported="true">
<!-- github markdown files handling -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="github.com"
android:pathPattern=".*\\.md"
android:scheme="https" />
</intent-filter>
</activity>
</application>
</manifest>

@ -0,0 +1 @@
../../../../README.md

@ -3,6 +3,8 @@ package io.noties.markwon.app
import android.app.Application
import io.noties.debug.AndroidLogDebugOutput
import io.noties.debug.Debug
import io.noties.markwon.app.sample.SampleManager
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
@Suppress("unused")
@ -13,10 +15,12 @@ class App : Application() {
Debug.init(AndroidLogDebugOutput(BuildConfig.DEBUG))
sampleManager = SampleManager(this, Executors.newCachedThreadPool())
executorService = Executors.newCachedThreadPool()
sampleManager = SampleManager(this, executorService)
}
companion object {
lateinit var executorService: ExecutorService
lateinit var sampleManager: SampleManager
}
}

@ -0,0 +1,25 @@
package io.noties.markwon.app.readme
import android.net.Uri
import android.text.TextUtils
import io.noties.markwon.image.destination.ImageDestinationProcessor
import io.noties.markwon.image.destination.ImageDestinationProcessorRelativeToAbsolute
class GithubImageDestinationProcessor(
username: String = "noties",
repository: String = "Markwon",
branch: String = "master"
) : ImageDestinationProcessor() {
private val processor = ImageDestinationProcessorRelativeToAbsolute("https://github.com/$username/$repository/raw/$branch/")
override fun process(destination: String): String {
// process only images without scheme information
val uri = Uri.parse(destination)
return if (TextUtils.isEmpty(uri.scheme)) {
processor.process(destination)
} else {
destination
}
}
}

@ -0,0 +1,166 @@
package io.noties.markwon.app.readme
import android.app.Activity
import android.content.Context
import android.net.Uri
import android.os.Bundle
import android.view.View
import android.widget.TextView
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import io.noties.debug.Debug
import io.noties.markwon.AbstractMarkwonPlugin
import io.noties.markwon.Markwon
import io.noties.markwon.MarkwonVisitor
import io.noties.markwon.app.R
import io.noties.markwon.app.utils.ReadMeUtils
import io.noties.markwon.app.utils.hidden
import io.noties.markwon.app.utils.loadReadMe
import io.noties.markwon.app.utils.textOrHide
import io.noties.markwon.ext.tasklist.TaskListPlugin
import io.noties.markwon.html.HtmlPlugin
import io.noties.markwon.image.ImagesPlugin
import io.noties.markwon.recycler.MarkwonAdapter
import io.noties.markwon.recycler.SimpleEntry
import io.noties.markwon.recycler.table.TableEntry
import io.noties.markwon.recycler.table.TableEntryPlugin
import io.noties.markwon.syntax.Prism4jThemeDefault
import io.noties.markwon.syntax.SyntaxHighlightPlugin
import io.noties.prism4j.Prism4j
import io.noties.prism4j.annotations.PrismBundle
import okhttp3.Call
import okhttp3.Callback
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.commonmark.ext.gfm.tables.TableBlock
import org.commonmark.node.FencedCodeBlock
import java.io.IOException
@PrismBundle(includeAll = true)
class ReadMeActivity : Activity() {
private lateinit var progressBar: View
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_read_me)
progressBar = findViewById(R.id.progress_bar)
val data = intent.data
Debug.i(data)
initAppBar(data)
initRecyclerView(data)
}
private val markwon: Markwon
get() = Markwon.builder(this)
.usePlugin(ImagesPlugin.create())
.usePlugin(HtmlPlugin.create())
.usePlugin(TableEntryPlugin.create(this))
.usePlugin(SyntaxHighlightPlugin.create(Prism4j(GrammarLocatorDef()), Prism4jThemeDefault.create(0)))
.usePlugin(TaskListPlugin.create(this))
.usePlugin(ReadMeImageDestinationPlugin(intent.data))
.usePlugin(object : AbstractMarkwonPlugin() {
override fun configureVisitor(builder: MarkwonVisitor.Builder) {
builder.on(FencedCodeBlock::class.java) { visitor, block ->
// we actually won't be applying code spans here, as our custom view will
// draw background and apply mono typeface
//
// NB the `trim` operation on literal (as code will have a new line at the end)
val code = visitor.configuration()
.syntaxHighlight()
.highlight(block.info, block.literal.trim())
visitor.builder().append(code)
}
}
})
.build()
private fun initAppBar(data: Uri?) {
val appBar = findViewById<View>(R.id.app_bar)
appBar.findViewById<View>(R.id.app_bar_icon).setOnClickListener { onBackPressed() }
val (title: String, subtitle: String?) = if (data == null) {
Pair("README.md", null)
} else {
Pair(data.lastPathSegment ?: "", data.toString())
}
appBar.findViewById<TextView>(R.id.title).text = title
appBar.findViewById<TextView>(R.id.subtitle).textOrHide(subtitle)
}
private fun initRecyclerView(data: Uri?) {
val adapter = MarkwonAdapter.builder(R.layout.adapter_node, R.id.text_view)
.include(FencedCodeBlock::class.java, SimpleEntry.create(R.layout.adapter_node_code_block, R.id.text_view))
.include(TableBlock::class.java, TableEntry.create {
it
.tableLayout(R.layout.adapter_node_table_block, R.id.table_layout)
.textLayoutIsRoot(R.layout.view_table_entry_cell)
})
.build()
val recyclerView: RecyclerView = findViewById(R.id.recycler_view)
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.setHasFixedSize(true)
recyclerView.itemAnimator = DefaultItemAnimator()
recyclerView.adapter = adapter
load(applicationContext, data) { result ->
when (result) {
is Result.Failure -> Debug.e(result.throwable)
is Result.Success -> {
val markwon = markwon
val node = markwon.parse(result.markdown)
if (window != null) {
recyclerView.post {
adapter.setParsedMarkdown(markwon, node)
adapter.notifyDataSetChanged()
progressBar.hidden = true
}
}
}
}
}
}
private sealed class Result {
data class Success(val markdown: String) : Result()
data class Failure(val throwable: Throwable) : Result()
}
private companion object {
fun load(context: Context, data: Uri?, callback: (Result) -> Unit) = try {
if (data == null) {
callback.invoke(Result.Success(loadReadMe(context)))
} else {
val request = Request.Builder()
.get()
.url(ReadMeUtils.buildRawGithubUrl(data))
.build()
OkHttpClient().newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
callback.invoke(Result.Failure(e))
}
override fun onResponse(call: Call, response: Response) {
val md = response.body()?.string() ?: ""
callback.invoke(Result.Success(md))
}
})
}
} catch (t: Throwable) {
callback.invoke(Result.Failure(t))
}
}
}

@ -0,0 +1,21 @@
package io.noties.markwon.app.readme
import android.net.Uri
import io.noties.markwon.AbstractMarkwonPlugin
import io.noties.markwon.MarkwonConfiguration
import io.noties.markwon.app.utils.ReadMeUtils
class ReadMeImageDestinationPlugin(private val data: Uri?) : AbstractMarkwonPlugin() {
override fun configureConfiguration(builder: MarkwonConfiguration.Builder) {
val info = ReadMeUtils.parseInfo(data)
if (info == null) {
builder.imageDestinationProcessor(GithubImageDestinationProcessor())
} else {
builder.imageDestinationProcessor(GithubImageDestinationProcessor(
username = info.username,
repository = info.repository,
branch = info.branch
))
}
}
}

@ -1,9 +1,9 @@
package io.noties.markwon.app
package io.noties.markwon.app.sample
import android.os.Bundle
import android.view.Window
import androidx.fragment.app.FragmentActivity
import io.noties.markwon.app.ui.SampleListFragment
import io.noties.markwon.app.sample.ui.SampleListFragment
class MainActivity : FragmentActivity() {

@ -1,4 +1,4 @@
package io.noties.markwon.app
package io.noties.markwon.app.sample
import android.os.Parcelable
import io.noties.markwon.sample.annotations.MarkwonArtifact

@ -1,4 +1,4 @@
package io.noties.markwon.app.adapt
package io.noties.markwon.app.sample
import android.text.Spanned
import android.view.LayoutInflater
@ -8,8 +8,7 @@ import android.widget.TextView
import io.noties.adapt.Item
import io.noties.markwon.Markwon
import io.noties.markwon.app.R
import io.noties.markwon.app.Sample
import io.noties.markwon.app.base.FlowLayout
import io.noties.markwon.app.widget.FlowLayout
import io.noties.markwon.app.utils.displayName
import io.noties.markwon.app.utils.hidden
import io.noties.markwon.app.utils.tagDisplayName

@ -1,4 +1,4 @@
package io.noties.markwon.app
package io.noties.markwon.app.sample
import android.content.Context
import io.noties.markwon.app.utils.Cancellable

@ -1,4 +1,4 @@
package io.noties.markwon.app
package io.noties.markwon.app.sample
import io.noties.markwon.sample.annotations.MarkwonArtifact

@ -1,4 +1,4 @@
package io.noties.markwon.app.ui
package io.noties.markwon.app.sample.ui
import android.view.LayoutInflater
import android.view.View

@ -1,4 +1,4 @@
package io.noties.markwon.app.ui
package io.noties.markwon.app.sample.ui
import android.content.Context
import android.view.View

@ -0,0 +1,74 @@
package io.noties.markwon.app.sample.ui
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.fragment.app.Fragment
import io.noties.markwon.app.App
import io.noties.markwon.app.R
import io.noties.markwon.app.sample.Sample
import io.noties.markwon.app.utils.hidden
import io.noties.markwon.app.utils.readCode
import io.noties.markwon.syntax.Prism4jSyntaxHighlight
import io.noties.markwon.syntax.Prism4jThemeDefault
import io.noties.prism4j.Prism4j
import io.noties.prism4j.annotations.PrismBundle
@PrismBundle(include = ["java", "kotlin"], grammarLocatorClassName = ".GrammarLocatorSourceCode")
class SampleCodeFragment : Fragment() {
private lateinit var progressBar: View
private lateinit var textView: TextView
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_sample_code, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
progressBar = view.findViewById(R.id.progress_bar)
textView = view.findViewById(R.id.text_view)
load()
}
private fun load() {
App.executorService.submit {
val code = sample.readCode(requireContext())
val prism = Prism4j(GrammarLocatorSourceCode())
val highlight = Prism4jSyntaxHighlight.create(prism, Prism4jThemeDefault.create(0))
val language = when (code.language) {
Sample.Language.KOTLIN -> "kotlin"
Sample.Language.JAVA -> "java"
}
val text = highlight.highlight(language, code.sourceCode)
textView.post {
//attached
if (context != null) {
progressBar.hidden = true
textView.text = text
}
}
}
}
private val sample: Sample by lazy(LazyThreadSafetyMode.NONE) {
arguments!!.getParcelable<Sample>(ARG_SAMPLE)
}
companion object {
private const val ARG_SAMPLE = "arg.Sample"
fun init(sample: Sample): SampleCodeFragment {
return SampleCodeFragment().apply {
arguments = Bundle().apply {
putParcelable(ARG_SAMPLE, sample)
}
}
}
}
}

@ -1,4 +1,4 @@
package io.noties.markwon.app.ui
package io.noties.markwon.app.sample.ui
import android.os.Bundle
import android.view.LayoutInflater
@ -8,7 +8,7 @@ import android.widget.TextView
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentTransaction
import io.noties.markwon.app.R
import io.noties.markwon.app.Sample
import io.noties.markwon.app.sample.Sample
import io.noties.markwon.app.utils.active
class SampleFragment : Fragment() {

@ -1,4 +1,4 @@
package io.noties.markwon.app.ui
package io.noties.markwon.app.sample.ui
import android.content.Context
import android.os.Bundle
@ -19,11 +19,11 @@ import io.noties.debug.Debug
import io.noties.markwon.Markwon
import io.noties.markwon.app.App
import io.noties.markwon.app.R
import io.noties.markwon.app.Sample
import io.noties.markwon.app.SampleManager
import io.noties.markwon.app.SampleSearch
import io.noties.markwon.app.adapt.SampleItem
import io.noties.markwon.app.base.SearchBar
import io.noties.markwon.app.sample.Sample
import io.noties.markwon.app.sample.SampleManager
import io.noties.markwon.app.sample.SampleSearch
import io.noties.markwon.app.sample.SampleItem
import io.noties.markwon.app.widget.SearchBar
import io.noties.markwon.app.utils.Cancellable
import io.noties.markwon.app.utils.displayName
import io.noties.markwon.app.utils.onPreDraw

@ -1,11 +1,11 @@
package io.noties.markwon.app.ui
package io.noties.markwon.app.sample.ui
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import io.noties.markwon.app.Sample
import io.noties.markwon.app.sample.Sample
class SamplePreviewFragment : Fragment() {

@ -1,7 +1,7 @@
package io.noties.markwon.app.samples
import io.noties.markwon.Markwon
import io.noties.markwon.app.ui.MarkwonTextViewSample
import io.noties.markwon.app.sample.ui.MarkwonTextViewSample
import io.noties.markwon.sample.annotations.MarkwonArtifact
import io.noties.markwon.sample.annotations.MarkwonSampleInfo

@ -1,7 +1,7 @@
package io.noties.markwon.app.samples.nested
import io.noties.markwon.Markwon
import io.noties.markwon.app.ui.MarkwonTextViewSample
import io.noties.markwon.app.sample.ui.MarkwonTextViewSample
import io.noties.markwon.sample.annotations.MarkwonArtifact
import io.noties.markwon.sample.annotations.MarkwonSampleInfo

@ -1,7 +1,7 @@
package io.noties.markwon.app.samples.nested;
import io.noties.markwon.Markwon;
import io.noties.markwon.app.ui.MarkwonTextViewSample;
import io.noties.markwon.app.sample.ui.MarkwonTextViewSample;
import io.noties.markwon.sample.annotations.MarkwonArtifact;
import io.noties.markwon.sample.annotations.MarkwonSampleInfo;

@ -1,43 +0,0 @@
package io.noties.markwon.app.ui
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.fragment.app.Fragment
import io.noties.markwon.app.R
import io.noties.markwon.app.Sample
import io.noties.markwon.app.utils.readCode
class SampleCodeFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_sample_code, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val textView: TextView = view.findViewById(R.id.text_view)
val code = sample.readCode(requireContext())
textView.text = code.sourceCode
}
private val sample: Sample by lazy(LazyThreadSafetyMode.NONE) {
arguments!!.getParcelable<Sample>(ARG_SAMPLE)
}
companion object {
private const val ARG_SAMPLE = "arg.Sample"
fun init(sample: Sample): SampleCodeFragment {
return SampleCodeFragment().apply {
arguments = Bundle().apply {
putParcelable(ARG_SAMPLE, sample)
}
}
}
}
}

@ -0,0 +1,21 @@
package io.noties.markwon.app.utils
import java.io.IOException
import java.io.InputStream
import java.util.Scanner
fun InputStream.readStringAndClose(): String {
try {
val scanner = Scanner(this).useDelimiter("\\A")
if (scanner.hasNext()) {
return scanner.next()
}
return ""
} finally {
try {
close()
} catch (e: IOException) {
// ignored
}
}
}

@ -0,0 +1,44 @@
package io.noties.markwon.app.utils
import android.net.Uri
import java.util.regex.Pattern
object ReadMeUtils {
// username, repo, branch, lastPathSegment
private val RE = Pattern.compile("^https:\\/\\/github\\.com\\/(\\w+?)\\/(\\w+?)\\/(?:blob|raw)\\/(\\w+?)\\/(.+)")
data class GithubInfo(
val username: String,
val repository: String,
val branch: String,
val fileName: String
)
fun parseInfo(data: Uri?): GithubInfo? {
if (data == null) {
return null
}
val matcher = RE.matcher(data.toString())
if (!matcher.matches()) {
return null
}
return GithubInfo(
username = matcher.group(1),
repository = matcher.group(2),
branch = matcher.group(3),
fileName = matcher.group(4)
)
}
fun buildRawGithubUrl(data: Uri): String {
val info = parseInfo(data)
return if (info == null) {
data.toString()
} else {
"https://github.com/${info.username}/${info.repository}/raw/${info.branch}/${info.fileName}"
}
}
}

@ -12,7 +12,7 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.List;
import io.noties.markwon.app.Sample;
import io.noties.markwon.app.sample.Sample;
public abstract class SampleUtils {

@ -1,10 +1,9 @@
package io.noties.markwon.app.utils
import android.content.Context
import io.noties.markwon.app.Sample
import io.noties.markwon.app.sample.Sample
import io.noties.markwon.sample.annotations.MarkwonArtifact
import java.io.InputStream
import java.util.Scanner
val MarkwonArtifact.displayName: String
get() = "@${artifactName()}"
@ -22,11 +21,6 @@ fun Sample.readCode(context: Context): Sample.Code {
.removePrefix(SAMPLE_PREFIX)
.replace('.', '/')
// now, we have 2 possibilities -> Kotlin or Java
fun read(stream: InputStream): String {
return Scanner(stream).useDelimiter("\\A").next()
}
fun obtain(path: String): InputStream? {
return try {
assets.open(path)
@ -35,6 +29,7 @@ fun Sample.readCode(context: Context): Sample.Code {
}
}
// now, we have 2 possibilities -> Kotlin or Java
var language: Sample.Language = Sample.Language.KOTLIN
var stream = obtain("$path.kt")
if (stream == null) {
@ -46,13 +41,12 @@ fun Sample.readCode(context: Context): Sample.Code {
throw IllegalStateException("Cannot obtain sample file at path: $path")
}
val code = read(stream)
try {
stream.close()
} catch (t: Throwable) {
// ignore
}
val code = stream.readStringAndClose()
return Sample.Code(language, code)
}
fun loadReadMe(context: Context): String {
val stream = context.assets.open("README.md")
return stream.readStringAndClose()
}

@ -0,0 +1,9 @@
package io.noties.markwon.app.utils
import android.text.TextUtils
import android.widget.TextView
fun TextView.textOrHide(text: CharSequence?) {
this.text = text
this.hidden = TextUtils.isEmpty(text)
}

@ -1,4 +1,4 @@
package io.noties.markwon.app.base
package io.noties.markwon.app.widget
import android.content.Context
import android.util.AttributeSet

@ -1,4 +1,4 @@
package io.noties.markwon.app.base
package io.noties.markwon.app.widget
import android.content.Context
import android.util.AttributeSet

@ -1,4 +1,4 @@
package io.noties.markwon.app.base
package io.noties.markwon.app.widget
import android.content.Context
import android.util.AttributeSet

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:orientation="vertical">
<LinearLayout
style="@style/AppBarContainer"
android:orientation="horizontal">
<ImageView
style="@style/AppBarIcon"
android:src="@drawable/ic_arrow_back_white_24dp"
tools:ignore="ContentDescription" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginEnd="@dimen/content_padding"
android:gravity="center_vertical"
android:orientation="vertical">
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="@color/white"
android:textStyle="bold"
tools:text="README.md" />
<TextView
android:id="@+id/subtitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="middle"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="@color/white"
tools:text="https://blah.blah/README.md" />
</LinearLayout>
</LinearLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false"
android:overScrollMode="never"
android:paddingTop="@dimen/content_padding"
android:paddingBottom="36dip" />
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="@dimen/progress_bar_side"
android:layout_height="@dimen/progress_bar_side"
android:layout_gravity="center" />
</FrameLayout>
</LinearLayout>

@ -54,7 +54,7 @@
android:textAppearance="?android:attr/textAppearance"
tools:text="Description goes here" />
<io.noties.markwon.app.base.FlowLayout
<io.noties.markwon.app.widget.FlowLayout
android:id="@+id/artifacts"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -66,9 +66,9 @@
<!-- we are actually fine with pre-inflating a single view -->
<include layout="@layout/view_artifact" />
</io.noties.markwon.app.base.FlowLayout>
</io.noties.markwon.app.widget.FlowLayout>
<io.noties.markwon.app.base.FlowLayout
<io.noties.markwon.app.widget.FlowLayout
android:id="@+id/tags"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -80,7 +80,7 @@
<include layout="@layout/view_tag" />
</io.noties.markwon.app.base.FlowLayout>
</io.noties.markwon.app.widget.FlowLayout>
</LinearLayout>

@ -0,0 +1,17 @@
<?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_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dip"
android:layout_marginRight="16dip"
android:breakStrategy="simple"
android:hyphenationFrequency="none"
android:lineSpacingExtra="2dip"
android:paddingTop="8dip"
android:paddingBottom="8dip"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="#000"
android:textSize="16sp"
tools:text="Hello" />

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<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:fillViewport="true"
android:paddingLeft="16dip"
android:paddingRight="16dip"
android:scrollbarStyle="outsideInset">
<TextView
android:id="@+id/text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#0f000000"
android:fontFamily="monospace"
android:lineSpacingExtra="2dip"
android:paddingLeft="16dip"
android:paddingTop="8dip"
android:paddingRight="16dip"
android:paddingBottom="8dip"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="14sp" />
</HorizontalScrollView>

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<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,9 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
@ -20,9 +24,18 @@
android:fontFamily="monospace"
android:lineSpacingExtra="2dip"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="?android:attr/textColorPrimary"
android:textSize="14sp"
tools:text="package io.noties.markwon" />
</HorizontalScrollView>
</androidx.core.widget.NestedScrollView>
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="@dimen/progress_bar_side"
android:layout_height="@dimen/progress_bar_side"
android:layout_gravity="center" />
</FrameLayout>

@ -42,7 +42,7 @@
android:paddingBottom="36dip"
tools:layout_marginTop="56dip" />
<io.noties.markwon.app.base.SearchBar
<io.noties.markwon.app.widget.SearchBar
android:id="@+id/search_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"

@ -51,7 +51,7 @@
android:visibility="gone"
tools:visibility="visible" />
<io.noties.markwon.app.base.TextField
<io.noties.markwon.app.widget.TextField
android:id="@+id/text_field"
android:layout_width="match_parent"
android:layout_height="wrap_content"

@ -0,0 +1,9 @@
<?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:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="#000"
android:textSize="16sp"
tools:text="Table content" />

@ -11,4 +11,7 @@
<dimen name="divider_height">1dip</dimen>
<dimen name="tab_bar_height">56dip</dimen>
<dimen name="progress_bar_side">64dip</dimen>
</resources>

@ -4,4 +4,5 @@
<string name="tab_bar_preview">Preview</string>
<string name="tab_bar_code">Code</string>
</resources>

@ -60,6 +60,7 @@ dependencies {
implementation it['debug']
implementation it['adapt']
implementation it['android-svg']
implementation it['android-gif']
}
deps['annotationProcessor'].with {