diff --git a/app-sample/build.gradle b/app-sample/build.gradle index 6947663f..5883ac72 100644 --- a/app-sample/build.gradle +++ b/app-sample/build.gradle @@ -43,6 +43,11 @@ kapt { dependencies { kapt project(':sample-utils:processor') - implementation 'io.noties:debug:5.1.0' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + + deps.with { + api it['x-recycler-view'] + api it['adapt'] + api it['debug'] + } } diff --git a/app-sample/samples.json b/app-sample/samples.json index 0637a088..e1b01528 100644 --- a/app-sample/samples.json +++ b/app-sample/samples.json @@ -1 +1,14 @@ -[] \ No newline at end of file +[ + { + "javaClassName": "io.noties.markwon.app.samples.FirstSample", + "id": "202006164150023", + "title": "First Sample", + "description": "This **is** _the first_ sample", + "artifacts": [ + "CORE" + ], + "tags": [ + "test" + ] + } +] \ No newline at end of file diff --git a/app-sample/src/main/java/io/noties/markwon/app/MainActivity.kt b/app-sample/src/main/java/io/noties/markwon/app/MainActivity.kt index be29cd6b..c265ce04 100644 --- a/app-sample/src/main/java/io/noties/markwon/app/MainActivity.kt +++ b/app-sample/src/main/java/io/noties/markwon/app/MainActivity.kt @@ -2,13 +2,60 @@ package io.noties.markwon.app import android.app.Activity import android.os.Bundle +import android.view.ViewTreeObserver +import androidx.recyclerview.widget.DefaultItemAnimator +import androidx.recyclerview.widget.LinearLayoutManager +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() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) - /**/ + val searchBar: SearchBar = findViewById(R.id.search_bar) + searchBar.onSearchListener = { + Debug.i("search: '$it'") + } + 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) }) } } \ No newline at end of file diff --git a/app-sample/src/main/java/io/noties/markwon/app/MarkwonSample.kt b/app-sample/src/main/java/io/noties/markwon/app/MarkwonSample.kt new file mode 100644 index 00000000..ed4f4d43 --- /dev/null +++ b/app-sample/src/main/java/io/noties/markwon/app/MarkwonSample.kt @@ -0,0 +1,5 @@ +package io.noties.markwon.app + +abstract class MarkwonSample { + +} \ No newline at end of file diff --git a/app-sample/src/main/java/io/noties/markwon/app/MarkwonSampleItem.kt b/app-sample/src/main/java/io/noties/markwon/app/MarkwonSampleItem.kt new file mode 100644 index 00000000..29942fba --- /dev/null +++ b/app-sample/src/main/java/io/noties/markwon/app/MarkwonSampleItem.kt @@ -0,0 +1,12 @@ +package io.noties.markwon.app + +import io.noties.markwon.sample.annotations.MarkwonArtifact + +data class MarkwonSampleItem( + val javaClassName: String, + val id: String, + val title: String, + val description: String, + val artifacts: List, + val tags: List +) \ No newline at end of file diff --git a/app-sample/src/main/java/io/noties/markwon/app/adapt/ArtifactItem.kt b/app-sample/src/main/java/io/noties/markwon/app/adapt/ArtifactItem.kt new file mode 100644 index 00000000..1dcfe5c0 --- /dev/null +++ b/app-sample/src/main/java/io/noties/markwon/app/adapt/ArtifactItem.kt @@ -0,0 +1,22 @@ +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(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) + } +} \ No newline at end of file diff --git a/app-sample/src/main/java/io/noties/markwon/app/adapt/SampleItem.kt b/app-sample/src/main/java/io/noties/markwon/app/adapt/SampleItem.kt new file mode 100644 index 00000000..9aa75ff9 --- /dev/null +++ b/app-sample/src/main/java/io/noties/markwon/app/adapt/SampleItem.kt @@ -0,0 +1,89 @@ +package io.noties.markwon.app.adapt + +import android.graphics.Color +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.View +import android.view.ViewGroup +import android.widget.TextView +import io.noties.adapt.Item +import io.noties.debug.Debug +import io.noties.markwon.app.MarkwonSampleItem +import io.noties.markwon.app.R +import io.noties.markwon.sample.annotations.MarkwonArtifact + +class SampleItem(private val item: MarkwonSampleItem) : Item(item.id.hashCode().toLong()) { + + var search: String? = null + + override fun createHolder(inflater: LayoutInflater, parent: ViewGroup): Holder { + val holder = Holder(inflater.inflate(R.layout.adapt_sample, parent, false)) + holder.artifactsAndTags.movementMethod = LinkMovementMethod.getInstance() + return holder + } + + override fun render(holder: Holder) { + holder.apply { + title.text = item.title + description.text = item.description + artifactsAndTags.text = buildArtifactsAndTags + } + } + + private val buildArtifactsAndTags: CharSequence + get() { + val builder = SpannableStringBuilder() + + item.artifacts + .forEach { + val length = builder.length + 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 + .forEach { + val length = builder.length + builder.append("\u00a0$it\u00a0") + builder.setSpan(TagSpan(it), length, builder.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + } + + return builder + } + + class Holder(itemView: View) : Item.Holder(itemView) { + val title: TextView = requireView(R.id.title) + val description: TextView = requireView(R.id.description) + val artifactsAndTags: TextView = requireView(R.id.artifacts_and_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 + } + } +} \ No newline at end of file diff --git a/app-sample/src/main/java/io/noties/markwon/app/base/FlowLayout.kt b/app-sample/src/main/java/io/noties/markwon/app/base/FlowLayout.kt new file mode 100644 index 00000000..6aa1ad10 --- /dev/null +++ b/app-sample/src/main/java/io/noties/markwon/app/base/FlowLayout.kt @@ -0,0 +1,52 @@ +package io.noties.markwon.app.base + +import android.content.Context +import android.util.AttributeSet +import android.view.ViewGroup + +class FlowLayout(context: Context, attrs: AttributeSet) : ViewGroup(context, attrs) { + override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { + for (i in 0 until childCount) { + val child = getChildAt(i) + val params = child.layoutParams as LayoutParams + val left = paddingLeft + params.x + val top = paddingTop + params.y + child.layout( + left, + top, + left + child.measuredWidth, + top + child.measuredHeight + ) + } + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + } + + override fun generateDefaultLayoutParams(): ViewGroup.LayoutParams { + return LayoutParams() + } + + override fun checkLayoutParams(p: ViewGroup.LayoutParams?): Boolean { + return p is LayoutParams + } + + override fun generateLayoutParams(attrs: AttributeSet): ViewGroup.LayoutParams { + return LayoutParams(context, attrs) + } + + override fun generateLayoutParams(p: ViewGroup.LayoutParams): ViewGroup.LayoutParams { + return LayoutParams(p) + } + + class LayoutParams : ViewGroup.LayoutParams { + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) + constructor(width: Int, height: Int) : super(width, height) + constructor(params: ViewGroup.LayoutParams) : super(params) + constructor() : this(WRAP_CONTENT, WRAP_CONTENT) + + var x: Int = 0 + var y: Int = 0 + } +} \ No newline at end of file diff --git a/app-sample/src/main/java/io/noties/markwon/app/base/SearchBar.kt b/app-sample/src/main/java/io/noties/markwon/app/base/SearchBar.kt new file mode 100644 index 00000000..6ab10838 --- /dev/null +++ b/app-sample/src/main/java/io/noties/markwon/app/base/SearchBar.kt @@ -0,0 +1,79 @@ +package io.noties.markwon.app.base + +import android.content.Context +import android.util.AttributeSet +import android.view.Gravity +import android.view.View +import android.widget.LinearLayout +import io.noties.markwon.app.R +import io.noties.markwon.app.utils.KeyEventUtils +import io.noties.markwon.app.utils.KeyboardUtils +import io.noties.markwon.app.utils.TextWatcherAdapter +import io.noties.markwon.app.utils.hidden + + +class SearchBar(context: Context, attrs: AttributeSet?) : LinearLayout(context, attrs) { + + private val focus: View + private val textField: TextField + private val clear: View + private val cancel: View + + var onSearchListener: ((String?) -> Unit)? = null + + init { + orientation = HORIZONTAL + gravity = Gravity.CENTER_VERTICAL + + View.inflate(context, R.layout.view_search_bar, this) + + focus = findViewById(R.id.focus) + textField = findViewById(R.id.text_field) + clear = findViewById(R.id.clear) + cancel = findViewById(R.id.cancel) + + // listen for text state + textField.addTextChangedListener(TextWatcherAdapter.afterTextChanged { + textFieldChanged(it) + }) + + fun looseFocus() { + KeyboardUtils.hide(textField) + focus.requestFocus() + } + + // on back pressed - lose focus and hide keyboard + textField.onBackPressedListener = { + // hide keyboard and lose focus + looseFocus() + } + + textField.setOnFocusChangeListener { _, hasFocus -> + cancel.hidden = textField.text.isEmpty() && !hasFocus + } + + textField.setOnEditorActionListener { _, _, event -> + if (KeyEventUtils.isActionUp(event)) { + looseFocus() + } + return@setOnEditorActionListener true + } + + clear.setOnClickListener { + textField.setText("") + } + + cancel.setOnClickListener { + textField.setText("") + looseFocus() + } + } + + private fun textFieldChanged(text: CharSequence) { + val isEmpty = text.isEmpty() + clear.hidden = isEmpty + cancel.hidden = isEmpty && !textField.hasFocus() + + onSearchListener?.invoke(if (text.isEmpty()) null else text.toString()) + } +} \ No newline at end of file diff --git a/app-sample/src/main/java/io/noties/markwon/app/base/TextField.kt b/app-sample/src/main/java/io/noties/markwon/app/base/TextField.kt new file mode 100644 index 00000000..0e826282 --- /dev/null +++ b/app-sample/src/main/java/io/noties/markwon/app/base/TextField.kt @@ -0,0 +1,29 @@ +package io.noties.markwon.app.base + +import android.content.Context +import android.util.AttributeSet +import android.view.KeyEvent +import android.widget.EditText +import io.noties.markwon.app.utils.KeyEventUtils + +class TextField(context: Context, attrs: AttributeSet?) : EditText(context, attrs) { + var onBackPressedListener: (() -> Unit)? = null + + override fun onDetachedFromWindow() { + onBackPressedListener = null + super.onDetachedFromWindow() + } + + override fun onKeyPreIme(keyCode: Int, event: KeyEvent?): Boolean { + if (isAttachedToWindow) { + onBackPressedListener?.also { listener -> + if (hasFocus() + && KeyEvent.KEYCODE_BACK == keyCode + && KeyEventUtils.isActionUp(event)) { + listener() + } + } + } + return super.onKeyPreIme(keyCode, event) + } +} \ No newline at end of file diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/FirstSample.kt b/app-sample/src/main/java/io/noties/markwon/app/samples/FirstSample.kt new file mode 100644 index 00000000..20b65e25 --- /dev/null +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/FirstSample.kt @@ -0,0 +1,15 @@ +package io.noties.markwon.app.samples + +import io.noties.markwon.app.MarkwonSample +import io.noties.markwon.sample.annotations.MarkwonArtifact +import io.noties.markwon.sample.annotations.MarkwonSampleInfo + +@MarkwonSampleInfo( + id = "202006164150023", + title = "First Sample", + description = "This **is** _the first_ sample", + artifacts = [MarkwonArtifact.CORE], + tags = ["test"] +) +class FirstSample : MarkwonSample() { +} \ No newline at end of file diff --git a/app-sample/src/main/java/io/noties/markwon/app/utils/KeyEventUtils.kt b/app-sample/src/main/java/io/noties/markwon/app/utils/KeyEventUtils.kt new file mode 100644 index 00000000..ea3a3bd8 --- /dev/null +++ b/app-sample/src/main/java/io/noties/markwon/app/utils/KeyEventUtils.kt @@ -0,0 +1,9 @@ +package io.noties.markwon.app.utils + +import android.view.KeyEvent + +object KeyEventUtils { + fun isActionUp(event: KeyEvent?): Boolean { + return event == null || KeyEvent.ACTION_UP == event.action + } +} \ No newline at end of file diff --git a/app-sample/src/main/java/io/noties/markwon/app/utils/KeyboardUtils.kt b/app-sample/src/main/java/io/noties/markwon/app/utils/KeyboardUtils.kt new file mode 100644 index 00000000..1bcdfa73 --- /dev/null +++ b/app-sample/src/main/java/io/noties/markwon/app/utils/KeyboardUtils.kt @@ -0,0 +1,17 @@ +package io.noties.markwon.app.utils + +import android.view.View +import android.view.inputmethod.InputMethodManager + +object KeyboardUtils { + + fun show(view: View) { + view.context.getSystemService(InputMethodManager::class.java) + ?.showSoftInput(view, 0) + } + + fun hide(view: View) { + view.context.getSystemService(InputMethodManager::class.java) + ?.hideSoftInputFromWindow(view.windowToken, 0) + } +} \ No newline at end of file diff --git a/app-sample/src/main/java/io/noties/markwon/app/utils/TextWatcherAdapter.java b/app-sample/src/main/java/io/noties/markwon/app/utils/TextWatcherAdapter.java new file mode 100644 index 00000000..143fb964 --- /dev/null +++ b/app-sample/src/main/java/io/noties/markwon/app/utils/TextWatcherAdapter.java @@ -0,0 +1,37 @@ +package io.noties.markwon.app.utils; + +import android.text.Editable; +import android.text.TextWatcher; + +import androidx.annotation.NonNull; + +public abstract class TextWatcherAdapter implements TextWatcher { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + + } + + @Override + public void afterTextChanged(Editable s) { + + } + + public interface AfterTextChanged { + void afterTextChanged(Editable s); + } + + @NonNull + public static TextWatcher afterTextChanged(@NonNull AfterTextChanged afterTextChanged) { + return new TextWatcherAdapter() { + @Override + public void afterTextChanged(Editable s) { + afterTextChanged.afterTextChanged(s); + } + }; + } +} diff --git a/app-sample/src/main/java/io/noties/markwon/app/utils/ViewUtils.kt b/app-sample/src/main/java/io/noties/markwon/app/utils/ViewUtils.kt new file mode 100644 index 00000000..497ba370 --- /dev/null +++ b/app-sample/src/main/java/io/noties/markwon/app/utils/ViewUtils.kt @@ -0,0 +1,11 @@ +package io.noties.markwon.app.utils + +import android.view.View +import android.view.View.GONE +import android.view.View.VISIBLE + +var View.hidden: Boolean + get() = visibility == GONE + set(value) { + visibility = if (value) GONE else VISIBLE + } \ No newline at end of file diff --git a/app-sample/src/main/res/drawable/bg_artifact.xml b/app-sample/src/main/res/drawable/bg_artifact.xml new file mode 100644 index 00000000..b039c039 --- /dev/null +++ b/app-sample/src/main/res/drawable/bg_artifact.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app-sample/src/main/res/drawable/bg_tag.xml b/app-sample/src/main/res/drawable/bg_tag.xml new file mode 100644 index 00000000..3e246adb --- /dev/null +++ b/app-sample/src/main/res/drawable/bg_tag.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app-sample/src/main/res/drawable/ic_close_white_24dp.xml b/app-sample/src/main/res/drawable/ic_close_white_24dp.xml new file mode 100644 index 00000000..1544037f --- /dev/null +++ b/app-sample/src/main/res/drawable/ic_close_white_24dp.xml @@ -0,0 +1,10 @@ + + + diff --git a/app-sample/src/main/res/drawable/ic_search_white_24dp.xml b/app-sample/src/main/res/drawable/ic_search_white_24dp.xml new file mode 100644 index 00000000..4d0f1858 --- /dev/null +++ b/app-sample/src/main/res/drawable/ic_search_white_24dp.xml @@ -0,0 +1,10 @@ + + + diff --git a/app-sample/src/main/res/layout/activity_main.xml b/app-sample/src/main/res/layout/activity_main.xml new file mode 100644 index 00000000..9a866cab --- /dev/null +++ b/app-sample/src/main/res/layout/activity_main.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app-sample/src/main/res/layout/adapt_artifact.xml b/app-sample/src/main/res/layout/adapt_artifact.xml new file mode 100644 index 00000000..27d5d894 --- /dev/null +++ b/app-sample/src/main/res/layout/adapt_artifact.xml @@ -0,0 +1,17 @@ + + \ No newline at end of file diff --git a/app-sample/src/main/res/layout/adapt_sample.xml b/app-sample/src/main/res/layout/adapt_sample.xml new file mode 100644 index 00000000..8499e6a7 --- /dev/null +++ b/app-sample/src/main/res/layout/adapt_sample.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app-sample/src/main/res/layout/view_search_bar.xml b/app-sample/src/main/res/layout/view_search_bar.xml new file mode 100644 index 00000000..de614f06 --- /dev/null +++ b/app-sample/src/main/res/layout/view_search_bar.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app-sample/src/main/res/values/colors.xml b/app-sample/src/main/res/values/colors.xml index d9cb06d5..7f60f058 100644 --- a/app-sample/src/main/res/values/colors.xml +++ b/app-sample/src/main/res/values/colors.xml @@ -1,4 +1,11 @@ - #5CB85C + #009900 + + #777777 + #999999 + + #FFFFFF + #EEEEEE + #FF0000 \ No newline at end of file diff --git a/app-sample/src/main/res/values/dimens.xml b/app-sample/src/main/res/values/dimens.xml new file mode 100644 index 00000000..c4f1ce17 --- /dev/null +++ b/app-sample/src/main/res/values/dimens.xml @@ -0,0 +1,10 @@ + + + 56dip + + 8dip + 16dip + + 36dip + 36dip + \ No newline at end of file diff --git a/app-sample/src/main/res/values/styles.xml b/app-sample/src/main/res/values/styles.xml index 955c5cc3..19eec27c 100644 --- a/app-sample/src/main/res/values/styles.xml +++ b/app-sample/src/main/res/values/styles.xml @@ -1,8 +1,26 @@ - + + + \ No newline at end of file diff --git a/art/ic_hash_white_24dp.svg b/art/ic_hash_white_24dp.svg new file mode 100644 index 00000000..a0c06ae2 --- /dev/null +++ b/art/ic_hash_white_24dp.svg @@ -0,0 +1,64 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/art/sample-app.epgz b/art/sample-app.epgz index f3f37831..10314e1f 100644 Binary files a/art/sample-app.epgz and b/art/sample-app.epgz differ diff --git a/build.gradle b/build.gradle index 4e594c90..8d0b833d 100644 --- a/build.gradle +++ b/build.gradle @@ -77,8 +77,8 @@ ext { 'jlatexmath-android' : 'ru.noties:jlatexmath-android:0.2.0', 'okhttp' : 'com.squareup.okhttp3:okhttp:3.9.0', 'prism4j' : 'io.noties:prism4j:2.0.0', - 'debug' : 'io.noties:debug:5.0.0@jar', - 'adapt' : 'io.noties:adapt:2.0.0', + 'debug' : 'io.noties:debug:5.1.0', + 'adapt' : 'io.noties:adapt:2.2.0', 'dagger' : "com.google.dagger:dagger:$daggerVersion", 'picasso' : 'com.squareup.picasso:picasso:2.71828', 'glide' : 'com.github.bumptech.glide:glide:4.9.0',