aboutsummaryrefslogtreecommitdiff
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/README.md10
-rw-r--r--core/src/androidTest/kotlin/ca/allanwang/kau/xml/FaqTest.kt28
-rw-r--r--core/src/main/kotlin/ca/allanwang/kau/ui/views/CollapsibleViewDelegate.kt105
-rw-r--r--core/src/main/kotlin/ca/allanwang/kau/utils/Const.kt7
-rw-r--r--core/src/main/kotlin/ca/allanwang/kau/utils/ContextUtils.kt9
-rw-r--r--core/src/main/kotlin/ca/allanwang/kau/xml/FAQ.kt75
-rw-r--r--core/src/main/res/values/ids.xml2
7 files changed, 188 insertions, 48 deletions
diff --git a/core/README.md b/core/README.md
index b952797..385c7ed 100644
--- a/core/README.md
+++ b/core/README.md
@@ -10,6 +10,7 @@
* [Kotterknife](#kotterknife)
* [Ripple Canvas](#ripple-canvas)
* [MeasureSpecDelegate](#measure-spec-delegate)
+* [CollapsibleViewDelegate](#collapsible-view-delegate)
* [Timber Logger](#timber-logger)
* [Email Builder](#email-builder)
* [Extensions](#extensions)
@@ -117,7 +118,7 @@ There is another parser for a FAQ list with the following format:
<answer>This is an answer</answer>
```
-Calling `kauParseFaq` will give you a `List<Pair<Spanned, Spanned>` that you can work with.
+Call `kauParseFaq` and pass a callback taking in a `List<Pair<Spanned, Spanned>` that you can work with.
By default, the questions are numbered, and the content is formatted with HTML.
You may still need to add your own methods to allow interaction with certain elements such as links.
@@ -150,6 +151,13 @@ If you ever have a view needing exact aspect ratios with its parent and/or itsel
Implementing this in any view class unlocks its attributes, giving you three layers of view measuring to ensure exact sizing.
More information can be found in the [klass file](https://github.com/AllanWang/KAU/blob/master/core/src/main/kotlin/ca/allanwang/kau/ui/views/MeasureSpecDelegate.kt)
+< a name="collapsible-view-delegate"></a>
+## Collapsible View Delegate
+
+A common animation is having a view that can smoothly enter and exit by changing its height.
+This delegate will implement everything for you and give you the methods `expand`, `collapse`, etc.
+See the [kclass file](https://github.com/AllanWang/KAU/blob/master/core/src/main/kotlin/ca/allanwang/kau/ui/views/CollapsibleViewDelegate.kt) for more details.
+
<a name="timber-logger"></a>
## Timber Logger
diff --git a/core/src/androidTest/kotlin/ca/allanwang/kau/xml/FaqTest.kt b/core/src/androidTest/kotlin/ca/allanwang/kau/xml/FaqTest.kt
index 94d1330..1b185f3 100644
--- a/core/src/androidTest/kotlin/ca/allanwang/kau/xml/FaqTest.kt
+++ b/core/src/androidTest/kotlin/ca/allanwang/kau/xml/FaqTest.kt
@@ -17,22 +17,26 @@ class FaqTest {
@Test
fun simpleTest() {
- val data = InstrumentationRegistry.getTargetContext().kauParseFaq(R.xml.test_faq)
- assertEquals(2, data.size, "FAQ size is incorrect")
- assertEquals("1. This is a question", data.first().first.toString(), "First question does not match")
- assertEquals("This is an answer", data.first().second.toString(), "First answer does not match")
- assertEquals("2. This is another question", data.last().first.toString(), "Second question does not match")
- assertEquals("This is another answer", data.last().second.toString(), "Second answer does not match")
+ InstrumentationRegistry.getTargetContext().kauParseFaq(R.xml.test_faq) {
+ data ->
+ assertEquals(2, data.size, "FAQ size is incorrect")
+ assertEquals("1. This is a question", data.first().first.toString(), "First question does not match")
+ assertEquals("This is an answer", data.first().second.toString(), "First answer does not match")
+ assertEquals("2. This is another question", data.last().first.toString(), "Second question does not match")
+ assertEquals("This is another answer", data.last().second.toString(), "Second answer does not match")
+ }
}
@Test
fun withoutNumbering() {
- val data = InstrumentationRegistry.getTargetContext().kauParseFaq(R.xml.test_faq, false)
- assertEquals(2, data.size, "FAQ size is incorrect")
- assertEquals("This is a question", data.first().first.toString(), "First question does not match")
- assertEquals("This is an answer", data.first().second.toString(), "First answer does not match")
- assertEquals("This is another question", data.last().first.toString(), "Second question does not match")
- assertEquals("This is another answer", data.last().second.toString(), "Second answer does not match")
+ InstrumentationRegistry.getTargetContext().kauParseFaq(R.xml.test_faq, false) {
+ data ->
+ assertEquals(2, data.size, "FAQ size is incorrect")
+ assertEquals("This is a question", data.first().first.toString(), "First question does not match")
+ assertEquals("This is an answer", data.first().second.toString(), "First answer does not match")
+ assertEquals("This is another question", data.last().first.toString(), "Second question does not match")
+ assertEquals("This is another answer", data.last().second.toString(), "Second answer does not match")
+ }
}
} \ No newline at end of file
diff --git a/core/src/main/kotlin/ca/allanwang/kau/ui/views/CollapsibleViewDelegate.kt b/core/src/main/kotlin/ca/allanwang/kau/ui/views/CollapsibleViewDelegate.kt
new file mode 100644
index 0000000..6994ca2
--- /dev/null
+++ b/core/src/main/kotlin/ca/allanwang/kau/ui/views/CollapsibleViewDelegate.kt
@@ -0,0 +1,105 @@
+package ca.allanwang.kau.ui.views
+
+import android.animation.ValueAnimator
+import android.view.View
+import ca.allanwang.kau.utils.*
+import java.lang.ref.WeakReference
+
+/**
+ * Created by Allan Wang on 2017-08-03.
+ *
+ * Delegation class for collapsible views
+ *
+ * Views that implement this MUST call [initCollapsible] before using any of the methods
+ * Additionally, you will need to call [getCollapsibleDimension] and use the response for
+ * [View.setMeasuredDimension] during [View.onMeasure]
+ * (That method is protected so we cannot access it here)
+ *
+ * With reference to <a href="https://github.com/cachapa/ExpandableLayout">ExpandableLayout</a>
+ */
+interface CollapsibleView {
+ var expansion: Float
+ val state: Int
+ val expanded: Boolean
+ fun initCollapsible(view: View)
+ fun resetCollapsibleAnimation()
+ fun getCollapsibleDimension(): Pair<Int, Int>
+ fun toggleExpansion()
+ fun toggleExpansion(animate: Boolean)
+ fun expand()
+ fun expand(animate: Boolean)
+ fun collapse()
+ fun collapse(animate: Boolean)
+ fun setExpanded(expand: Boolean)
+ fun setExpanded(expand: Boolean, animate: Boolean)
+}
+
+class CollapsibleViewDelegate : CollapsibleView {
+
+ private lateinit var viewRef: WeakReference<View>
+ private val view
+ get() = viewRef.get()
+ private var animator: ValueAnimator? = null
+
+ override var expansion = 0f
+ set(value) {
+ if (value == field) return
+ var v = value
+ if (v > 1) v = 1f
+ else if (v < 0) v = 0f
+ stateHolder =
+ if (v == 0f) KAU_COLLAPSED
+ else if (v == 1f) KAU_EXPANDED
+ else if (v - field < 0) KAU_COLLAPSING
+ else KAU_EXPANDING
+ field = v
+ view?.goneIf(state == KAU_COLLAPSED)
+ view?.requestLayout()
+ }
+
+ private var stateHolder = KAU_COLLAPSED
+ override val state
+ get() = stateHolder
+ override val expanded
+ get() = stateHolder == KAU_EXPANDED || stateHolder == KAU_EXPANDING
+
+ override fun initCollapsible(view: View) {
+ this.viewRef = WeakReference(view)
+ }
+
+ override fun resetCollapsibleAnimation() {
+ animator?.cancel()
+ animator = null
+ if (stateHolder == KAU_COLLAPSING) stateHolder = KAU_COLLAPSED
+ else if (stateHolder == KAU_EXPANDING) stateHolder = KAU_EXPANDED
+ }
+
+ override fun getCollapsibleDimension(): Pair<Int, Int> {
+ val v = view ?: return Pair(0, 0)
+ val size = v.measuredHeight
+ v.goneIf(expansion == 0f && size == 0)
+ return Pair(v.measuredWidth, Math.round(size * expansion))
+ }
+
+ private fun animateSize(target: Float) {
+ resetCollapsibleAnimation()
+ animator = ValueAnimator.ofFloat(expansion, target).apply {
+ addUpdateListener { expansion = it.animatedValue as Float }
+ start()
+ }
+ }
+
+ override fun toggleExpansion() = toggleExpansion(true)
+ override fun toggleExpansion(animate: Boolean) = setExpanded(!expanded, animate)
+ override fun expand() = expand(true)
+ override fun expand(animate: Boolean) = setExpanded(true, animate)
+ override fun collapse() = collapse(true)
+ override fun collapse(animate: Boolean) = setExpanded(false, animate)
+ override fun setExpanded(expand: Boolean) = setExpanded(expand, true)
+ override fun setExpanded(expand: Boolean, animate: Boolean) {
+ if (expand == expanded) return //state already matches
+ val target = if (expand) 1f else 0f
+ if (animate) animateSize(target) else expansion = target
+ }
+
+} \ No newline at end of file
diff --git a/core/src/main/kotlin/ca/allanwang/kau/utils/Const.kt b/core/src/main/kotlin/ca/allanwang/kau/utils/Const.kt
index 3e90926..dad01f1 100644
--- a/core/src/main/kotlin/ca/allanwang/kau/utils/Const.kt
+++ b/core/src/main/kotlin/ca/allanwang/kau/utils/Const.kt
@@ -11,4 +11,9 @@ const val KAU_RIGHT = 4
const val KAU_BOTTOM = 8
const val KAU_HORIZONTAL = KAU_LEFT or KAU_RIGHT
const val KAU_VERTICAL = KAU_TOP or KAU_BOTTOM
-const val KAU_ALL = KAU_HORIZONTAL or KAU_VERTICAL \ No newline at end of file
+const val KAU_ALL = KAU_HORIZONTAL or KAU_VERTICAL
+
+const val KAU_COLLAPSED = 0
+const val KAU_COLLAPSING = 1
+const val KAU_EXPANDING = 2
+const val KAU_EXPANDED = 3 \ No newline at end of file
diff --git a/core/src/main/kotlin/ca/allanwang/kau/utils/ContextUtils.kt b/core/src/main/kotlin/ca/allanwang/kau/utils/ContextUtils.kt
index 2219b5d..20cec73 100644
--- a/core/src/main/kotlin/ca/allanwang/kau/utils/ContextUtils.kt
+++ b/core/src/main/kotlin/ca/allanwang/kau/utils/ContextUtils.kt
@@ -1,3 +1,5 @@
+@file:Suppress("NOTHING_TO_INLINE")
+
package ca.allanwang.kau.utils
import android.annotation.SuppressLint
@@ -89,9 +91,12 @@ fun Context.startLink(vararg url: String?) {
}
//Toast helpers
-fun Context.toast(@StringRes id: Int, duration: Int = Toast.LENGTH_LONG) = toast(this.string(id), duration)
+inline fun View.toast(@StringRes id: Int, duration: Int = Toast.LENGTH_LONG) = context.toast(id, duration)
+
+inline fun Context.toast(@StringRes id: Int, duration: Int = Toast.LENGTH_LONG) = toast(this.string(id), duration)
-fun Context.toast(text: String, duration: Int = Toast.LENGTH_LONG) {
+inline fun View.toast(text: String, duration: Int = Toast.LENGTH_LONG) = context.toast(text, duration)
+inline fun Context.toast(text: String, duration: Int = Toast.LENGTH_LONG) {
Toast.makeText(this, text, duration).show()
}
diff --git a/core/src/main/kotlin/ca/allanwang/kau/xml/FAQ.kt b/core/src/main/kotlin/ca/allanwang/kau/xml/FAQ.kt
index b39540c..07a0287 100644
--- a/core/src/main/kotlin/ca/allanwang/kau/xml/FAQ.kt
+++ b/core/src/main/kotlin/ca/allanwang/kau/xml/FAQ.kt
@@ -6,6 +6,8 @@ import android.support.annotation.XmlRes
import android.text.Html
import android.text.Spanned
import ca.allanwang.kau.utils.use
+import org.jetbrains.anko.doAsync
+import org.jetbrains.anko.uiThread
import org.xmlpull.v1.XmlPullParser
/**
@@ -13,42 +15,51 @@ import org.xmlpull.v1.XmlPullParser
*/
/**
- * Parse an xml with two tags, <question>Text</question> and <answer>Text</answer>,
- * and return them as a list of string pairs
+ * Parse an xml asynchronously with two tags, <question>Text</question> and <answer>Text</answer>,
+ * and invoke the [callback] on the ui thread
*/
-fun Context.kauParseFaq(@XmlRes xmlRes: Int, withNumbering: Boolean = true): List<Pair<Spanned, Spanned>> {
- val items = mutableListOf<Pair<Spanned, Spanned>>()
- resources.getXml(xmlRes).use {
- parser: XmlResourceParser ->
- var eventType = parser.eventType
- var question: Spanned? = null
- var flag = -1 //-1, 0, 1 -> invalid, question, answer
- while (eventType != XmlPullParser.END_DOCUMENT) {
- if (eventType == XmlPullParser.START_TAG) {
- flag = when (parser.name) {
- "question" -> 0
- "answer" -> 1
- else -> -1
- }
- } else if (eventType == XmlPullParser.TEXT) {
- when (flag) {
- 0 -> {
- var q = parser.text.replace("\n", "<br/>")
- if (withNumbering) q = "${items.size + 1}. $q"
- question = Html.fromHtml(q)
- flag = -1
+@Suppress("DEPRECATION")
+fun Context.kauParseFaq(
+ @XmlRes xmlRes: Int,
+ /**
+ * If \n is used, it will automatically be converted to </br>
+ */
+ parseNewLine: Boolean = true,
+ callback: (items: List<FaqItem>) -> Unit) {
+ doAsync {
+ val items = mutableListOf<FaqItem>()
+ resources.getXml(xmlRes).use {
+ parser: XmlResourceParser ->
+ var eventType = parser.eventType
+ var question: Spanned? = null
+ var flag = -1 //-1, 0, 1 -> invalid, question, answer
+ while (eventType != XmlPullParser.END_DOCUMENT) {
+ if (eventType == XmlPullParser.START_TAG) {
+ flag = when (parser.name) {
+ "question" -> 0
+ "answer" -> 1
+ else -> -1
}
- 1 -> {
- items.add(Pair(question ?: throw IllegalArgumentException("KAU FAQ answer found without a question"),
- Html.fromHtml(parser.text.replace("\n", "<br/>"))))
- question = null
- flag = -1
+ } else if (eventType == XmlPullParser.TEXT) {
+ when (flag) {
+ 0 -> {
+ question = Html.fromHtml(parser.text.replace("\n", if (parseNewLine) "<br/>" else ""))
+ flag = -1
+ }
+ 1 -> {
+ items.add(FaqItem(items.size + 1,
+ question ?: throw IllegalArgumentException("KAU FAQ answer found without a question"),
+ Html.fromHtml(parser.text.replace("\n", if (parseNewLine) "<br/>" else ""))))
+ question = null
+ flag = -1
+ }
}
}
+ eventType = parser.next()
}
-
- eventType = parser.next()
}
+ uiThread { callback(items) }
}
- return items
-} \ No newline at end of file
+}
+
+data class FaqItem(val number: Int, val question: Spanned, val answer: Spanned) \ No newline at end of file
diff --git a/core/src/main/res/values/ids.xml b/core/src/main/res/values/ids.xml
index 003e8a7..c4912e2 100644
--- a/core/src/main/res/values/ids.xml
+++ b/core/src/main/res/values/ids.xml
@@ -6,6 +6,8 @@
<item name="kau_item_cutout" type="id"/>
<item name="kau_item_header_big_margin_top" type="id"/>
<item name="kau_item_library" type="id"/>
+ <item name="kau_item_faq" type="id"/>
+
<item name="kau_item_pref_checkbox" type="id"/>
<item name="kau_item_pref_color_picker" type="id"/>
<item name="kau_item_pref_header" type="id"/>