Sample app, bind list results, search

This commit is contained in:
Dimitry Ivanov 2020-06-18 15:39:21 +03:00
parent 7e8ed3ea0b
commit 66f77f35fe
43 changed files with 1100 additions and 212 deletions

View File

@ -1,6 +1,7 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-kapt'
apply plugin: 'kotlin-android-extensions'
android { android {
@ -40,14 +41,23 @@ kapt {
} }
} }
androidExtensions {
features = ["parcelize"]
}
dependencies { dependencies {
kapt project(':sample-utils:processor') kapt project(':sample-utils:processor')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation project(':markwon-core')
deps.with { deps.with {
api it['x-recycler-view'] implementation it['x-recycler-view']
api it['adapt'] implementation it['x-cardview']
api it['debug'] implementation it['x-fragment']
implementation it['gson']
implementation it['adapt']
implementation it['debug']
} }
} }

View File

@ -0,0 +1,166 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="@dimen/content_padding"
tools:ignore="MissingDefaultResource,HardcodedText">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Hey!" />
<io.noties.markwon.app.base.FlowLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/content_padding"
android:paddingBottom="@dimen/content_padding"
app:fl_spacingHorizontal="@dimen/content_padding"
app:fl_spacingVertical="4dip">
<TextView
style="@style/ArtifactTagText"
android:background="@drawable/bg_artifact"
android:text="core" />
<TextView
style="@style/ArtifactTagText"
android:background="@drawable/bg_artifact"
android:text="ext-latex" />
<TextView
style="@style/ArtifactTagText"
android:background="@drawable/bg_artifact"
android:text="another" />
<TextView
style="@style/ArtifactTagText"
android:background="@drawable/bg_artifact"
android:text="core" />
<TextView
style="@style/ArtifactTagText"
android:background="@drawable/bg_artifact"
android:text="core" />
<TextView
style="@style/ArtifactTagText"
android:background="@drawable/bg_artifact"
android:text="core" />
<TextView
style="@style/ArtifactTagText"
android:background="@drawable/bg_artifact"
android:text="core" />
<TextView
style="@style/ArtifactTagText"
android:background="@drawable/bg_artifact"
android:text="core" />
<TextView
style="@style/ArtifactTagText"
android:background="@drawable/bg_artifact"
android:text="core" />
<TextView
style="@style/ArtifactTagText"
android:background="@drawable/bg_artifact"
android:text="core" />
<TextView
style="@style/ArtifactTagText"
android:background="@drawable/bg_artifact"
android:text="core" />
<TextView
style="@style/ArtifactTagText"
android:background="@drawable/bg_artifact"
android:text="corejksjk sjkdf sdhjf sdjhf sjjksdjkjksd sdjksd hjsd hsdhjhjs shjsdhjhjsdhj sdj dshjs dhjsd sdhj sdjhsd hjsdh sjhd sdhjsd jhsd sdhj sdjhsd hjsd sjdh s" />
<TextView
style="@style/ArtifactTagText"
android:background="@drawable/bg_artifact"
android:text="core" />
</io.noties.markwon.app.base.FlowLayout>
<io.noties.markwon.app.base.FlowLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/content_padding"
android:paddingBottom="@dimen/content_padding"
app:fl_spacingHorizontal="@dimen/content_padding"
app:fl_spacingVertical="4dip">
<TextView
style="@style/ArtifactTagText"
android:background="@drawable/bg_tag"
android:text="core" />
<TextView
style="@style/ArtifactTagText"
android:background="@drawable/bg_tag"
android:text="ext-latex" />
<TextView
style="@style/ArtifactTagText"
android:background="@drawable/bg_tag"
android:text="another" />
<TextView
style="@style/ArtifactTagText"
android:background="@drawable/bg_tag"
android:text="core" />
<TextView
style="@style/ArtifactTagText"
android:background="@drawable/bg_tag"
android:text="core" />
<TextView
style="@style/ArtifactTagText"
android:background="@drawable/bg_tag"
android:text="core" />
<TextView
style="@style/ArtifactTagText"
android:background="@drawable/bg_tag"
android:text="core" />
<TextView
style="@style/ArtifactTagText"
android:background="@drawable/bg_tag"
android:text="core" />
<TextView
style="@style/ArtifactTagText"
android:background="@drawable/bg_tag"
android:text="core" />
<TextView
style="@style/ArtifactTagText"
android:background="@drawable/bg_tag"
android:text="core" />
<TextView
style="@style/ArtifactTagText"
android:background="@drawable/bg_tag"
android:text="core" />
<TextView
style="@style/ArtifactTagText"
android:background="@drawable/bg_tag"
android:text="corejksjk sjkdf sdhjf sdjhf sjjksdjkjksd sdjksd hjsd hsdhjhjs shjsdhjhjsdhj sdj dshjs dhjsd sdhj sdjhsd hjsdh sjhd sdhjsd jhsd sdhj sdjhsd hjsd sjdh s" />
<TextView
style="@style/ArtifactTagText"
android:background="@drawable/bg_tag"
android:text="core" />
</io.noties.markwon.app.base.FlowLayout>
</LinearLayout>

View File

@ -3,12 +3,20 @@ 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 java.util.concurrent.Executors
@Suppress("unused")
class App : Application() { class App : Application() {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
Debug.init(AndroidLogDebugOutput(BuildConfig.DEBUG)) Debug.init(AndroidLogDebugOutput(BuildConfig.DEBUG))
sampleManager = SampleManager(this, Executors.newCachedThreadPool())
}
companion object {
lateinit var sampleManager: SampleManager
} }
} }

View File

@ -1,61 +1,19 @@
package io.noties.markwon.app package io.noties.markwon.app
import android.app.Activity
import android.os.Bundle import android.os.Bundle
import android.view.ViewTreeObserver import android.view.Window
import androidx.recyclerview.widget.DefaultItemAnimator import androidx.fragment.app.FragmentActivity
import androidx.recyclerview.widget.LinearLayoutManager import io.noties.markwon.app.ui.SampleListFragment
import androidx.recyclerview.widget.RecyclerView
import io.noties.adapt.Adapt
import io.noties.debug.Debug
import io.noties.markwon.app.adapt.SampleItem
import io.noties.markwon.app.base.SearchBar
import io.noties.markwon.sample.annotations.MarkwonArtifact
class MainActivity : Activity() { class MainActivity : FragmentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val searchBar: SearchBar = findViewById(R.id.search_bar) if (supportFragmentManager.findFragmentById(Window.ID_ANDROID_CONTENT) == null) {
searchBar.onSearchListener = { supportFragmentManager.beginTransaction()
Debug.i("search: '$it'") .add(Window.ID_ANDROID_CONTENT, SampleListFragment.init())
.commitNowAllowingStateLoss()
} }
val recyclerView: RecyclerView = findViewById(R.id.recycler_view)
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.itemAnimator = DefaultItemAnimator()
recyclerView.setHasFixedSize(true)
recyclerView.clipToPadding = false
searchBar.viewTreeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener {
override fun onPreDraw(): Boolean {
searchBar.viewTreeObserver.removeOnPreDrawListener(this)
recyclerView.setPadding(
recyclerView.paddingLeft,
recyclerView.paddingTop + searchBar.height,
recyclerView.paddingRight,
recyclerView.paddingBottom
)
return true
}
})
val adapt = Adapt.create()
recyclerView.adapter = adapt
val list = listOf(
MarkwonSampleItem(
"first",
"1",
"Title first",
"Description her egoes and goes ang goes, so will it ever stop?",
listOf(MarkwonArtifact.CORE, MarkwonArtifact.EDITOR),
listOf("first", "second")
)
)
adapt.setItems(list.map { SampleItem(it) })
} }
} }

View File

@ -1,5 +0,0 @@
package io.noties.markwon.app
abstract class MarkwonSample {
}

View File

@ -1,12 +1,15 @@
package io.noties.markwon.app package io.noties.markwon.app
import android.os.Parcelable
import io.noties.markwon.sample.annotations.MarkwonArtifact import io.noties.markwon.sample.annotations.MarkwonArtifact
import kotlinx.android.parcel.Parcelize
data class MarkwonSampleItem( @Parcelize
data class Sample(
val javaClassName: String, val javaClassName: String,
val id: String, val id: String,
val title: String, val title: String,
val description: String, val description: String,
val artifacts: List<MarkwonArtifact>, val artifacts: List<MarkwonArtifact>,
val tags: List<String> val tags: List<String>
) ) : Parcelable

View File

@ -0,0 +1,71 @@
package io.noties.markwon.app
import android.content.Context
import io.noties.markwon.app.utils.Cancellable
import io.noties.markwon.app.utils.SampleUtils
import io.noties.markwon.sample.annotations.MarkwonArtifact
import java.util.concurrent.ExecutorService
class SampleManager(
private val context: Context,
private val executorService: ExecutorService
) {
private val samples: List<Sample> by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
SampleUtils.readSamples(context)
}
fun samples(search: SampleSearch?, callback: (List<Sample>) -> Unit): Cancellable {
var action: ((List<Sample>) -> Unit)? = callback
val future = executorService.submit {
val source = when (search) {
is SampleSearch.Artifact -> samples.filter { it.artifacts.contains(search.artifact) }
is SampleSearch.Tag -> samples.filter { it.tags.contains(search.tag) }
else -> samples.toList() // just copy all
}
val text = search?.text
val results = if (text == null) {
// no further filtering, just return the full source here
source
} else {
source.filter { filter(it, text) }
}
action?.invoke(results)
}
return object : Cancellable {
override val isCancelled: Boolean
get() = future.isDone
override fun cancel() {
action = null
future.cancel(true)
}
}
}
// if title contains,
// if description contains,
// if tags contains
// if artifacts contains,
private fun filter(sample: Sample, text: String): Boolean {
return sample.javaClassName.contains(text, true)
|| sample.title.contains(text, true)
|| sample.description.contains(text, true)
|| filterTags(sample.tags, text)
|| filterArtifacts(sample.artifacts, text)
}
private fun filterTags(tags: List<String>, text: String): Boolean {
return tags.firstOrNull { it.contains(text, true) } != null
}
private fun filterArtifacts(artifacts: List<MarkwonArtifact>, text: String): Boolean {
return artifacts.firstOrNull { it.artifactName().contains(text, true) } != null
}
}

View File

@ -0,0 +1,13 @@
package io.noties.markwon.app
import io.noties.markwon.sample.annotations.MarkwonArtifact
sealed class SampleSearch(val text: String?) {
class Artifact(text: String?, val artifact: MarkwonArtifact) : SampleSearch(text)
class Tag(text: String?, val tag: String) : SampleSearch(text)
class All(text: String?) : SampleSearch(text)
override fun toString(): String {
return "SampleSearch(text=$text,type=${javaClass.simpleName})"
}
}

View File

@ -1,22 +0,0 @@
package io.noties.markwon.app.adapt
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import io.noties.adapt.Item
import io.noties.markwon.sample.annotations.MarkwonArtifact
class ArtifactItem(artifact: MarkwonArtifact): Item<ArtifactItem.Holder>(artifact.name.hashCode().toLong()) {
override fun createHolder(inflater: LayoutInflater, parent: ViewGroup): Holder {
return Holder(inflater.inflate(0, parent, false))
}
override fun render(holder: Holder) {
}
class Holder(itemView: View): Item.Holder(itemView) {
val textView: TextView = requireView(0)
}
}

View File

@ -1,89 +1,100 @@
package io.noties.markwon.app.adapt package io.noties.markwon.app.adapt
import android.graphics.Color import android.text.Spanned
import android.text.Spannable
import android.text.SpannableStringBuilder
import android.text.TextPaint
import android.text.method.LinkMovementMethod
import android.text.style.ClickableSpan
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 android.widget.TextView import android.widget.TextView
import io.noties.adapt.Item import io.noties.adapt.Item
import io.noties.debug.Debug import io.noties.markwon.Markwon
import io.noties.markwon.app.MarkwonSampleItem
import io.noties.markwon.app.R import io.noties.markwon.app.R
import io.noties.markwon.app.Sample
import io.noties.markwon.app.base.FlowLayout
import io.noties.markwon.app.utils.displayName
import io.noties.markwon.app.utils.hidden
import io.noties.markwon.app.utils.tagDisplayName
import io.noties.markwon.sample.annotations.MarkwonArtifact import io.noties.markwon.sample.annotations.MarkwonArtifact
import io.noties.markwon.utils.NoCopySpannableFactory
class SampleItem(private val item: MarkwonSampleItem) : Item<SampleItem.Holder>(item.id.hashCode().toLong()) { class SampleItem(
private val markwon: Markwon,
private val sample: Sample,
private val onArtifactClick: (MarkwonArtifact) -> Unit,
private val onTagClick: (String) -> Unit,
private val onSampleClick: (Sample) -> Unit
) : Item<SampleItem.Holder>(sample.id.hashCode().toLong()) {
var search: String? = null // var search: String? = null
private val text: Spanned by lazy(LazyThreadSafetyMode.NONE) {
markwon.toMarkdown(sample.description)
}
override fun createHolder(inflater: LayoutInflater, parent: ViewGroup): Holder { override fun createHolder(inflater: LayoutInflater, parent: ViewGroup): Holder {
val holder = Holder(inflater.inflate(R.layout.adapt_sample, parent, false)) return Holder(inflater.inflate(R.layout.adapt_sample, parent, false)).apply {
holder.artifactsAndTags.movementMethod = LinkMovementMethod.getInstance() description.setSpannableFactory(NoCopySpannableFactory.getInstance())
return holder }
} }
override fun render(holder: Holder) { override fun render(holder: Holder) {
holder.apply { holder.apply {
title.text = item.title title.text = sample.title
description.text = item.description
artifactsAndTags.text = buildArtifactsAndTags
}
}
private val buildArtifactsAndTags: CharSequence val text = this@SampleItem.text
get() { if (text.isEmpty()) {
val builder = SpannableStringBuilder() description.text = ""
description.hidden = true
item.artifacts } else {
.forEach { markwon.setParsedMarkdown(description, text)
val length = builder.length description.hidden = false
builder.append("\u00a0${it.name}\u00a0")
builder.setSpan(ArtifactSpan(it), length, builder.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
if (!builder.isEmpty()) {
builder.append("\n")
} }
item.tags // there is no need to display the core artifact (it is implicit),
.forEach { // hide if empty (removed core)
val length = builder.length artifacts.ensure(sample.artifacts.size, R.layout.view_artifact)
builder.append("\u00a0$it\u00a0") .zip(sample.artifacts)
builder.setSpan(TagSpan(it), length, builder.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) .forEach { (view, artifact) ->
(view as TextView).text = artifact.displayName
view.setOnClickListener {
onArtifactClick(artifact)
}
} }
return builder tags.ensure(sample.tags.size, R.layout.view_tag)
.zip(sample.tags)
.forEach { (view, tag) ->
(view as TextView).text = tag.tagDisplayName
view.setOnClickListener {
onTagClick(tag)
}
}
itemView.setOnClickListener {
onSampleClick(sample)
}
} }
}
class Holder(itemView: View) : Item.Holder(itemView) { class Holder(itemView: View) : Item.Holder(itemView) {
val title: TextView = requireView(R.id.title) val title: TextView = requireView(R.id.title)
val description: TextView = requireView(R.id.description) val description: TextView = requireView(R.id.description)
val artifactsAndTags: TextView = requireView(R.id.artifacts_and_tags) val artifacts: FlowLayout = requireView(R.id.artifacts)
} val tags: FlowLayout = requireView(R.id.tags)
private class ArtifactSpan(val artifact: MarkwonArtifact) : ClickableSpan() {
override fun onClick(widget: View) {
Debug.i("clicked artifact: $artifact")
}
override fun updateDrawState(ds: TextPaint) {
ds.isUnderlineText = false
ds.bgColor = Color.GREEN
}
}
private class TagSpan(val tag: String) : ClickableSpan() {
override fun onClick(widget: View) {
Debug.i("clicked tag: $tag")
}
override fun updateDrawState(ds: TextPaint) {
ds.isUnderlineText = false
ds.bgColor = Color.BLUE
}
} }
} }
private fun FlowLayout.ensure(viewsCount: Int, layoutResId: Int): List<View> {
if (viewsCount > childCount) {
// inflate new views
val inflater = LayoutInflater.from(context)
for (i in 0 until (viewsCount - childCount)) {
addView(inflater.inflate(layoutResId, this, false))
}
} else {
// return requested vies and GONE the rest
for (i in viewsCount until childCount) {
getChildAt(i).hidden = true
}
}
return (0 until viewsCount).map { getChildAt(it) }
}

View File

@ -3,8 +3,25 @@ package io.noties.markwon.app.base
import android.content.Context import android.content.Context
import android.util.AttributeSet import android.util.AttributeSet
import android.view.ViewGroup import android.view.ViewGroup
import io.noties.markwon.app.R
import kotlin.math.max
class FlowLayout(context: Context, attrs: AttributeSet) : ViewGroup(context, attrs) { class FlowLayout(context: Context, attrs: AttributeSet) : ViewGroup(context, attrs) {
private val spacingVertical: Int
private val spacingHorizontal: Int
init {
val array = context.obtainStyledAttributes(attrs, R.styleable.FlowLayout)
try {
val spacing = array.getDimensionPixelSize(R.styleable.FlowLayout_fl_spacing, 0)
spacingVertical = array.getDimensionPixelSize(R.styleable.FlowLayout_fl_spacingVertical, spacing)
spacingHorizontal = array.getDimensionPixelSize(R.styleable.FlowLayout_fl_spacingHorizontal, spacing)
} finally {
array.recycle()
}
}
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
for (i in 0 until childCount) { for (i in 0 until childCount) {
val child = getChildAt(i) val child = getChildAt(i)
@ -21,7 +38,61 @@ class FlowLayout(context: Context, attrs: AttributeSet) : ViewGroup(context, att
} }
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec) val width = MeasureSpec.getSize(widthMeasureSpec)
// we must have width (match_parent or exact dimension)
if (width <= 0) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
return
}
val availableWidth = width - paddingLeft - paddingRight
// child must not exceed our width
val childWidthSpec = MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST)
// we also could enforce flexible height here (instead of exact one)
val childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
var x = 0
var y = 0
var lineHeight = 0
for (i in 0 until childCount) {
val child = getChildAt(i)
// measure
child.measure(childWidthSpec, childHeightSpec)
val params = child.layoutParams as LayoutParams
val measuredWidth = child.measuredWidth
if (measuredWidth > (availableWidth - x)) {
// new line
// make next child start at child measure width (starting at x = 0)
params.x = 0
params.y = y + lineHeight + spacingVertical
x = measuredWidth + spacingHorizontal
// move vertically by max value of child height on this line
y += lineHeight + spacingVertical
lineHeight = child.measuredHeight
} else {
// we fit this line
params.x = x
params.y = y
x += measuredWidth + spacingHorizontal
lineHeight = max(lineHeight, child.measuredHeight)
}
}
val height = y + lineHeight + paddingTop + paddingBottom
setMeasuredDimension(width, height)
} }
override fun generateDefaultLayoutParams(): ViewGroup.LayoutParams { override fun generateDefaultLayoutParams(): ViewGroup.LayoutParams {

View File

@ -61,6 +61,12 @@ class SearchBar(context: Context, attrs: AttributeSet?) : LinearLayout(context,
clear.setOnClickListener { clear.setOnClickListener {
textField.setText("") textField.setText("")
// ensure that we have focus when clear is clicked
if (!textField.hasFocus()) {
textField.requestFocus()
// additionally ensure keyboard is showing
KeyboardUtils.show(textField)
}
} }
cancel.setOnClickListener { cancel.setOnClickListener {
@ -69,6 +75,10 @@ class SearchBar(context: Context, attrs: AttributeSet?) : LinearLayout(context,
} }
} }
fun search(text: String) {
textField.setText(text)
}
private fun textFieldChanged(text: CharSequence) { private fun textFieldChanged(text: CharSequence) {
val isEmpty = text.isEmpty() val isEmpty = text.isEmpty()
clear.hidden = isEmpty clear.hidden = isEmpty

View File

@ -1,6 +1,6 @@
package io.noties.markwon.app.samples package io.noties.markwon.app.samples
import io.noties.markwon.app.MarkwonSample import io.noties.markwon.app.ui.MarkwonSample
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

@ -0,0 +1,5 @@
package io.noties.markwon.app.ui
abstract class MarkwonSample {
}

View File

@ -0,0 +1,20 @@
package io.noties.markwon.app.ui
import android.os.Bundle
import androidx.fragment.app.Fragment
import io.noties.markwon.app.Sample
class SampleFragment : Fragment() {
companion object {
private const val ARG_SAMPLE = "arg.Sample"
fun init(sample: Sample): SampleFragment {
val fragment = SampleFragment()
fragment.arguments = Bundle().apply {
putParcelable(ARG_SAMPLE, sample)
}
return fragment
}
}
}

View File

@ -0,0 +1,288 @@
package io.noties.markwon.app.ui
import android.content.Context
import android.os.Bundle
import android.os.Parcelable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.Window
import android.widget.ImageView
import android.widget.TextView
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import io.noties.adapt.Adapt
import io.noties.adapt.DiffUtilDataSetChanged
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.utils.Cancellable
import io.noties.markwon.app.utils.displayName
import io.noties.markwon.app.utils.onPreDraw
import io.noties.markwon.app.utils.recyclerView
import io.noties.markwon.app.utils.tagDisplayName
import io.noties.markwon.movement.MovementMethodPlugin
import io.noties.markwon.sample.annotations.MarkwonArtifact
import kotlinx.android.parcel.Parcelize
class SampleListFragment : Fragment() {
private val adapt: Adapt = Adapt.create(DiffUtilDataSetChanged.create())
private lateinit var markwon: Markwon
private val type: Type by lazy(LazyThreadSafetyMode.NONE) {
parseType(arguments!!)
}
private var search: String? = null
// postpone state restoration
private var pendingRecyclerScrollPosition: RecyclerScrollPosition? = null
private var cancellable: Cancellable? = null
private val sampleManager: SampleManager
get() = App.sampleManager
override fun onAttach(context: Context?) {
super.onAttach(context)
context?.also {
markwon = markwon(it)
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_sample_list, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initAppBar(view)
val context = requireContext()
val searchBar: SearchBar = view.findViewById(R.id.search_bar)
searchBar.onSearchListener = {
search = it
fetch()
}
val recyclerView: RecyclerView = view.findViewById(R.id.recycler_view)
recyclerView.layoutManager = LinearLayoutManager(context)
recyclerView.itemAnimator = DefaultItemAnimator()
recyclerView.setHasFixedSize(true)
recyclerView.adapter = adapt
// additional padding for RecyclerView
searchBar.onPreDraw {
recyclerView.setPadding(
recyclerView.paddingLeft,
recyclerView.paddingTop + searchBar.height,
recyclerView.paddingRight,
recyclerView.paddingBottom
)
}
val state: State? = savedInstanceState?.getParcelable(STATE)
pendingRecyclerScrollPosition = state?.recyclerScrollPosition
if (state?.search != null) {
searchBar.search(state.search)
} else {
fetch()
}
}
override fun onDestroyView() {
val cancellable = this.cancellable
if (cancellable != null && !cancellable.isCancelled) {
cancellable.cancel()
this.cancellable = null
}
super.onDestroyView()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
val state = State(
search,
adapt.recyclerView?.scrollPosition
)
outState.putParcelable(STATE, state)
}
private fun initAppBar(view: View) {
val appBar = view.findViewById<View>(R.id.app_bar)
val appBarIcon: ImageView = appBar.findViewById(R.id.app_bar_icon)
val appBarTitle: TextView = appBar.findViewById(R.id.app_bar_title)
val type = this.type
if (type is Type.All) {
return
}
appBarIcon.setImageResource(R.drawable.ic_arrow_back_white_24dp)
appBarIcon.setOnClickListener {
requireActivity().onBackPressed()
}
val (text, background) = when (type) {
is Type.Artifact -> Pair(type.artifact.displayName, R.drawable.bg_artifact)
is Type.Tag -> Pair(type.tag.tagDisplayName, R.drawable.bg_tag)
else -> error("Unexpected type: $type")
}
appBarTitle.text = text
appBarTitle.setBackgroundResource(background)
}
private fun bindSamples(samples: List<Sample>) {
val items = samples.map {
SampleItem(
markwon,
it,
{ artifact -> openArtifact(artifact) },
{ tag -> openTag(tag) },
{ sample -> openSample(sample) }
)
}
adapt.setItems(items)
val scrollPosition = pendingRecyclerScrollPosition
if (scrollPosition != null) {
pendingRecyclerScrollPosition = null
val recyclerView = adapt.recyclerView ?: return
recyclerView.onPreDraw {
(recyclerView.layoutManager as? LinearLayoutManager)
?.scrollToPositionWithOffset(scrollPosition.position, scrollPosition.offset)
}
}
}
private fun openArtifact(artifact: MarkwonArtifact) {
Debug.i(artifact)
openResultFragment(init(artifact))
}
private fun openTag(tag: String) {
Debug.i(tag)
openResultFragment(init(tag))
}
private fun openResultFragment(fragment: SampleListFragment) {
openFragment(fragment)
}
private fun openSample(sample: Sample) {
openFragment(SampleFragment.init(sample))
}
private fun openFragment(fragment: Fragment) {
fragmentManager!!.beginTransaction()
.setCustomAnimations(R.anim.screen_in, R.anim.screen_out, R.anim.screen_in_pop, R.anim.screen_out_pop)
.replace(Window.ID_ANDROID_CONTENT, fragment)
.addToBackStack(null)
.commitAllowingStateLoss()
}
private fun fetch() {
val sampleSearch: SampleSearch = when (val type = this.type) {
is Type.Artifact -> SampleSearch.Artifact(search, type.artifact)
is Type.Tag -> SampleSearch.Tag(search, type.tag)
else -> SampleSearch.All(search)
}
// clear current
cancellable?.let {
if (!it.isCancelled) {
it.cancel()
}
}
cancellable = sampleManager.samples(sampleSearch) {
bindSamples(it)
}
}
companion object {
private const val ARG_ARTIFACT = "arg.Artifact"
private const val ARG_TAG = "arg.Tag"
private const val STATE = "key.State"
fun init(): SampleListFragment {
val fragment = SampleListFragment()
fragment.arguments = Bundle()
return fragment
}
fun init(artifact: MarkwonArtifact): SampleListFragment {
val fragment = SampleListFragment()
fragment.arguments = Bundle().apply {
putString(ARG_ARTIFACT, artifact.name)
}
return fragment
}
fun init(tag: String): SampleListFragment {
val fragment = SampleListFragment()
fragment.arguments = Bundle().apply {
putString(ARG_TAG, tag)
}
return fragment
}
fun markwon(context: Context): Markwon {
return Markwon.builder(context)
.usePlugin(MovementMethodPlugin.none())
.build()
}
private fun parseType(arguments: Bundle): Type {
val name = arguments.getString(ARG_ARTIFACT)
val tag = arguments.getString(ARG_TAG)
return when {
name != null -> Type.Artifact(MarkwonArtifact.valueOf(name))
tag != null -> Type.Tag(tag)
else -> Type.All
}
}
}
@Parcelize
private data class State(
val search: String?,
val recyclerScrollPosition: RecyclerScrollPosition?
) : Parcelable
@Parcelize
private data class RecyclerScrollPosition(
val position: Int,
val offset: Int
) : Parcelable
private val RecyclerView.scrollPosition: RecyclerScrollPosition?
get() {
val holder = findViewHolderForLayoutPosition(0) ?: return null
val position = holder.adapterPosition
val offset = holder.itemView.top
return RecyclerScrollPosition(position, offset)
}
private sealed class Type {
class Artifact(val artifact: MarkwonArtifact) : Type()
class Tag(val tag: String) : Type()
object All : Type()
}
}

View File

@ -0,0 +1,16 @@
package io.noties.markwon.app.utils
import androidx.recyclerview.widget.RecyclerView
import io.noties.adapt.Adapt
import io.noties.debug.Debug
val Adapt.recyclerView: RecyclerView?
get() {
// internally throws if recycler is not present (detached from recyclerView)
return try {
recyclerView()
} catch (t: Throwable) {
Debug.e(t)
null
}
}

View File

@ -0,0 +1,7 @@
package io.noties.markwon.app.utils
interface Cancellable {
val isCancelled: Boolean
fun cancel()
}

View File

@ -0,0 +1,37 @@
package io.noties.markwon.app.utils;
import android.content.Context;
import androidx.annotation.NonNull;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.List;
import io.noties.markwon.app.Sample;
public abstract class SampleUtils {
@NonNull
public static List<Sample> readSamples(@NonNull Context context) {
final Gson gson = new Gson();
try (InputStream inputStream = context.getAssets().open("samples.json")) {
return gson.fromJson(
new InputStreamReader(inputStream),
new TypeToken<List<Sample>>() {
}.getType()
);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private SampleUtils() {
}
}

View File

@ -0,0 +1,9 @@
package io.noties.markwon.app.utils
import io.noties.markwon.sample.annotations.MarkwonArtifact
val MarkwonArtifact.displayName: String
get() = "@${artifactName()}"
val String.tagDisplayName: String
get() = "#$this"

View File

@ -3,9 +3,24 @@ package io.noties.markwon.app.utils
import android.view.View import android.view.View
import android.view.View.GONE import android.view.View.GONE
import android.view.View.VISIBLE import android.view.View.VISIBLE
import android.view.ViewTreeObserver
var View.hidden: Boolean var View.hidden: Boolean
get() = visibility == GONE get() = visibility == GONE
set(value) { set(value) {
visibility = if (value) GONE else VISIBLE visibility = if (value) GONE else VISIBLE
} }
fun View.onPreDraw(action: () -> Unit) {
viewTreeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener {
override fun onPreDraw(): Boolean {
val vto = viewTreeObserver
if (vto.isAlive) {
vto.removeOnPreDrawListener(this)
}
action()
// do not block drawing
return true
}
})
}

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@android:integer/config_shortAnimTime">
<alpha
android:fromAlpha="0"
android:toAlpha="1" />
<translate
android:fromXDelta="100%"
android:toXDelta="0" />
</set>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@android:integer/config_shortAnimTime">
<alpha
android:fromAlpha="0"
android:toAlpha="1" />
<translate
android:fromXDelta="-25%"
android:toXDelta="0" />
</set>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@android:integer/config_shortAnimTime">
<alpha
android:fromAlpha="1"
android:toAlpha="0.25" />
<translate
android:fromXDelta="0"
android:toXDelta="-25%" />
</set>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@android:integer/config_shortAnimTime">
<alpha
android:fromAlpha="1"
android:toAlpha="0" />
<translate
android:fromXDelta="0"
android:toXDelta="100%" />
</set>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/search_bar_background" />
<corners android:radius="128dip" />
</shape>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape>
<solid android:color="@color/window_background" />
</shape>
</item>
<item>
<bitmap
android:gravity="center"
android:src="@mipmap/ic_launcher_foreground"
android:tileMode="disabled" />
</item>
</layer-list>

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"/>
</vector>

View File

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/bg_artifact"
android:lines="1"
android:maxLines="1"
android:paddingStart="@dimen/content_padding"
android:paddingTop="2dip"
android:paddingEnd="@dimen/content_padding"
android:paddingBottom="2dip"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="@color/white"
tools:text="core" />

View File

@ -1,51 +1,87 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
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="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:layout_marginStart="@dimen/content_padding"
android:paddingTop="@dimen/content_padding" android:layout_marginTop="4dip"
android:paddingEnd="@dimen/content_padding" android:layout_marginEnd="@dimen/content_padding"
android:paddingBottom="@dimen/content_padding" android:layout_marginBottom="4dip"
tools:ignore="RtlSymmetry"> tools:ignore="RtlSymmetry">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal"> android:orientation="vertical"
android:paddingTop="@dimen/content_padding_half"
android:paddingEnd="@dimen/content_padding"
android:paddingBottom="@dimen/content_padding">
<!--textView instead of icon, so we align baseline--> <LinearLayout
<TextView
android:layout_width="@dimen/adapt_sample_hash_width"
android:layout_height="wrap_content"
android:gravity="center"
android:text="#"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textColor="?android:attr/colorSecondary"
tools:ignore="HardcodedText" />
<TextView
android:id="@+id/title"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge" android:orientation="horizontal">
tools:text="Title" />
<!--textView instead of icon, so we align baseline-->
<TextView
android:layout_width="@dimen/adapt_sample_hash_width"
android:layout_height="wrap_content"
android:alpha="0.5"
android:gravity="center"
android:text="#"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textColor="?android:attr/textColorSecondary"
tools:ignore="HardcodedText" />
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textStyle="bold"
tools:text="Title" />
</LinearLayout>
<TextView
android:id="@+id/description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/adapt_sample_hash_width"
android:breakStrategy="simple"
android:hyphenationFrequency="none"
android:textAppearance="?android:attr/textAppearance"
tools:text="Description goes here" />
<io.noties.markwon.app.base.FlowLayout
android:id="@+id/artifacts"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/adapt_sample_hash_width"
android:layout_marginTop="4dip"
app:fl_spacing="@dimen/content_padding"
app:fl_spacingVertical="2dip">
<!-- 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.base.FlowLayout
android:id="@+id/tags"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/adapt_sample_hash_width"
android:layout_marginTop="4dip"
android:layout_marginBottom="4dip"
app:fl_spacing="@dimen/content_padding"
app:fl_spacingVertical="2dip">
<include layout="@layout/view_tag" />
</io.noties.markwon.app.base.FlowLayout>
</LinearLayout> </LinearLayout>
<TextView </androidx.cardview.widget.CardView>
android:id="@+id/description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/adapt_sample_hash_width"
android:textAppearance="?android:attr/textAppearanceMedium"
tools:text="Description goes here" />
<TextView
android:id="@+id/artifacts_and_tags"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/adapt_sample_hash_width"
tools:text="recycler-view adapt another" />
</LinearLayout>

View File

@ -7,25 +7,39 @@
android:orientation="vertical"> android:orientation="vertical">
<LinearLayout <LinearLayout
android:id="@+id/app_bar"
style="@style/AppBarContainer" style="@style/AppBarContainer"
android:elevation="4dip" android:elevation="4dip"
android:orientation="horizontal"> android:orientation="horizontal"
tools:ignore="UseCompoundDrawables">
<ImageView <ImageView
android:id="@+id/app_bar_icon"
style="@style/AppBarIcon" style="@style/AppBarIcon"
android:contentDescription="@null" android:contentDescription="@null"
android:padding="8dip" android:padding="8dip"
android:src="@mipmap/ic_launcher" /> android:src="@mipmap/ic_launcher" />
<TextView <FrameLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_marginEnd="@dimen/app_bar_height" android:layout_marginEnd="@dimen/app_bar_height">
android:gravity="center"
android:text="@string/app_name" <TextView
android:textAppearance="?android:attr/textAppearanceLarge" android:id="@+id/app_bar_title"
android:textColor="@color/white" android:layout_width="wrap_content"
android:textStyle="bold" /> android:layout_height="wrap_content"
android:layout_gravity="center"
android:paddingLeft="@dimen/content_padding_double"
android:paddingTop="@dimen/content_padding_half"
android:paddingRight="@dimen/content_padding_double"
android:paddingBottom="@dimen/content_padding_half"
android:text="@string/app_name"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textColor="@color/white"
android:textStyle="bold" />
</FrameLayout>
</LinearLayout> </LinearLayout>
@ -37,13 +51,18 @@
android:id="@+id/recycler_view" android:id="@+id/recycler_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:translationY="56dip" /> android:clipChildren="false"
android:clipToPadding="false"
android:overScrollMode="never"
android:paddingBottom="36dip"
tools:layout_marginTop="56dip" />
<io.noties.markwon.app.base.SearchBar <io.noties.markwon.app.base.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"
android:animateLayoutChanges="true" android:animateLayoutChanges="true"
android:background="@color/search_bar_background_full"
android:padding="@dimen/content_padding" /> android:padding="@dimen/content_padding" />
</FrameLayout> </FrameLayout>

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/scroll_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/content_padding"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="?android:attr/textColorPrimary"
tools:text="Hello there" />
</FrameLayout>
</ScrollView>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
style="@style/ArtifactTagText"
android:background="@drawable/bg_artifact"
tools:text="core" />

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android" <merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
tools:background="#eee" tools:background="#fff"
tools:gravity="center_vertical" tools:gravity="center_vertical"
tools:layout_height="wrap_content" tools:layout_height="wrap_content"
tools:layout_widht="match_parent" tools:layout_widht="match_parent"
@ -13,7 +13,8 @@
android:layout_width="0px" android:layout_width="0px"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:background="@color/white" android:animateLayoutChanges="true"
android:background="@drawable/bg_search_bar"
android:paddingTop="@dimen/content_padding" android:paddingTop="@dimen/content_padding"
android:paddingBottom="@dimen/content_padding"> android:paddingBottom="@dimen/content_padding">
@ -31,10 +32,10 @@
<ImageView <ImageView
android:layout_width="@dimen/search_bar_icon_side" android:layout_width="@dimen/search_bar_icon_side"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:contentDescription="@null" android:contentDescription="@null"
android:scaleType="centerInside" android:scaleType="centerInside"
android:src="@drawable/ic_search_white_24dp" android:src="@drawable/ic_search_white_24dp"
android:layout_gravity="center_vertical"
android:tint="@color/gray" /> android:tint="@color/gray" />
<ImageView <ImageView

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
style="@style/ArtifactTagText"
android:background="@drawable/bg_tag"
tools:text="recycler-view" />

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="FlowLayout">
<attr name="fl_spacing" format="dimension" />
<attr name="fl_spacingVertical" format="dimension" />
<attr name="fl_spacingHorizontal" format="dimension" />
</declare-styleable>
</resources>

View File

@ -6,6 +6,10 @@
<color name="gray_light">#999999</color> <color name="gray_light">#999999</color>
<color name="white">#FFFFFF</color> <color name="white">#FFFFFF</color>
<color name="window_background">#EEEEEE</color> <color name="window_background">@color/white</color>
<color name="red">#FF0000</color> <color name="red">#FF0000</color>
<color name="search_bar_background">#eee</color>
<color name="search_bar_background_full">#BFFFFFFF</color>
</resources> </resources>

View File

@ -4,6 +4,7 @@
<dimen name="content_padding">8dip</dimen> <dimen name="content_padding">8dip</dimen>
<dimen name="content_padding_double">16dip</dimen> <dimen name="content_padding_double">16dip</dimen>
<dimen name="content_padding_half">4dip</dimen>
<dimen name="search_bar_icon_side">36dip</dimen> <dimen name="search_bar_icon_side">36dip</dimen>
<dimen name="adapt_sample_hash_width">36dip</dimen> <dimen name="adapt_sample_hash_width">36dip</dimen>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item name="text" type="id" />
</resources>

View File

@ -1,11 +1,12 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources xmlns:tools="http://schemas.android.com/tools">
<style name="AppThemeBase" parent="android:Theme.Material.Light.NoActionBar"> <style name="AppThemeBase" parent="android:Theme.Material.Light.NoActionBar">
<item name="android:colorAccent">@color/accent</item> <item name="android:colorAccent">@color/accent</item>
<item name="android:colorPrimary">@color/gray_light</item> <item name="android:colorPrimary">@color/gray_light</item>
<item name="android:colorPrimaryDark">@color/gray</item> <item name="android:colorPrimaryDark">@color/gray</item>
<item name="android:windowBackground">@color/window_background</item> <item name="android:windowBackground">@color/window_background</item>
<item name="android:windowSplashscreenContent" tools:ignore="NewApi">@drawable/bg_splash</item>
</style> </style>
<style name="AppTheme" parent="AppThemeBase" /> <style name="AppTheme" parent="AppThemeBase" />
@ -23,4 +24,19 @@
<item name="android:background">?android:attr/selectableItemBackgroundBorderless</item> <item name="android:background">?android:attr/selectableItemBackgroundBorderless</item>
</style> </style>
<style name="ArtifactTagText">
<item name="android:id">@id/text</item>
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:lines">1</item>
<item name="android:singleLine">true</item>
<item name="android:maxLines">1</item>
<item name="android:paddingTop">@dimen/content_padding_half</item>
<item name="android:paddingStart">@dimen/content_padding</item>
<item name="android:paddingEnd">@dimen/content_padding</item>
<item name="android:paddingBottom">@dimen/content_padding_half</item>
<item name="android:textAppearance">?android:attr/textAppearance</item>
<item name="android:textColor">@color/white</item>
</style>
</resources> </resources>

View File

@ -6,7 +6,7 @@ buildscript {
} }
dependencies { dependencies {
// on `3.5.3` tests are not run from CLI // on `3.5.3` tests are not run from CLI
classpath 'com.android.tools.build:gradle:3.5.2' classpath 'com.android.tools.build:gradle:4.0.0'
classpath 'com.github.ben-manes:gradle-versions-plugin:0.27.0' classpath 'com.github.ben-manes:gradle-versions-plugin:0.27.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
} }
@ -69,6 +69,8 @@ ext {
'x-recycler-view' : 'androidx.recyclerview:recyclerview:1.0.0', 'x-recycler-view' : 'androidx.recyclerview:recyclerview:1.0.0',
'x-core' : 'androidx.core:core:1.0.2', 'x-core' : 'androidx.core:core:1.0.2',
'x-appcompat' : 'androidx.appcompat:appcompat:1.1.0', 'x-appcompat' : 'androidx.appcompat:appcompat:1.1.0',
'x-cardview' : 'androidx.cardview:cardview:1.0.0',
'x-fragment' : 'androidx.fragment:fragment:1.0.0',
'commonmark' : "com.atlassian.commonmark:commonmark:$commonMarkVersion", 'commonmark' : "com.atlassian.commonmark:commonmark:$commonMarkVersion",
'commonmark-strikethrough': "com.atlassian.commonmark:commonmark-ext-gfm-strikethrough:$commonMarkVersion", 'commonmark-strikethrough': "com.atlassian.commonmark:commonmark-ext-gfm-strikethrough:$commonMarkVersion",
'commonmark-table' : "com.atlassian.commonmark:commonmark-ext-gfm-tables:$commonMarkVersion", 'commonmark-table' : "com.atlassian.commonmark:commonmark-ext-gfm-tables:$commonMarkVersion",

View File

@ -1,5 +1,6 @@
#Wed Jun 17 17:05:04 MSK 2020
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.5.1-all.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip

View File

@ -1,5 +1,9 @@
package io.noties.markwon.sample.annotations; package io.noties.markwon.sample.annotations;
import androidx.annotation.NonNull;
import java.util.Locale;
public enum MarkwonArtifact { public enum MarkwonArtifact {
CORE, CORE,
EDITOR, EDITOR,
@ -17,5 +21,10 @@ public enum MarkwonArtifact {
RECYCLER, RECYCLER,
RECYCLER_TABLE, RECYCLER_TABLE,
SIMPLE_EXT, SIMPLE_EXT,
SYNTAX_HIGHLIGHT SYNTAX_HIGHLIGHT;
@NonNull
public String artifactName() {
return name().toLowerCase(Locale.US).replace('_', '-');
}
} }