Sample app, bind list results, search
This commit is contained in:
parent
7e8ed3ea0b
commit
66f77f35fe
@ -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']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
166
app-sample/src/debug/res/layout/flowlayout_preview.xml
Normal file
166
app-sample/src/debug/res/layout/flowlayout_preview.xml
Normal 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>
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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) })
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,5 +0,0 @@
|
|||||||
package io.noties.markwon.app
|
|
||||||
|
|
||||||
abstract class MarkwonSample {
|
|
||||||
|
|
||||||
}
|
|
@ -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
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
@ -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})"
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -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() {
|
private fun FlowLayout.ensure(viewsCount: Int, layoutResId: Int): List<View> {
|
||||||
override fun onClick(widget: View) {
|
if (viewsCount > childCount) {
|
||||||
Debug.i("clicked artifact: $artifact")
|
// inflate new views
|
||||||
|
val inflater = LayoutInflater.from(context)
|
||||||
|
for (i in 0 until (viewsCount - childCount)) {
|
||||||
|
addView(inflater.inflate(layoutResId, this, false))
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
override fun updateDrawState(ds: TextPaint) {
|
// return requested vies and GONE the rest
|
||||||
ds.isUnderlineText = false
|
for (i in viewsCount until childCount) {
|
||||||
ds.bgColor = Color.GREEN
|
getChildAt(i).hidden = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return (0 until viewsCount).map { getChildAt(it) }
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
package io.noties.markwon.app.ui
|
||||||
|
|
||||||
|
abstract class MarkwonSample {
|
||||||
|
|
||||||
|
}
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package io.noties.markwon.app.utils
|
||||||
|
|
||||||
|
interface Cancellable {
|
||||||
|
val isCancelled: Boolean
|
||||||
|
|
||||||
|
fun cancel()
|
||||||
|
}
|
@ -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() {
|
||||||
|
}
|
||||||
|
}
|
@ -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"
|
@ -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
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
13
app-sample/src/main/res/anim/screen_in.xml
Normal file
13
app-sample/src/main/res/anim/screen_in.xml
Normal 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>
|
13
app-sample/src/main/res/anim/screen_in_pop.xml
Normal file
13
app-sample/src/main/res/anim/screen_in_pop.xml
Normal 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>
|
13
app-sample/src/main/res/anim/screen_out.xml
Normal file
13
app-sample/src/main/res/anim/screen_out.xml
Normal 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>
|
13
app-sample/src/main/res/anim/screen_out_pop.xml
Normal file
13
app-sample/src/main/res/anim/screen_out_pop.xml
Normal 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>
|
5
app-sample/src/main/res/drawable/bg_search_bar.xml
Normal file
5
app-sample/src/main/res/drawable/bg_search_bar.xml
Normal 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>
|
14
app-sample/src/main/res/drawable/bg_splash.xml
Normal file
14
app-sample/src/main/res/drawable/bg_splash.xml
Normal 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>
|
@ -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>
|
@ -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" />
|
|
@ -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>
|
|
||||||
|
@ -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>
|
23
app-sample/src/main/res/layout/sample_text_view.xml
Normal file
23
app-sample/src/main/res/layout/sample_text_view.xml
Normal 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>
|
6
app-sample/src/main/res/layout/view_artifact.xml
Normal file
6
app-sample/src/main/res/layout/view_artifact.xml
Normal 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" />
|
@ -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
|
||||||
|
6
app-sample/src/main/res/layout/view_tag.xml
Normal file
6
app-sample/src/main/res/layout/view_tag.xml
Normal 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" />
|
10
app-sample/src/main/res/values/attrs.xml
Normal file
10
app-sample/src/main/res/values/attrs.xml
Normal 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>
|
@ -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>
|
@ -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>
|
||||||
|
4
app-sample/src/main/res/values/ids.xml
Normal file
4
app-sample/src/main/res/values/ids.xml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<item name="text" type="id" />
|
||||||
|
</resources>
|
@ -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>
|
@ -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",
|
||||||
|
3
gradle/wrapper/gradle-wrapper.properties
vendored
3
gradle/wrapper/gradle-wrapper.properties
vendored
@ -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
|
||||||
|
@ -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('_', '-');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user