aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAllan Wang <me@allanwang.ca>2017-06-24 19:09:54 -0700
committerAllan Wang <me@allanwang.ca>2017-06-24 19:09:54 -0700
commit6ad176dde0a84a0eb96dea2f9c7eb34394045526 (patch)
tree17859e9be76430d4489ae008de8428c11e14f9ec
parentb536b151f1012e730782f615dceed6be7e3a9652 (diff)
downloadkau-6ad176dde0a84a0eb96dea2f9c7eb34394045526.tar.gz
kau-6ad176dde0a84a0eb96dea2f9c7eb34394045526.tar.bz2
kau-6ad176dde0a84a0eb96dea2f9c7eb34394045526.zip
Push working build
-rw-r--r--gradle.properties3
-rw-r--r--library/build.gradle5
-rw-r--r--library/src/main/kotlin/ca/allanwang/kau/searchview/SearchItem.kt16
-rw-r--r--library/src/main/kotlin/ca/allanwang/kau/searchview/SearchView.kt138
-rw-r--r--library/src/main/kotlin/ca/allanwang/kau/utils/ActivityUtils.kt4
-rw-r--r--library/src/main/kotlin/ca/allanwang/kau/utils/TransitionUtils.kt19
-rw-r--r--library/src/main/kotlin/ca/allanwang/kau/utils/ViewUtils.kt14
-rw-r--r--library/src/main/res/layout/kau_search_view.xml4
-rw-r--r--library/src/main/res/transition/kau_auto.xml4
-rw-r--r--library/src/main/res/values/strings_commons.xml1
-rw-r--r--sample/src/main/kotlin/ca/allanwang/kau/sample/MainActivity.kt22
11 files changed, 181 insertions, 49 deletions
diff --git a/gradle.properties b/gradle.properties
index 504fd7a..aa9e621 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -32,4 +32,7 @@ CONSTRAINT_LAYOUT=1.1.0-beta1
FAST_ADAPTER=2.6.2
FAST_ADAPTER_COMMONS=2.6.0
ANKO=0.10.1
+RX_JAVA=2.1.0
+RX_ANDROID=2.0.1
+RX_BINDING=2.0.0
diff --git a/library/build.gradle b/library/build.gradle
index 3498378..946dbe2 100644
--- a/library/build.gradle
+++ b/library/build.gradle
@@ -62,6 +62,11 @@ dependencies {
compile "com.jakewharton.timber:timber:${TIMBER}"
compile "org.jetbrains.anko:anko-commons:${ANKO}"
+
+ compile "io.reactivex.rxjava2:rxkotlin:${RX_JAVA}"
+ compile "io.reactivex.rxjava2:rxandroid:${RX_ANDROID}"
+ compile "com.jakewharton.rxbinding2:rxbinding-kotlin:${RX_BINDING}"
+ compile "com.jakewharton.rxbinding2:rxbinding-appcompat-v7-kotlin:${RX_BINDING}"
}
// build a jar with source files
diff --git a/library/src/main/kotlin/ca/allanwang/kau/searchview/SearchItem.kt b/library/src/main/kotlin/ca/allanwang/kau/searchview/SearchItem.kt
index 0b36a5d..fac5ca1 100644
--- a/library/src/main/kotlin/ca/allanwang/kau/searchview/SearchItem.kt
+++ b/library/src/main/kotlin/ca/allanwang/kau/searchview/SearchItem.kt
@@ -5,9 +5,11 @@ import android.view.View
import android.widget.ImageView
import android.widget.TextView
import ca.allanwang.kau.R
-import ca.allanwang.kau.utils.LazyResettable
import ca.allanwang.kau.utils.bindView
+import ca.allanwang.kau.utils.setIcon
import com.mikepenz.fastadapter.items.AbstractItem
+import com.mikepenz.google_material_typeface_library.GoogleMaterial
+import com.mikepenz.iconics.typeface.IIcon
/**
* Created by Allan Wang on 2017-06-23.
@@ -16,10 +18,11 @@ import com.mikepenz.fastadapter.items.AbstractItem
* Contains a [key] which acts as a unique identifier (eg url)
* and a [content] which is displayed in the item
*/
-class SearchItem(val key: String, val content: String = key) : AbstractItem<SearchItem, SearchItem.ViewHolder>() {
+class SearchItem(val key: String, val content: String = key, val iicon: IIcon? = GoogleMaterial.Icon.gmd_search
+) : AbstractItem<SearchItem, SearchItem.ViewHolder>() {
companion object {
- var foregroundColor: Int?=null
+ var foregroundColor: Int? = null
}
override fun getLayoutRes(): Int = R.layout.kau_search_item
@@ -32,7 +35,7 @@ class SearchItem(val key: String, val content: String = key) : AbstractItem<Sear
super.bindView(holder, payloads)
if (foregroundColor != null) {
holder.text.setTextColor(foregroundColor!!)
- holder.icon.drawable.setTint(foregroundColor!!)
+ holder.icon.setIcon(iicon, sizeDp = 18, color = foregroundColor!!)
}
holder.text.text = content
}
@@ -40,14 +43,11 @@ class SearchItem(val key: String, val content: String = key) : AbstractItem<Sear
override fun unbindView(holder: ViewHolder) {
super.unbindView(holder)
holder.text.text = null
+ holder.icon.setImageDrawable(null)
}
class ViewHolder(v: View) : RecyclerView.ViewHolder(v) {
val icon: ImageView by bindView(R.id.search_icon)
val text: TextView by bindView(R.id.search_text)
-
- init {
-
- }
}
} \ No newline at end of file
diff --git a/library/src/main/kotlin/ca/allanwang/kau/searchview/SearchView.kt b/library/src/main/kotlin/ca/allanwang/kau/searchview/SearchView.kt
index 4449fe5..6db75dc 100644
--- a/library/src/main/kotlin/ca/allanwang/kau/searchview/SearchView.kt
+++ b/library/src/main/kotlin/ca/allanwang/kau/searchview/SearchView.kt
@@ -1,8 +1,10 @@
package ca.allanwang.kau.searchview
import android.content.Context
+import android.content.res.ColorStateList
import android.support.annotation.ColorInt
import android.support.annotation.IdRes
+import android.support.transition.AutoTransition
import android.support.v7.widget.AppCompatEditText
import android.support.v7.widget.CardView
import android.support.v7.widget.LinearLayoutManager
@@ -13,15 +15,23 @@ import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.ProgressBar
import ca.allanwang.kau.R
-import ca.allanwang.kau.logging.KL
import ca.allanwang.kau.utils.*
+import com.jakewharton.rxbinding2.widget.RxTextView
import com.mikepenz.fastadapter.commons.adapters.FastItemAdapter
import com.mikepenz.google_material_typeface_library.GoogleMaterial
import com.mikepenz.iconics.typeface.IIcon
+import io.reactivex.Observable
+import io.reactivex.schedulers.Schedulers
+import org.jetbrains.anko.runOnUiThread
/**
* Created by Allan Wang on 2017-06-23.
+ *
+ * A materialized SearchView with complete theming and observables
+ *
+ * Huge thanks to @lapism for his base
+ * https://github.com/lapism/SearchView
*/
class SearchView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
@@ -60,36 +70,76 @@ class SearchView @JvmOverloads constructor(
if (value == null) iconClear.gone()
}
var revealDuration: Long = 300L
- var shouldClearOnOpen: Boolean = true
+ var transitionDuration: Long = 100L
+ var shouldClearOnClose: Boolean = true
var openListener: ((searchView: SearchView) -> Unit)? = null
var closeListener: ((searchView: SearchView) -> Unit)? = null
+ /**
+ * StringRes for a "no results found" item
+ * If [results] is ever set to an empty list, it will default to
+ * a list with one item with this string
+ *
+ * For simplicity, kau contains [R.string.kau_no_results_found]
+ * which you may use
+ */
+ var noResultsFound: Int = -1
+ /**
+ * Text watcher configurations on init
+ * By default, the observable is on a separate thread, so you may directly execute background processes
+ * This builder acts on an observable, so you may switch threads, debounce, and do anything else that you require
+ */
+ var textObserver: (observable: Observable<String>, searchView: SearchView) -> Unit = { _, _ -> }
}
+ /**
+ * Contract for adapter items
+ * Setting results will ensure that the values are sent on the UI thread
+ */
+ var results: List<SearchItem>
+ get() = adapter.adapterItems
+ set(value) = context.runOnUiThread {
+ cardTransition()
+ adapter.setNewList(
+ if (configs.noResultsFound > 0 && value.isEmpty())
+ listOf(SearchItem("", context.string(configs.noResultsFound), null))
+ else value)
+ }
+
+
+ fun clearResults() = context.runOnUiThread { adapter.clear() }
+
val configs = Configs()
//views
- val shadow: View by bindView(R.id.search_shadow)
- val card: CardView by bindView(R.id.search_cardview)
- val iconNav: ImageView by bindView(R.id.search_nav)
- val editText: AppCompatEditText by bindView(R.id.search_edit_text)
- val progress: ProgressBar by bindView(R.id.search_progress)
- val iconMic: ImageView by bindView(R.id.search_mic)
- val iconClear: ImageView by bindView(R.id.search_clear)
- val recycler: RecyclerView by bindView(R.id.search_recycler)
+ private val shadow: View by bindView(R.id.search_shadow)
+ private val card: CardView by bindView(R.id.search_cardview)
+ private val iconNav: ImageView by bindView(R.id.search_nav)
+ private val editText: AppCompatEditText by bindView(R.id.search_edit_text)
+ val textEvents: Observable<String>
+ private val progress: ProgressBar by bindView(R.id.search_progress)
+ private val iconMic: ImageView by bindView(R.id.search_mic)
+ private val iconClear: ImageView by bindView(R.id.search_clear)
+ private val recycler: RecyclerView by bindView(R.id.search_recycler)
val adapter = FastItemAdapter<SearchItem>()
lateinit var parent: ViewGroup
val isOpen: Boolean
get() = card.isVisible()
- //menu view
- var menuX: Int = -1
- var menuY: Int = -1
- var menuHalfHeight: Int = -1
+ /*
+ * Ripple start points and search view offset
+ * These are calculated every time the search view is opened,
+ * and can be overridden with the open listener if necessary
+ */
+ var menuX: Int = -1 //starting x for circular reveal
+ var menuY: Int = -1 //reference for cardview's marginTop
+ var menuHalfHeight: Int = -1 //starting y for circular reveal (relative to the cardview)
init {
View.inflate(context, R.layout.kau_search_view, this)
iconNav.setSearchIcon(configs.navIcon)
iconMic.setSearchIcon(configs.micIcon)
- iconClear.setSearchIcon(configs.clearIcon)
+ iconClear.setSearchIcon(configs.clearIcon).setOnClickListener {
+ editText.text.clear()
+ }
tintForeground(configs.foregroundColor)
tintBackground(configs.backgroundColor)
with(recycler) {
@@ -102,10 +152,21 @@ class SearchView @JvmOverloads constructor(
})
adapter = this@SearchView.adapter
}
+ textEvents = RxTextView.textChangeEvents(editText)
+ .skipInitialValue()
+ .observeOn(Schedulers.newThread())
+ .map { it.text().toString().trim() }
+ textEvents.filter { it.isBlank() }
+ .subscribe { clearResults() }
}
- internal fun ImageView.setSearchIcon(iicon: IIcon?) {
+ internal fun ImageView.setSearchIcon(iicon: IIcon?): ImageView {
setIcon(iicon, sizeDp = 18, color = configs.foregroundColor)
+ return this
+ }
+
+ internal fun cardTransition(builder: AutoTransition.() -> Unit = {}) {
+ card.transitionAuto { duration = configs.transitionDuration; builder() }
}
fun config(config: Configs.() -> Unit) {
@@ -114,6 +175,7 @@ class SearchView @JvmOverloads constructor(
fun bind(parent: ViewGroup, menu: Menu, @IdRes id: Int, config: Configs.() -> Unit = {}): SearchView {
config(config)
+ configs.textObserver(textEvents.filter { it.isNotBlank() }, this)
this.parent = parent
val item = menu.findItem(id)
if (item.icon == null) item.icon = GoogleMaterial.Icon.gmd_search.toDrawable(context, 20)
@@ -133,9 +195,6 @@ class SearchView @JvmOverloads constructor(
card.viewTreeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener {
override fun onPreDraw(): Boolean {
view.viewTreeObserver.removeOnPreDrawListener(this)
- with(card) {
- KL.e("S $width $measuredWidth $height $measuredHeight")
- }
val topAlignment = menuY - card.height / 2
val params = (card.layoutParams as MarginLayoutParams).apply {
topMargin = topAlignment
@@ -151,6 +210,8 @@ class SearchView @JvmOverloads constructor(
iconMic.drawable.setTint(color)
iconClear.drawable.setTint(color)
SearchItem.foregroundColor = color
+ editText.tint(color)
+ editText.setTextColor(ColorStateList.valueOf(color))
}
fun tintBackground(@ColorInt color: Int) {
@@ -164,25 +225,36 @@ class SearchView @JvmOverloads constructor(
* We therefore use half the menuItem height, which is a close approximation to our intended value
* The cardView matches the parent's width, so menuX is correct
*/
- card.circularReveal(menuX, menuHalfHeight, duration = configs.revealDuration,
- onStart = {
- configs.openListener?.invoke(this)
- if (configs.shouldClearOnOpen) editText.text.clear()
- },
- onFinish = {
- editText.requestFocus()
- shadow.fadeIn()
- })
+ configs.openListener?.invoke(this)
+ card.circularReveal(menuX, menuHalfHeight, duration = configs.revealDuration) {
+ editText.showKeyboard()
+ cardTransition()
+ recycler.visible()
+ shadow.fadeIn()
+ }
}
fun revealClose() {
if (!isOpen) return
- shadow.fadeOut() {
- card.circularHide(menuX, menuHalfHeight, duration = configs.revealDuration,
- onFinish = {
- configs.closeListener?.invoke(this)
- })
+ editText.hideKeyboard()
+ shadow.fadeOut(duration = configs.transitionDuration)
+ cardTransition {
+ addEndListener {
+ card.circularHide(menuX, menuHalfHeight, duration = configs.revealDuration,
+ onFinish = {
+ configs.closeListener?.invoke(this@SearchView)
+ if (configs.shouldClearOnClose) editText.text.clear()
+ recycler.gone()
+ })
+ }
}
+ recycler.gone()
+// card.circularHide(menuX, menuHalfHeight, offset = 100, duration = configs.revealDuration,
+// onFinish = {
+// configs.closeListener?.invoke(this)
+// if (configs.shouldClearOnClose) editText.text.clear()
+// recycler.gone()
+// })
}
}
diff --git a/library/src/main/kotlin/ca/allanwang/kau/utils/ActivityUtils.kt b/library/src/main/kotlin/ca/allanwang/kau/utils/ActivityUtils.kt
index 7a13685..f712337 100644
--- a/library/src/main/kotlin/ca/allanwang/kau/utils/ActivityUtils.kt
+++ b/library/src/main/kotlin/ca/allanwang/kau/utils/ActivityUtils.kt
@@ -55,4 +55,6 @@ fun Activity.setMenuIcons(menu: Menu, @ColorInt color: Int = Color.WHITE, vararg
}
}
-fun Activity.hideKeyboard() = currentFocus.hideKeyboard() \ No newline at end of file
+fun Activity.hideKeyboard() = currentFocus.hideKeyboard()
+
+fun Activity.showKeyboard() = currentFocus.showKeyboard() \ No newline at end of file
diff --git a/library/src/main/kotlin/ca/allanwang/kau/utils/TransitionUtils.kt b/library/src/main/kotlin/ca/allanwang/kau/utils/TransitionUtils.kt
new file mode 100644
index 0000000..5fa9150
--- /dev/null
+++ b/library/src/main/kotlin/ca/allanwang/kau/utils/TransitionUtils.kt
@@ -0,0 +1,19 @@
+package ca.allanwang.kau.utils
+
+import android.support.transition.Transition
+import android.support.transition.TransitionSet
+
+/**
+ * Created by Allan Wang on 2017-06-24.
+ */
+class TransitionEndListener(val onEnd: (transition: Transition) -> Unit) : Transition.TransitionListener {
+ override fun onTransitionEnd(transition: Transition) = onEnd(transition)
+ override fun onTransitionResume(transition: Transition) {}
+ override fun onTransitionPause(transition: Transition) {}
+ override fun onTransitionCancel(transition: Transition) {}
+ override fun onTransitionStart(transition: Transition) {}
+}
+
+fun TransitionSet.addEndListener(onEnd: (transition: Transition) -> Unit) {
+ addListener(TransitionEndListener { onEnd })
+} \ No newline at end of file
diff --git a/library/src/main/kotlin/ca/allanwang/kau/utils/ViewUtils.kt b/library/src/main/kotlin/ca/allanwang/kau/utils/ViewUtils.kt
index 4ec7a4e..20ffb4b 100644
--- a/library/src/main/kotlin/ca/allanwang/kau/utils/ViewUtils.kt
+++ b/library/src/main/kotlin/ca/allanwang/kau/utils/ViewUtils.kt
@@ -1,15 +1,15 @@
package ca.allanwang.kau.utils
-import android.app.Activity
import android.content.Context
import android.graphics.Color
import android.support.annotation.ColorInt
import android.support.annotation.StringRes
import android.support.design.widget.Snackbar
+import android.support.transition.AutoTransition
+import android.support.transition.TransitionManager
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager
-import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.TextView
import com.mikepenz.iconics.IconicsDrawable
@@ -61,3 +61,13 @@ fun View.hideKeyboard() {
(context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager).hideSoftInputFromWindow(windowToken, 0)
}
+fun View.showKeyboard() {
+ requestFocus()
+ (context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager).showSoftInput(this, InputMethodManager.SHOW_IMPLICIT)
+}
+
+fun ViewGroup.transitionAuto(builder: AutoTransition.() -> Unit = {}) {
+ val transition = AutoTransition()
+ transition.builder()
+ TransitionManager.beginDelayedTransition(this, transition)
+}
diff --git a/library/src/main/res/layout/kau_search_view.xml b/library/src/main/res/layout/kau_search_view.xml
index 72cfd02..5626c4b 100644
--- a/library/src/main/res/layout/kau_search_view.xml
+++ b/library/src/main/res/layout/kau_search_view.xml
@@ -13,7 +13,9 @@
<android.support.v7.widget.CardView
android:id="@+id/search_cardview"
android:layout_width="match_parent"
- android:layout_height="wrap_content">
+ android:layout_height="wrap_content"
+ android:paddingEnd="2dp"
+ android:paddingStart="2dp">
<LinearLayout
android:layout_width="match_parent"
diff --git a/library/src/main/res/transition/kau_auto.xml b/library/src/main/res/transition/kau_auto.xml
new file mode 100644
index 0000000..8cda329
--- /dev/null
+++ b/library/src/main/res/transition/kau_auto.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<autoTransition xmlns:android="http://schemas.android.com/apk/res/android"
+ android:duration="300"
+ android:interpolator="@android:interpolator/fast_out_slow_in" /> \ No newline at end of file
diff --git a/library/src/main/res/values/strings_commons.xml b/library/src/main/res/values/strings_commons.xml
index b37c1ea..c678ba1 100644
--- a/library/src/main/res/values/strings_commons.xml
+++ b/library/src/main/res/values/strings_commons.xml
@@ -50,4 +50,5 @@ Most resources are verbatim and x represents a formatted item
<string name="kau_warning">Warning</string>
<string name="kau_yes">Yes</string>
<string name="kau_search">Search</string>
+ <string name="kau_no_results_found">No Results Found</string>
</resources>
diff --git a/sample/src/main/kotlin/ca/allanwang/kau/sample/MainActivity.kt b/sample/src/main/kotlin/ca/allanwang/kau/sample/MainActivity.kt
index 7c4de5a..7680588 100644
--- a/sample/src/main/kotlin/ca/allanwang/kau/sample/MainActivity.kt
+++ b/sample/src/main/kotlin/ca/allanwang/kau/sample/MainActivity.kt
@@ -7,8 +7,13 @@ import ca.allanwang.kau.email.sendEmail
import ca.allanwang.kau.kpref.CoreAttributeContract
import ca.allanwang.kau.kpref.KPrefActivity
import ca.allanwang.kau.kpref.KPrefAdapterBuilder
+import ca.allanwang.kau.logging.KL
+import ca.allanwang.kau.searchview.SearchItem
import ca.allanwang.kau.searchview.bindSearchView
-import ca.allanwang.kau.utils.*
+import ca.allanwang.kau.utils.materialDialog
+import ca.allanwang.kau.utils.navigationBarColor
+import ca.allanwang.kau.utils.startActivity
+import ca.allanwang.kau.utils.toast
import ca.allanwang.kau.views.RippleCanvas
import com.mikepenz.google_material_typeface_library.GoogleMaterial
@@ -120,9 +125,18 @@ class MainActivity : KPrefActivity() {
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_main, menu)
- //workaround for menuY since this view draws under the status bar
- val statusBarHeight = dimen(R.dimen.kau_status_bar_height).toInt().dpToPx
- container.bindSearchView(menu, R.id.action_search)
+ container.bindSearchView(menu, R.id.action_search) {
+ textObserver = {
+ observable, searchView ->
+ observable.subscribe {
+ text ->
+ KL.e(text)
+ searchView.results = if (text.length == 3) emptyList() else Array<String>(text.length, { text }).map { SearchItem(it) }
+ }
+ }
+ noResultsFound = R.string.kau_no_results_found
+ shouldClearOnClose = false
+ }
return true
}