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"] features = ["parcelize"]
} }
configurations.all {
exclude group: 'org.jetbrains', module: 'annotations-java5'
}
dependencies { dependencies {
kapt project(':sample-utils:processor') kapt project(':sample-utils:processor')
deps['annotationProcessor'].with {
kapt it['prism4j-bundler']
}
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation project(':markwon-core') 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 { deps.with {
implementation it['x-recycler-view'] implementation it['x-recycler-view']
implementation it['x-cardview'] implementation it['x-cardview']
implementation it['x-fragment'] implementation it['x-fragment']
implementation it['okhttp']
implementation it['prism4j']
implementation it['gson'] implementation it['gson']
implementation it['adapt'] implementation it['adapt']
implementation it['debug'] implementation it['debug']
implementation it['android-svg']
implementation it['android-gif']
} }
} }

View File

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

View File

@ -3,6 +3,8 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
package="io.noties.markwon.app"> package="io.noties.markwon.app">
<uses-permission android:name="android.permission.INTERNET" />
<application <application
android:name=".App" android:name=".App"
android:allowBackup="true" android:allowBackup="true"
@ -13,12 +15,32 @@
android:theme="@style/AppTheme" android:theme="@style/AppTheme"
tools:ignore="AllowBackup,GoogleAppIndexingWarning"> tools:ignore="AllowBackup,GoogleAppIndexingWarning">
<activity android:name=".MainActivity"> <activity android:name=".sample.MainActivity">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </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> </application>
</manifest> </manifest>

View File

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

View File

@ -3,6 +3,8 @@ package io.noties.markwon.app
import android.app.Application import android.app.Application
import io.noties.debug.AndroidLogDebugOutput import io.noties.debug.AndroidLogDebugOutput
import io.noties.debug.Debug import io.noties.debug.Debug
import io.noties.markwon.app.sample.SampleManager
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors import java.util.concurrent.Executors
@Suppress("unused") @Suppress("unused")
@ -13,10 +15,12 @@ class App : Application() {
Debug.init(AndroidLogDebugOutput(BuildConfig.DEBUG)) Debug.init(AndroidLogDebugOutput(BuildConfig.DEBUG))
sampleManager = SampleManager(this, Executors.newCachedThreadPool()) executorService = Executors.newCachedThreadPool()
sampleManager = SampleManager(this, executorService)
} }
companion object { companion object {
lateinit var executorService: ExecutorService
lateinit var sampleManager: SampleManager 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.os.Bundle
import android.view.Window import android.view.Window
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import io.noties.markwon.app.ui.SampleListFragment import io.noties.markwon.app.sample.ui.SampleListFragment
class MainActivity : FragmentActivity() { 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 android.os.Parcelable
import io.noties.markwon.sample.annotations.MarkwonArtifact 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.text.Spanned
import android.view.LayoutInflater import android.view.LayoutInflater
@ -8,8 +8,7 @@ import android.widget.TextView
import io.noties.adapt.Item import io.noties.adapt.Item
import io.noties.markwon.Markwon import io.noties.markwon.Markwon
import io.noties.markwon.app.R import io.noties.markwon.app.R
import io.noties.markwon.app.Sample import io.noties.markwon.app.widget.FlowLayout
import io.noties.markwon.app.base.FlowLayout
import io.noties.markwon.app.utils.displayName import io.noties.markwon.app.utils.displayName
import io.noties.markwon.app.utils.hidden import io.noties.markwon.app.utils.hidden
import io.noties.markwon.app.utils.tagDisplayName 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 android.content.Context
import io.noties.markwon.app.utils.Cancellable 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 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.LayoutInflater
import android.view.View 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.content.Context
import android.view.View 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.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
@ -8,7 +8,7 @@ import android.widget.TextView
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentTransaction import androidx.fragment.app.FragmentTransaction
import io.noties.markwon.app.R 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 import io.noties.markwon.app.utils.active
class SampleFragment : Fragment() { 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.content.Context
import android.os.Bundle import android.os.Bundle
@ -19,11 +19,11 @@ import io.noties.debug.Debug
import io.noties.markwon.Markwon import io.noties.markwon.Markwon
import io.noties.markwon.app.App import io.noties.markwon.app.App
import io.noties.markwon.app.R import io.noties.markwon.app.R
import io.noties.markwon.app.Sample import io.noties.markwon.app.sample.Sample
import io.noties.markwon.app.SampleManager import io.noties.markwon.app.sample.SampleManager
import io.noties.markwon.app.SampleSearch import io.noties.markwon.app.sample.SampleSearch
import io.noties.markwon.app.adapt.SampleItem import io.noties.markwon.app.sample.SampleItem
import io.noties.markwon.app.base.SearchBar import io.noties.markwon.app.widget.SearchBar
import io.noties.markwon.app.utils.Cancellable import io.noties.markwon.app.utils.Cancellable
import io.noties.markwon.app.utils.displayName import io.noties.markwon.app.utils.displayName
import io.noties.markwon.app.utils.onPreDraw 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.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import io.noties.markwon.app.Sample import io.noties.markwon.app.sample.Sample
class SamplePreviewFragment : Fragment() { class SamplePreviewFragment : Fragment() {

View File

@ -1,7 +1,7 @@
package io.noties.markwon.app.samples package io.noties.markwon.app.samples
import io.noties.markwon.Markwon 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.MarkwonArtifact
import io.noties.markwon.sample.annotations.MarkwonSampleInfo import io.noties.markwon.sample.annotations.MarkwonSampleInfo

View File

@ -1,7 +1,7 @@
package io.noties.markwon.app.samples.nested package io.noties.markwon.app.samples.nested
import io.noties.markwon.Markwon 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.MarkwonArtifact
import io.noties.markwon.sample.annotations.MarkwonSampleInfo import io.noties.markwon.sample.annotations.MarkwonSampleInfo

View File

@ -1,7 +1,7 @@
package io.noties.markwon.app.samples.nested; package io.noties.markwon.app.samples.nested;
import io.noties.markwon.Markwon; 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.MarkwonArtifact;
import io.noties.markwon.sample.annotations.MarkwonSampleInfo; 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.io.InputStreamReader;
import java.util.List; import java.util.List;
import io.noties.markwon.app.Sample; import io.noties.markwon.app.sample.Sample;
public abstract class SampleUtils { public abstract class SampleUtils {

View File

@ -1,10 +1,9 @@
package io.noties.markwon.app.utils package io.noties.markwon.app.utils
import android.content.Context 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 io.noties.markwon.sample.annotations.MarkwonArtifact
import java.io.InputStream import java.io.InputStream
import java.util.Scanner
val MarkwonArtifact.displayName: String val MarkwonArtifact.displayName: String
get() = "@${artifactName()}" get() = "@${artifactName()}"
@ -22,11 +21,6 @@ fun Sample.readCode(context: Context): Sample.Code {
.removePrefix(SAMPLE_PREFIX) .removePrefix(SAMPLE_PREFIX)
.replace('.', '/') .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? { fun obtain(path: String): InputStream? {
return try { return try {
assets.open(path) 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 language: Sample.Language = Sample.Language.KOTLIN
var stream = obtain("$path.kt") var stream = obtain("$path.kt")
if (stream == null) { if (stream == null) {
@ -46,13 +41,12 @@ fun Sample.readCode(context: Context): Sample.Code {
throw IllegalStateException("Cannot obtain sample file at path: $path") throw IllegalStateException("Cannot obtain sample file at path: $path")
} }
val code = read(stream) val code = stream.readStringAndClose()
try {
stream.close()
} catch (t: Throwable) {
// ignore
}
return Sample.Code(language, code) 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.content.Context
import android.util.AttributeSet 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.content.Context
import android.util.AttributeSet 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.content.Context
import android.util.AttributeSet 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" android:textAppearance="?android:attr/textAppearance"
tools:text="Description goes here" /> tools:text="Description goes here" />
<io.noties.markwon.app.base.FlowLayout <io.noties.markwon.app.widget.FlowLayout
android:id="@+id/artifacts" android:id="@+id/artifacts"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -66,9 +66,9 @@
<!-- we are actually fine with pre-inflating a single view --> <!-- we are actually fine with pre-inflating a single view -->
<include layout="@layout/view_artifact" /> <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:id="@+id/tags"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -80,7 +80,7 @@
<include layout="@layout/view_tag" /> <include layout="@layout/view_tag" />
</io.noties.markwon.app.base.FlowLayout> </io.noties.markwon.app.widget.FlowLayout>
</LinearLayout> </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"?> <?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" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<HorizontalScrollView <androidx.core.widget.NestedScrollView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="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">
<TextView <HorizontalScrollView
android:id="@+id/text_view" android:layout_width="match_parent"
android:layout_width="wrap_content" android:layout_height="match_parent"
android:layout_height="wrap_content" android:clipChildren="false"
android:fontFamily="monospace" android:clipToPadding="false"
android:lineSpacingExtra="2dip" android:fillViewport="true"
android:textAppearance="?android:attr/textAppearanceMedium" android:padding="@dimen/content_padding_double"
android:textSize="14sp" android:scrollbarStyle="outsideInset">
tools:text="package io.noties.markwon" />
</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" android:paddingBottom="36dip"
tools:layout_marginTop="56dip" /> tools:layout_marginTop="56dip" />
<io.noties.markwon.app.base.SearchBar <io.noties.markwon.app.widget.SearchBar
android:id="@+id/search_bar" android:id="@+id/search_bar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View File

@ -51,7 +51,7 @@
android:visibility="gone" android:visibility="gone"
tools:visibility="visible" /> tools:visibility="visible" />
<io.noties.markwon.app.base.TextField <io.noties.markwon.app.widget.TextField
android:id="@+id/text_field" android:id="@+id/text_field"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" 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="divider_height">1dip</dimen>
<dimen name="tab_bar_height">56dip</dimen> <dimen name="tab_bar_height">56dip</dimen>
<dimen name="progress_bar_side">64dip</dimen>
</resources> </resources>

View File

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

View File

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