aboutsummaryrefslogtreecommitdiff
path: root/library/src/main/kotlin/ca/allanwang
diff options
context:
space:
mode:
Diffstat (limited to 'library/src/main/kotlin/ca/allanwang')
-rw-r--r--library/src/main/kotlin/ca/allanwang/kau/searchview/SearchCard.kt32
-rw-r--r--library/src/main/kotlin/ca/allanwang/kau/searchview/SearchItem.kt19
-rw-r--r--library/src/main/kotlin/ca/allanwang/kau/searchview/SearchView.kt106
-rw-r--r--library/src/main/kotlin/ca/allanwang/kau/views/KauBoundedCardView.kt56
4 files changed, 158 insertions, 55 deletions
diff --git a/library/src/main/kotlin/ca/allanwang/kau/searchview/SearchCard.kt b/library/src/main/kotlin/ca/allanwang/kau/searchview/SearchCard.kt
deleted file mode 100644
index 74f72d0..0000000
--- a/library/src/main/kotlin/ca/allanwang/kau/searchview/SearchCard.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-package ca.allanwang.kau.searchview
-
-import android.content.Context
-import android.graphics.Rect
-import android.support.v7.widget.CardView
-import android.util.AttributeSet
-import android.view.ViewGroup
-import ca.allanwang.kau.utils.parentViewGroup
-
-/**
- * Created by Allan Wang on 2017-06-26.
- *
- * CardView with a limited height
- * Leaves space for users to tap to exit and ensures that all search items are visible
- */
-class SearchCard @JvmOverloads constructor(
- context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
-) : CardView(context, attrs, defStyleAttr) {
-
- val parentVisibleHeight: Int
- get() {
- val r = Rect()
- parentViewGroup.getWindowVisibleDisplayFrame(r)
- return r.height()
- }
-
- override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
- val trueHeightMeasureSpec = MeasureSpec.makeMeasureSpec((parentVisibleHeight * 0.9f).toInt(), MeasureSpec.AT_MOST)
- super.onMeasure(widthMeasureSpec, trueHeightMeasureSpec)
- }
-
-} \ No newline at end of file
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 3882a06..a672e8a 100644
--- a/library/src/main/kotlin/ca/allanwang/kau/searchview/SearchItem.kt
+++ b/library/src/main/kotlin/ca/allanwang/kau/searchview/SearchItem.kt
@@ -1,11 +1,14 @@
package ca.allanwang.kau.searchview
+import android.graphics.Typeface
import android.graphics.drawable.Drawable
import android.support.constraint.ConstraintLayout
import android.support.v7.widget.RecyclerView
+import android.text.Spannable
+import android.text.SpannableStringBuilder
+import android.text.style.StyleSpan
import android.view.View
import android.widget.ImageView
-import android.widget.LinearLayout
import android.widget.TextView
import ca.allanwang.kau.R
import ca.allanwang.kau.utils.*
@@ -32,6 +35,18 @@ class SearchItem(val key: String,
var backgroundColor: Int = 0xfffafafa.toInt()
}
+ var styledContent: SpannableStringBuilder? = null
+
+ /**
+ * Highlight the subText if it is present in the content
+ */
+ fun withHighlights(subText: String) {
+ val index = content.indexOf(subText)
+ if (index == -1) return
+ styledContent = SpannableStringBuilder(content)
+ styledContent!!.setSpan(StyleSpan(Typeface.BOLD), index, index + subText.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
+ }
+
override fun getLayoutRes(): Int = R.layout.kau_search_item
override fun getType(): Int = R.id.kau_item_search
@@ -47,7 +62,7 @@ class SearchItem(val key: String,
else holder.icon.setIcon(iicon, sizeDp = 18, color = foregroundColor)
holder.container.setRippleBackground(foregroundColor, backgroundColor)
- holder.title.text = content
+ holder.title.text = styledContent ?: content
if (description?.isNotBlank() ?: false) holder.desc.visible().text = description
}
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 ba8243d..a0330c5 100644
--- a/library/src/main/kotlin/ca/allanwang/kau/searchview/SearchView.kt
+++ b/library/src/main/kotlin/ca/allanwang/kau/searchview/SearchView.kt
@@ -19,7 +19,9 @@ import android.widget.ImageView
import android.widget.ProgressBar
import ca.allanwang.kau.R
import ca.allanwang.kau.kotlin.nonReadable
+import ca.allanwang.kau.searchview.SearchView.Configs
import ca.allanwang.kau.utils.*
+import ca.allanwang.kau.views.KauBoundedCardView
import com.jakewharton.rxbinding2.widget.RxTextView
import com.mikepenz.fastadapter.commons.adapters.FastItemAdapter
import com.mikepenz.google_material_typeface_library.GoogleMaterial
@@ -33,6 +35,9 @@ import org.jetbrains.anko.runOnUiThread
* Created by Allan Wang on 2017-06-23.
*
* A materialized SearchView with complete theming and observables
+ * This view can be added programmatically and configured using the [Configs] DSL
+ * It is preferred to add the view through an activity, but it can be attached to any ViewGroup
+ * Beware of where specifically this is added, as its view or the keyboard may affect positioning
*
* Huge thanks to @lapism for his base
* https://github.com/lapism/SearchView
@@ -41,7 +46,16 @@ class SearchView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {
+ /**
+ * Collection of all possible arguments when building the SearchView
+ * Everything is made as opened as possible so other components may be found in the [SearchView]
+ * However, these are the notable options put together an an inner class for better visibility
+ */
inner class Configs {
+ /**
+ * In the searchview, foreground color accounts for all text colors and icon colors
+ * Various alpha levels may be used for sub texts/dividers etc
+ */
var foregroundColor: Int
get() = SearchItem.foregroundColor
set(value) {
@@ -49,6 +63,9 @@ class SearchView @JvmOverloads constructor(
SearchItem.foregroundColor = value
tintForeground(value)
}
+ /**
+ * Namely the background for the card and recycler view
+ */
var backgroundColor: Int
get() = SearchItem.backgroundColor
set(value) {
@@ -56,49 +73,78 @@ class SearchView @JvmOverloads constructor(
SearchItem.backgroundColor = value
tintBackground(value)
}
+ /**
+ * Icon for the leftmost ImageView, which typically contains the hamburger menu/back arror
+ */
var navIcon: IIcon? = GoogleMaterial.Icon.gmd_arrow_back
set(value) {
field = value
iconNav.setSearchIcon(value)
if (value == null) iconNav.gone()
}
+
/**
* Optional icon just to the left of the clear icon
* This is not implemented by default, but can be used for anything, such as mic or redirects
- * Make sure you add a click listener to [iconExtra] if you plan on using this
+ * Returns the extra imageview
+ * Set the iicon as null to hide the extra icon
+ */
+ fun setExtraIcon(iicon: IIcon?, onClick: OnClickListener?): ImageView {
+ iconExtra.setSearchIcon(iicon)
+ if (iicon == null) iconClear.gone()
+ iconExtra.setOnClickListener(onClick)
+ return iconExtra
+ }
+
+ /**
+ * Icon for the rightmost ImageView, which typically contains a close icon
*/
- var extraIcon: IIcon? = null
- set(value) {
- field = value
- iconExtra.setSearchIcon(value)
- if (value == null) iconClear.gone()
- }
var clearIcon: IIcon? = GoogleMaterial.Icon.gmd_clear
set(value) {
field = value
iconClear.setSearchIcon(value)
if (value == null) iconClear.gone()
}
+ /**
+ * Duration for the circular reveal animation
+ */
var revealDuration: Long = 300L
+ /**
+ * Duration for the auto transition, which is namely used to resize the recycler view
+ */
var transitionDuration: Long = 100L
+ /**
+ * Defines whether the edit text and adapter should clear themselves when the searchView is closed
+ */
var shouldClearOnClose: Boolean = false
+ /**
+ * Callback that will be called every time the searchView opens
+ */
var openListener: ((searchView: SearchView) -> Unit)? = null
+ /**
+ * Callback that will be called every time the searchView closes
+ */
var closeListener: ((searchView: SearchView) -> Unit)? = null
/**
* Draw a divider between the search bar and the suggestion items
- * The divider is colored based on the foreground color
+ * The divider is colored based on the [foregroundColor]
*/
var withDivider: Boolean = true
set(value) {
field = value
if (value) divider.visible() else divider.invisible()
}
+ /**
+ * Hint string to be set in the searchView
+ */
var hintText: String?
get() = editText.hint?.toString()
set(value) {
editText.hint = value
}
-
+ /**
+ * Hint string res to be set in the searchView
+ */
var hintTextRes: Int
@Deprecated(level = DeprecationLevel.ERROR, message = "Non readable property")
get() = nonReadable()
@@ -130,8 +176,11 @@ class SearchView @JvmOverloads constructor(
* This event is only triggered when [key] is not blank (like in [noResultsFound]
*/
var onItemLongClick: (position: Int, key: String, content: String, searchView: SearchView) -> Unit = { _, _, _, _ -> }
-
-
+ /**
+ * If a [SearchItem]'s title contains the submitted query, make that portion bold
+ * See [SearchItem.withHighlights]
+ */
+ var highlightQueryText: Boolean = true
}
/**
@@ -141,11 +190,12 @@ class SearchView @JvmOverloads constructor(
var results: List<SearchItem>
get() = adapter.adapterItems
set(value) = context.runOnUiThread {
+ val list = if (value.isEmpty() && configs.noResultsFound > 0)
+ listOf(SearchItem("", context.string(configs.noResultsFound), iicon = null))
+ else value
+ if (configs.highlightQueryText && value.isNotEmpty()) list.forEach { it.withHighlights(editText.text.toString()) }
cardTransition()
- adapter.setNewList(
- if (configs.noResultsFound > 0 && value.isEmpty())
- listOf(SearchItem("", context.string(configs.noResultsFound), iicon = null))
- else value)
+ adapter.setNewList(list)
}
/**
@@ -157,7 +207,7 @@ class SearchView @JvmOverloads constructor(
val configs = Configs()
//views
private val shadow: View by bindView(R.id.search_shadow)
- private val card: SearchCard by bindView(R.id.search_cardview)
+ private val card: KauBoundedCardView 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>
@@ -229,7 +279,12 @@ class SearchView @JvmOverloads constructor(
configs.config()
}
- fun bind(activity: Activity, menu: Menu, @IdRes id: Int, @ColorInt menuIconColor: Int = Color.WHITE, config: Configs.() -> Unit = {}): SearchView {
+ /**
+ * Binds the SearchView to a menu item and handles everything internally
+ * This is assuming that SearchView has already been added to a ViewGroup
+ * If not, see the extension function [bindSearchView]
+ */
+ fun bind(menu: Menu, @IdRes id: Int, @ColorInt menuIconColor: Int = Color.WHITE, config: Configs.() -> Unit = {}): SearchView {
config(config)
configs.textObserver(textEvents.filter { it.isNotBlank() }, this)
menuItem = menu.findItem(id)
@@ -334,11 +389,20 @@ class SearchView @JvmOverloads constructor(
@DslMarker
annotation class KauSearch
+/**
+ * Helper function that binds to an activity's main view
+ */
@KauSearch
-fun Activity.bindSearchView(menu: Menu, @IdRes id: Int, @ColorInt menuIconColor: Int = Color.WHITE, config: SearchView.Configs.() -> Unit = {}): SearchView {
- val searchView = SearchView(this)
+fun Activity.bindSearchView(menu: Menu, @IdRes id: Int, @ColorInt menuIconColor: Int = Color.WHITE, config: SearchView.Configs.() -> Unit = {}): SearchView
+ = findViewById<ViewGroup>(android.R.id.content).bindSearchView(menu, id, menuIconColor, config)
+
+
+@KauSearch
+fun ViewGroup.bindSearchView(menu: Menu, @IdRes id: Int, @ColorInt menuIconColor: Int = Color.WHITE, config: SearchView.Configs.() -> Unit = {}): SearchView {
+ val searchView = SearchView(context)
searchView.layoutParams = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)
- findViewById<ViewGroup>(android.R.id.content).addView(searchView)
- searchView.bind(this, menu, id, menuIconColor, config)
+ addView(searchView)
+ searchView.bind(menu, id, menuIconColor, config)
return searchView
}
+
diff --git a/library/src/main/kotlin/ca/allanwang/kau/views/KauBoundedCardView.kt b/library/src/main/kotlin/ca/allanwang/kau/views/KauBoundedCardView.kt
new file mode 100644
index 0000000..a07a118
--- /dev/null
+++ b/library/src/main/kotlin/ca/allanwang/kau/views/KauBoundedCardView.kt
@@ -0,0 +1,56 @@
+package ca.allanwang.kau.views
+
+import android.content.Context
+import android.graphics.Rect
+import android.support.v7.widget.CardView
+import android.util.AttributeSet
+import ca.allanwang.kau.R
+import ca.allanwang.kau.utils.parentViewGroup
+
+
+/**
+ * Created by Allan Wang on 2017-06-26.
+ *
+ * CardView with a limited height
+ * This view should be used with wrap_content as its height
+ * Defaults to at most the parent's visible height
+ */
+class KauBoundedCardView @JvmOverloads constructor(
+ context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
+) : CardView(context, attrs, defStyleAttr) {
+
+ val parentVisibleHeight: Int
+ get() {
+ val r = Rect()
+ parentViewGroup.getWindowVisibleDisplayFrame(r)
+ return r.height()
+ }
+
+ /**
+ * Maximum height possible, defined in dp (will be converted to px)
+ * Defaults to parent's visible height
+ */
+ var maxHeight: Int = -1
+ /**
+ * Percentage of resulting max height to fill
+ * Negative value = fill all of maxHeight
+ */
+ var maxHeightPercent: Float = -1.0f
+
+ init {
+ if (attrs != null) {
+ val styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.KauBoundedCardView)
+ maxHeight = styledAttrs.getDimensionPixelSize(R.styleable.KauBoundedCardView_kau_maxHeight, -1)
+ maxHeightPercent = styledAttrs.getFloat(R.styleable.KauBoundedCardView_kau_maxHeightPercent, -1.0f)
+ styledAttrs.recycle()
+ }
+ }
+
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ var maxMeasureHeight = if (maxHeight > 0) maxHeight else parentVisibleHeight
+ if (maxHeightPercent > 0f) maxMeasureHeight = (maxMeasureHeight * maxHeightPercent).toInt()
+ val trueHeightMeasureSpec = MeasureSpec.makeMeasureSpec(maxMeasureHeight, MeasureSpec.AT_MOST)
+ super.onMeasure(widthMeasureSpec, trueHeightMeasureSpec)
+ }
+
+} \ No newline at end of file