aboutsummaryrefslogtreecommitdiff
path: root/core/src
diff options
context:
space:
mode:
Diffstat (limited to 'core/src')
-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
6 files changed, 179 insertions, 47 deletions
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"/>