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

View File

@ -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']
}
}

View File

@ -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>

View File

@ -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>

View File

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

View File

@ -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
}
}

View File

@ -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
}
}
}

View File

@ -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))
}
}
}

View File

@ -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
))
}
}
}

View File

@ -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() {

View File

@ -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

View File

@ -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

View File

@ -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

View File

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

View File

@ -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

View File

@ -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

View File

@ -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)
}
}
}
}
}

View File

@ -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() {

View File

@ -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

View File

@ -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() {

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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)
}
}
}
}
}

View File

@ -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
}
}
}

View File

@ -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}"
}
}
}

View File

@ -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 {

View File

@ -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()
}

View File

@ -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)
}

View File

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

View File

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

View File

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

View File

@ -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>

View File

@ -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>

View File

@ -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" />

View File

@ -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>

View File

@ -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>

View File

@ -1,28 +1,41 @@
<?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">
<HorizontalScrollView
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false"
android:fillViewport="true"
android:padding="@dimen/content_padding_double"
android:scrollbarStyle="outsideInset">
android:layout_height="match_parent">
<TextView
android:id="@+id/text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="monospace"
android:lineSpacingExtra="2dip"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="14sp"
tools:text="package io.noties.markwon" />
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false"
android:fillViewport="true"
android:padding="@dimen/content_padding_double"
android:scrollbarStyle="outsideInset">
</HorizontalScrollView>
<TextView
android:id="@+id/text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
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" />
</androidx.core.widget.NestedScrollView>
</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>

View File

@ -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"

View File

@ -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"

View File

@ -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" />

View File

@ -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>

View File

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

View File

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