aboutsummaryrefslogtreecommitdiff
path: root/core/src
diff options
context:
space:
mode:
authorAllan Wang <me@allanwang.ca>2017-07-31 23:02:01 -0700
committerGitHub <noreply@github.com>2017-07-31 23:02:01 -0700
commit48213d0b427c478865c75fee912ff1ae8bbaffb5 (patch)
tree7aef1d8400fc3403ee5a40aba945f33a95319359 /core/src
parent8a4e9fd44dfbcf58aa7ab63167dcbdf8752db7d0 (diff)
downloadkau-48213d0b427c478865c75fee912ff1ae8bbaffb5.tar.gz
kau-48213d0b427c478865c75fee912ff1ae8bbaffb5.tar.bz2
kau-48213d0b427c478865c75fee912ff1ae8bbaffb5.zip
Major update to core and kotterknife; create mediapicker (#15)
* Readme * Fix kau direction bits * Truly support transparent ripples * Update changelog * Test rect as base * Replace fab transition with generic fade scale transition * Add scalexy func * Add scaleXY * Add arguments to fadeScaleTransition * Clean up ink indicator * Create setOnSingleTapListener * Fix lint and add rndColor * Create kotterknife resettables * Add readme and missing object * Create lazy resettable registered * Update core docs * Opt for separate class for resettable registry * Clean up resettable registry * Rename functions * Add ripple callback listener * Adjust kprefactivity desc color * Add more transitions * Add delete keys option * Add instrumentation tests * switch id * Revert automatic instrumental tests * Generify imagepickercore and prepare video alternative * Create working video picker * Address possible null issue * Update searchview * Make layouts public * Add changelog test * Update logo link * Add custom color gif
Diffstat (limited to 'core/src')
-rw-r--r--core/src/androidTest/kotlin/ca/allanwang/kau/utils/KotterknifeTest.kt198
-rw-r--r--core/src/androidTest/kotlin/ca/allanwang/kau/xml/ChangelogTest.kt22
-rw-r--r--core/src/androidTest/res/xml/text_changelog.xml11
-rw-r--r--core/src/main/kotlin/ca/allanwang/kau/kotlin/LazyResettable.kt30
-rw-r--r--core/src/main/kotlin/ca/allanwang/kau/kpref/KPref.kt21
-rw-r--r--core/src/main/kotlin/ca/allanwang/kau/kpref/KPrefDelegate.kt3
-rw-r--r--core/src/main/kotlin/ca/allanwang/kau/ui/views/RippleCanvas.kt90
-rw-r--r--core/src/main/kotlin/ca/allanwang/kau/utils/AnimUtils.kt5
-rw-r--r--core/src/main/kotlin/ca/allanwang/kau/utils/ColorUtils.kt15
-rw-r--r--core/src/main/kotlin/ca/allanwang/kau/utils/Const.kt6
-rw-r--r--core/src/main/kotlin/ca/allanwang/kau/utils/Kotterknife.kt183
-rw-r--r--core/src/main/kotlin/ca/allanwang/kau/utils/PackageUtils.kt3
-rw-r--r--core/src/main/kotlin/ca/allanwang/kau/utils/ViewUtils.kt54
-rw-r--r--core/src/main/kotlin/ca/allanwang/kau/xml/Changelog.kt (renamed from core/src/main/kotlin/ca/allanwang/kau/changelog/Changelog.kt)2
-rw-r--r--core/src/main/kotlin/ca/allanwang/kau/xml/FAQ.kt42
-rw-r--r--core/src/main/res-public/anim/kau_slide_out_left_top.xml8
-rw-r--r--core/src/main/res-public/transition-v21/kau_enter_slide_bottom.xml7
-rw-r--r--core/src/main/res-public/transition-v21/kau_enter_slide_left.xml21
-rw-r--r--core/src/main/res-public/transition-v21/kau_enter_slide_right.xml21
-rw-r--r--core/src/main/res-public/transition-v21/kau_enter_slide_top.xml7
-rw-r--r--core/src/main/res-public/transition-v21/kau_exit_slide_bottom.xml11
-rw-r--r--core/src/main/res-public/transition-v21/kau_exit_slide_left.xml22
-rw-r--r--core/src/main/res-public/transition-v21/kau_exit_slide_right.xml22
-rw-r--r--core/src/main/res-public/transition-v21/kau_exit_slide_top.xml11
-rw-r--r--core/src/main/res-public/values/public.xml5
-rw-r--r--core/src/test/kotlin/ca/allanwang/kau/kotlin/LazyResettableTest.kt35
-rw-r--r--core/src/test/kotlin/ca/allanwang/kau/utils/UtilsTest.kt (renamed from core/src/test/kotlin/ca/allanwang/kprefs/library/UtilsTest.kt)4
27 files changed, 738 insertions, 121 deletions
diff --git a/core/src/androidTest/kotlin/ca/allanwang/kau/utils/KotterknifeTest.kt b/core/src/androidTest/kotlin/ca/allanwang/kau/utils/KotterknifeTest.kt
new file mode 100644
index 0000000..3282911
--- /dev/null
+++ b/core/src/androidTest/kotlin/ca/allanwang/kau/utils/KotterknifeTest.kt
@@ -0,0 +1,198 @@
+package ca.allanwang.kau.utils
+
+import android.content.Context
+import android.support.test.InstrumentationRegistry
+import android.support.test.filters.MediumTest
+import android.support.test.runner.AndroidJUnit4
+import android.view.View
+import android.widget.FrameLayout
+import android.widget.TextView
+import org.junit.Assert.*
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Created by Allan Wang on 2017-07-30.
+ */
+@RunWith(AndroidJUnit4::class)
+@MediumTest
+class KotterknifeTest {
+
+ lateinit var context: Context
+
+ @Before
+ fun init() {
+ context = InstrumentationRegistry.getContext()
+ }
+
+ @Test
+ fun testCast() {
+ class Example(context: Context) : FrameLayout(context) {
+ val name: TextView by bindView(1)
+ }
+
+ val example = Example(context)
+ example.addView(textViewWithId(1))
+ assertNotNull(example.name)
+ }
+
+ @Test
+ fun testFindCached() {
+ class Example(context: Context) : FrameLayout(context) {
+ val name: View by bindView(1)
+ }
+
+ val example = Example(context)
+ example.addView(viewWithId(1))
+ assertNotNull(example.name)
+ example.removeAllViews()
+ assertNotNull(example.name)
+ }
+
+ @Test
+ fun testOptional() {
+ class Example(context: Context) : FrameLayout(context) {
+ val present: View? by bindOptionalView(1)
+ val missing: View? by bindOptionalView(2)
+ }
+
+ val example = Example(context)
+ example.addView(viewWithId(1))
+ assertNotNull(example.present)
+ assertNull(example.missing)
+ }
+
+ @Test
+ fun testOptionalCached() {
+ class Example(context: Context) : FrameLayout(context) {
+ val present: View? by bindOptionalView(1)
+ val missing: View? by bindOptionalView(2)
+ }
+
+ val example = Example(context)
+ example.addView(viewWithId(1))
+ assertNotNull(example.present)
+ assertNull(example.missing)
+ example.removeAllViews()
+ example.addView(viewWithId(2))
+ assertNotNull(example.present)
+ assertNull(example.missing)
+ }
+
+ @Test
+ fun testMissingFails() {
+ class Example(context: Context) : FrameLayout(context) {
+ val name: TextView? by bindView(1)
+ }
+
+ val example = Example(context)
+ try {
+ example.name
+ } catch (e: IllegalStateException) {
+ assertEquals("View ID 1 for 'name' not found.", e.message)
+ }
+ }
+
+ @Test
+ fun testList() {
+ class Example(context: Context) : FrameLayout(context) {
+ val name: List<TextView> by bindViews(1, 2, 3)
+ }
+
+ val example = Example(context)
+ example.addView(viewWithId(1))
+ example.addView(viewWithId(2))
+ example.addView(viewWithId(3))
+ assertNotNull(example.name)
+ assertEquals(3, example.name.size)
+ }
+
+ @Test
+ fun testListCaches() {
+ class Example(context: Context) : FrameLayout(context) {
+ val name: List<TextView> by bindViews(1, 2, 3)
+ }
+
+ val example = Example(context)
+ example.addView(viewWithId(1))
+ example.addView(viewWithId(2))
+ example.addView(viewWithId(3))
+ assertNotNull(example.name)
+ assertEquals(3, example.name.size)
+ example.removeAllViews()
+ assertNotNull(example.name)
+ assertEquals(3, example.name.size)
+ }
+
+ @Test
+ fun testListMissingFails() {
+ class Example(context: Context) : FrameLayout(context) {
+ val name: List<TextView> by bindViews(1, 2, 3)
+ }
+
+ val example = Example(context)
+ example.addView(viewWithId(1))
+ example.addView(viewWithId(3))
+ try {
+ example.name
+ } catch (e: IllegalStateException) {
+ assertEquals("View ID 2 for 'name' not found.", e.message)
+ }
+ }
+
+ @Test
+ fun testOptionalList() {
+ class Example(context: Context) : FrameLayout(context) {
+ val name: List<TextView> by bindOptionalViews(1, 2, 3)
+ }
+
+ val example = Example(context)
+ example.addView(viewWithId(1))
+ example.addView(viewWithId(3))
+ assertNotNull(example.name)
+ assertEquals(2, example.name.size)
+ }
+
+ @Test
+ fun testOptionalListCaches() {
+ class Example(context: Context) : FrameLayout(context) {
+ val name: List<TextView> by bindOptionalViews(1, 2, 3)
+ }
+
+ val example = Example(context)
+ example.addView(viewWithId(1))
+ example.addView(viewWithId(3))
+ assertNotNull(example.name)
+ assertEquals(2, example.name.size)
+ example.removeAllViews()
+ assertNotNull(example.name)
+ assertEquals(2, example.name.size)
+ }
+
+ @Test
+ fun testReset() {
+ class Example(context: Context) : FrameLayout(context) {
+ val name: View? by bindOptionalViewResettable(1)
+ }
+
+ val example = Example(context)
+ example.addView(viewWithId(1))
+ assertNotNull(example.name)
+ example.removeAllViews()
+ Kotterknife.reset(example)
+ assertNull(example.name)
+ }
+
+ private fun viewWithId(id: Int): View {
+ val view = View(context)
+ view.id = id
+ return view
+ }
+
+ private fun textViewWithId(id: Int): View {
+ val view = TextView(context)
+ view.id = id
+ return view
+ }
+} \ No newline at end of file
diff --git a/core/src/androidTest/kotlin/ca/allanwang/kau/xml/ChangelogTest.kt b/core/src/androidTest/kotlin/ca/allanwang/kau/xml/ChangelogTest.kt
new file mode 100644
index 0000000..dcff70e
--- /dev/null
+++ b/core/src/androidTest/kotlin/ca/allanwang/kau/xml/ChangelogTest.kt
@@ -0,0 +1,22 @@
+package ca.allanwang.kau.xml
+
+import android.support.test.InstrumentationRegistry
+import android.support.test.filters.MediumTest
+import android.support.test.runner.AndroidJUnit4
+import ca.allanwang.kau.test.R
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Created by Allan Wang on 2017-07-31.
+ */
+@RunWith(AndroidJUnit4::class)
+@MediumTest
+class ChangelogTest {
+
+ @Test
+ fun simpleTest() {
+ val data = parse(InstrumentationRegistry.getTargetContext(), R.xml.text_changelog)
+ }
+
+} \ No newline at end of file
diff --git a/core/src/androidTest/res/xml/text_changelog.xml b/core/src/androidTest/res/xml/text_changelog.xml
new file mode 100644
index 0000000..6294144
--- /dev/null
+++ b/core/src/androidTest/res/xml/text_changelog.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <version title="title1"/>
+ <item text="test case" />
+ <item text="" />
+
+ <version title="title2"/>
+ <item text="potato" />
+
+</resources> \ No newline at end of file
diff --git a/core/src/main/kotlin/ca/allanwang/kau/kotlin/LazyResettable.kt b/core/src/main/kotlin/ca/allanwang/kau/kotlin/LazyResettable.kt
index 2981dda..701cb07 100644
--- a/core/src/main/kotlin/ca/allanwang/kau/kotlin/LazyResettable.kt
+++ b/core/src/main/kotlin/ca/allanwang/kau/kotlin/LazyResettable.kt
@@ -13,7 +13,7 @@ internal object UNINITIALIZED
fun <T : Any> lazyResettable(initializer: () -> T): LazyResettable<T> = LazyResettable<T>(initializer)
-class LazyResettable<T : Any>(private val initializer: () -> T, lock: Any? = null) : ILazyResettable<T>, Serializable {
+open class LazyResettable<T : Any>(private val initializer: () -> T, lock: Any? = null) : ILazyResettable<T>, Serializable {
@Volatile private var _value: Any = UNINITIALIZED
private val lock = lock ?: this
@@ -52,4 +52,32 @@ class LazyResettable<T : Any>(private val initializer: () -> T, lock: Any? = nul
interface ILazyResettable<T> : Lazy<T> {
fun invalidate()
+}
+
+interface ILazyResettableRegistry {
+ fun <T : Any> lazy(initializer: () -> T): LazyResettable<T>
+ fun <T : Any> add(resettable: LazyResettable<T>): LazyResettable<T>
+ fun invalidateAll()
+ fun clear()
+}
+
+/**
+ * The following below is a helper class that registers all resettables into a weakly held list
+ * All resettables can therefore be invalidated at once
+ */
+class LazyResettableRegistry : ILazyResettableRegistry {
+
+ var lazyRegistry: MutableList<LazyResettable<*>> = mutableListOf()
+
+ override fun <T : Any> lazy(initializer: () -> T): LazyResettable<T>
+ = add(lazyResettable(initializer))
+
+ override fun <T : Any> add(resettable: LazyResettable<T>): LazyResettable<T> {
+ lazyRegistry.add(resettable)
+ return resettable
+ }
+
+ override fun invalidateAll() = lazyRegistry.forEach { it.invalidate() }
+
+ override fun clear() = lazyRegistry.clear()
} \ No newline at end of file
diff --git a/core/src/main/kotlin/ca/allanwang/kau/kpref/KPref.kt b/core/src/main/kotlin/ca/allanwang/kau/kpref/KPref.kt
index fa6c5a9..c1ce282 100644
--- a/core/src/main/kotlin/ca/allanwang/kau/kpref/KPref.kt
+++ b/core/src/main/kotlin/ca/allanwang/kau/kpref/KPref.kt
@@ -6,6 +6,19 @@ import ca.allanwang.kau.kotlin.ILazyResettable
/**
* Created by Allan Wang on 2017-06-07.
+ *
+ * Base class for shared preferences
+ * All objects extending this class must be called in
+ * the app's [android.app.Application] class
+ *
+ * See the [KPref.kpref] extensions for more details
+ *
+ * Furthermore, all kprefs are held in the [prefMap],
+ * so if you wish to reset a preference, you must also invalidate the kpref
+ * from that map
+ *
+ * You may optionally override [deleteKeys]. This will be called on initialization
+ * And delete all keys returned from that method
*/
open class KPref {
@@ -18,6 +31,12 @@ open class KPref {
initialized = true
this.c = c.applicationContext
PREFERENCE_NAME = preferenceName
+ val toDelete = deleteKeys()
+ if (toDelete.isNotEmpty()) {
+ val edit = sp.edit()
+ toDelete.forEach { edit.remove(it) }
+ edit.apply()
+ }
}
internal val sp: SharedPreferences by lazy {
@@ -33,4 +52,6 @@ open class KPref {
operator fun get(key: String): ILazyResettable<*>? = prefMap[key]
+ open fun deleteKeys(): Array<String> = arrayOf()
+
} \ No newline at end of file
diff --git a/core/src/main/kotlin/ca/allanwang/kau/kpref/KPrefDelegate.kt b/core/src/main/kotlin/ca/allanwang/kau/kpref/KPrefDelegate.kt
index f742078..8a582d8 100644
--- a/core/src/main/kotlin/ca/allanwang/kau/kpref/KPrefDelegate.kt
+++ b/core/src/main/kotlin/ca/allanwang/kau/kpref/KPrefDelegate.kt
@@ -2,7 +2,6 @@ package ca.allanwang.kau.kpref
import ca.allanwang.kau.kotlin.ILazyResettable
-object UNINITIALIZED
fun KPref.kpref(key: String, fallback: Boolean, postSetter: (value: Boolean) -> Unit = {}) = KPrefDelegate(key, fallback, this, postSetter)
fun KPref.kpref(key: String, fallback: Double, postSetter: (value: Float) -> Unit = {}) = KPrefDelegate(key, fallback.toFloat(), this, postSetter)
@@ -25,6 +24,8 @@ class KPrefDelegate<T : Any> internal constructor(
private val key: String, private val fallback: T, private val pref: KPref, var postSetter: (value: T) -> Unit = {}, lock: Any? = null
) : ILazyResettable<T>, java.io.Serializable {
+ private object UNINITIALIZED
+
@Volatile private var _value: Any = UNINITIALIZED
private val lock = lock ?: this
diff --git a/core/src/main/kotlin/ca/allanwang/kau/ui/views/RippleCanvas.kt b/core/src/main/kotlin/ca/allanwang/kau/ui/views/RippleCanvas.kt
index 773490c..f587e60 100644
--- a/core/src/main/kotlin/ca/allanwang/kau/ui/views/RippleCanvas.kt
+++ b/core/src/main/kotlin/ca/allanwang/kau/ui/views/RippleCanvas.kt
@@ -1,14 +1,13 @@
package ca.allanwang.kau.ui.views
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
import android.animation.ArgbEvaluator
import android.animation.ValueAnimator
import android.content.Context
-import android.graphics.Canvas
-import android.graphics.Color
-import android.graphics.Paint
+import android.graphics.*
import android.util.AttributeSet
import android.view.View
-import ca.allanwang.kau.utils.adjustAlpha
/**
* Created by Allan Wang on 2016-11-17.
@@ -21,63 +20,45 @@ import ca.allanwang.kau.utils.adjustAlpha
class RippleCanvas @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
- private val paint: Paint = Paint()
+ private val paint: Paint = Paint().apply {
+ isAntiAlias = true
+ style = Paint.Style.FILL
+ }
+ private val eraser: Paint = Paint().apply {
+ style = Paint.Style.FILL
+ xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
+ }
private var baseColor = Color.TRANSPARENT
private val ripples: MutableList<Ripple> = mutableListOf()
- init {
- paint.isAntiAlias = true
- paint.style = Paint.Style.FILL
- }
-
/**
- * Drawing the ripples involves having access to the next layer if it exists,
- * and using its values to decide on the current color.
- * If the next layer requests a fade, we will adjust the alpha of our current layer before drawing.
- * Otherwise we will just draw the color as intended
+ * Draw ripples one at a time in the order given
+ * To support transparent ripples, we simply erase the overlapping base before adding a new circle
*/
override fun onDraw(canvas: Canvas) {
- val itr = ripples.listIterator()
- if (!itr.hasNext()) return canvas.drawColor(baseColor)
- var next = itr.next()
- canvas.drawColor(colorToDraw(baseColor, next.fade, next.radius, next.maxRadius))
- var last = false
- while (!last) {
- val current = next
- if (itr.hasNext()) next = itr.next()
- else last = true
- //We may fade any layer except for the last one
- paint.color = colorToDraw(current.color, next.fade && !last, next.radius, next.maxRadius)
- canvas.drawCircle(current.x, current.y, current.radius, paint)
- if (current.radius == current.maxRadius) {
- if (!last) {
- itr.previous()
- itr.remove()
- itr.next()
- } else {
- itr.remove()
- }
- baseColor = current.color
+ paint.color = baseColor
+ canvas.drawRect(0f, 0f, canvas.width.toFloat(), canvas.height.toFloat(), paint)
+ val itr = ripples.iterator()
+ while (itr.hasNext()) {
+ val r = itr.next()
+ paint.color = r.color
+ canvas.drawCircle(r.x, r.y, r.radius, eraser)
+ canvas.drawCircle(r.x, r.y, r.radius, paint)
+ if (r.radius == r.maxRadius) {
+ itr.remove()
+ baseColor = r.color
}
}
}
/**
- * Given our current color and next layer's radius & max,
- * we will decide on the alpha of our current layer
- */
- internal fun colorToDraw(color: Int, fade: Boolean, current: Float, goal: Float): Int {
- if (!fade || (current / goal <= FADE_PIVOT)) return color
- val factor = (goal - current) / (goal - FADE_PIVOT * goal)
- return color.adjustAlpha(factor)
- }
-
- /**
* Creates a ripple effect from the given starting values
- * [fade] will gradually transition previous ripples to a transparent color so the resulting background is what we want
- * this is typically only necessary if the ripple color has transparency
*/
- fun ripple(color: Int, startX: Float = 0f, startY: Float = 0f, duration: Long = 600L, fade: Boolean = Color.alpha(color) != 255) {
+ fun ripple(color: Int,
+ startX: Float = 0f,
+ startY: Float = 0f,
+ duration: Long = 600L,
+ callback: (() -> Unit)? = null) {
val w = width.toFloat()
val h = height.toFloat()
val x = when (startX) {
@@ -91,7 +72,7 @@ class RippleCanvas @JvmOverloads constructor(
else -> startY
}
val maxRadius = Math.hypot(Math.max(x, w - x).toDouble(), Math.max(y, h - y).toDouble()).toFloat()
- val ripple = Ripple(color, x, y, 0f, maxRadius, fade)
+ val ripple = Ripple(color, x, y, 0f, maxRadius)
ripples.add(ripple)
val animator = ValueAnimator.ofFloat(0f, maxRadius)
animator.duration = duration
@@ -99,6 +80,11 @@ class RippleCanvas @JvmOverloads constructor(
ripple.radius = animation.animatedValue as Float
invalidate()
}
+ if (callback != null)
+ animator.addListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationCancel(animation: Animator?) = callback()
+ override fun onAnimationEnd(animation: Animator?) = callback()
+ })
animator.start()
}
@@ -111,6 +97,8 @@ class RippleCanvas @JvmOverloads constructor(
invalidate()
}
+ override fun setBackgroundColor(color: Int) = set(color)
+
/**
* Sets a color directly but with a transition
*/
@@ -129,12 +117,10 @@ class RippleCanvas @JvmOverloads constructor(
val x: Float,
val y: Float,
var radius: Float,
- val maxRadius: Float,
- val fade: Boolean)
+ val maxRadius: Float)
companion object {
const val MIDDLE = -1.0f
const val END = -2.0f
- const val FADE_PIVOT = 0.5f
}
}
diff --git a/core/src/main/kotlin/ca/allanwang/kau/utils/AnimUtils.kt b/core/src/main/kotlin/ca/allanwang/kau/utils/AnimUtils.kt
index 112c8ec..5da21bb 100644
--- a/core/src/main/kotlin/ca/allanwang/kau/utils/AnimUtils.kt
+++ b/core/src/main/kotlin/ca/allanwang/kau/utils/AnimUtils.kt
@@ -6,6 +6,7 @@ import android.annotation.SuppressLint
import android.support.annotation.StringRes
import android.view.View
import android.view.ViewAnimationUtils
+import android.view.ViewPropertyAnimator
import android.view.animation.Animation
import android.view.animation.AnimationUtils
import android.widget.TextView
@@ -123,4 +124,6 @@ import android.widget.TextView
})
}
-@KauUtils fun TextView.setTextWithFade(@StringRes textId: Int, duration: Long = 200, onFinish: (() -> Unit)? = null) = setTextWithFade(context.getString(textId), duration, onFinish) \ No newline at end of file
+@KauUtils fun TextView.setTextWithFade(@StringRes textId: Int, duration: Long = 200, onFinish: (() -> Unit)? = null) = setTextWithFade(context.getString(textId), duration, onFinish)
+
+@KauUtils inline fun ViewPropertyAnimator.scaleXY(value: Float) = scaleX(value).scaleY(value) \ No newline at end of file
diff --git a/core/src/main/kotlin/ca/allanwang/kau/utils/ColorUtils.kt b/core/src/main/kotlin/ca/allanwang/kau/utils/ColorUtils.kt
index 50d117c..8537185 100644
--- a/core/src/main/kotlin/ca/allanwang/kau/utils/ColorUtils.kt
+++ b/core/src/main/kotlin/ca/allanwang/kau/utils/ColorUtils.kt
@@ -15,10 +15,25 @@ import android.support.v7.widget.AppCompatEditText
import android.support.v7.widget.Toolbar
import android.widget.*
import com.afollestad.materialdialogs.R
+import java.util.*
/**
* Created by Allan Wang on 2017-06-08.
*/
+
+/**
+ * Generates a random opaque color
+ * Note that this is mainly for testing
+ * Should you require this method often, consider
+ * rewriting the method and storing the [Random] instance
+ * rather than generating one each time
+ */
+inline val rndColor: Int
+ get() {
+ val rnd = Random()
+ return Color.rgb(rnd.nextInt(256), rnd.nextInt(256), rnd.nextInt(256))
+ }
+
inline val Int.isColorDark: Boolean
get() = isColorDark(0.5f)
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 f267a60..3e90926 100644
--- a/core/src/main/kotlin/ca/allanwang/kau/utils/Const.kt
+++ b/core/src/main/kotlin/ca/allanwang/kau/utils/Const.kt
@@ -9,6 +9,6 @@ const val KAU_LEFT = 1
const val KAU_TOP = 2
const val KAU_RIGHT = 4
const val KAU_BOTTOM = 8
-const val KAU_HORIZONTAL = KAU_LEFT and KAU_RIGHT
-const val KAU_VERTICAL = KAU_TOP and KAU_BOTTOM
-const val KAU_ALL = KAU_HORIZONTAL and KAU_VERTICAL \ No newline at end of file
+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
diff --git a/core/src/main/kotlin/ca/allanwang/kau/utils/Kotterknife.kt b/core/src/main/kotlin/ca/allanwang/kau/utils/Kotterknife.kt
index 3783931..f3c08bd 100644
--- a/core/src/main/kotlin/ca/allanwang/kau/utils/Kotterknife.kt
+++ b/core/src/main/kotlin/ca/allanwang/kau/utils/Kotterknife.kt
@@ -1,3 +1,5 @@
+@file:Suppress("UNCHECKED_CAST")
+
package ca.allanwang.kau.utils
/**
@@ -13,6 +15,7 @@ import android.app.DialogFragment
import android.app.Fragment
import android.support.v7.widget.RecyclerView.ViewHolder
import android.view.View
+import java.util.*
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
import android.support.v4.app.DialogFragment as SupportDialogFragment
@@ -30,13 +33,13 @@ fun <V : View> Dialog.bindView(id: Int)
fun <V : View> DialogFragment.bindView(id: Int)
: ReadOnlyProperty<DialogFragment, V> = required(id, viewFinder)
-fun <V : View> android.support.v4.app.DialogFragment.bindView(id: Int)
+fun <V : View> SupportDialogFragment.bindView(id: Int)
: ReadOnlyProperty<android.support.v4.app.DialogFragment, V> = required(id, viewFinder)
fun <V : View> Fragment.bindView(id: Int)
: ReadOnlyProperty<Fragment, V> = required(id, viewFinder)
-fun <V : View> android.support.v4.app.Fragment.bindView(id: Int)
+fun <V : View> SupportFragment.bindView(id: Int)
: ReadOnlyProperty<android.support.v4.app.Fragment, V> = required(id, viewFinder)
fun <V : View> ViewHolder.bindView(id: Int)
@@ -54,13 +57,13 @@ fun <V : View> Dialog.bindOptionalView(id: Int)
fun <V : View> DialogFragment.bindOptionalView(id: Int)
: ReadOnlyProperty<DialogFragment, V?> = optional(id, viewFinder)
-fun <V : View> android.support.v4.app.DialogFragment.bindOptionalView(id: Int)
+fun <V : View> SupportDialogFragment.bindOptionalView(id: Int)
: ReadOnlyProperty<android.support.v4.app.DialogFragment, V?> = optional(id, viewFinder)
fun <V : View> Fragment.bindOptionalView(id: Int)
: ReadOnlyProperty<Fragment, V?> = optional(id, viewFinder)
-fun <V : View> android.support.v4.app.Fragment.bindOptionalView(id: Int)
+fun <V : View> SupportFragment.bindOptionalView(id: Int)
: ReadOnlyProperty<android.support.v4.app.Fragment, V?> = optional(id, viewFinder)
fun <V : View> ViewHolder.bindOptionalView(id: Int)
@@ -78,13 +81,13 @@ fun <V : View> Dialog.bindViews(vararg ids: Int)
fun <V : View> DialogFragment.bindViews(vararg ids: Int)
: ReadOnlyProperty<DialogFragment, List<V>> = required(ids, viewFinder)
-fun <V : View> android.support.v4.app.DialogFragment.bindViews(vararg ids: Int)
+fun <V : View> SupportDialogFragment.bindViews(vararg ids: Int)
: ReadOnlyProperty<android.support.v4.app.DialogFragment, List<V>> = required(ids, viewFinder)
fun <V : View> Fragment.bindViews(vararg ids: Int)
: ReadOnlyProperty<Fragment, List<V>> = required(ids, viewFinder)
-fun <V : View> android.support.v4.app.Fragment.bindViews(vararg ids: Int)
+fun <V : View> SupportFragment.bindViews(vararg ids: Int)
: ReadOnlyProperty<android.support.v4.app.Fragment, List<V>> = required(ids, viewFinder)
fun <V : View> ViewHolder.bindViews(vararg ids: Int)
@@ -102,13 +105,13 @@ fun <V : View> Dialog.bindOptionalViews(vararg ids: Int)
fun <V : View> DialogFragment.bindOptionalViews(vararg ids: Int)
: ReadOnlyProperty<DialogFragment, List<V>> = optional(ids, viewFinder)
-fun <V : View> android.support.v4.app.DialogFragment.bindOptionalViews(vararg ids: Int)
+fun <V : View> SupportDialogFragment.bindOptionalViews(vararg ids: Int)
: ReadOnlyProperty<android.support.v4.app.DialogFragment, List<V>> = optional(ids, viewFinder)
fun <V : View> Fragment.bindOptionalViews(vararg ids: Int)
: ReadOnlyProperty<Fragment, List<V>> = optional(ids, viewFinder)
-fun <V : View> android.support.v4.app.Fragment.bindOptionalViews(vararg ids: Int)
+fun <V : View> SupportFragment.bindOptionalViews(vararg ids: Int)
: ReadOnlyProperty<android.support.v4.app.Fragment, List<V>> = optional(ids, viewFinder)
fun <V : View> ViewHolder.bindOptionalViews(vararg ids: Int)
@@ -122,11 +125,11 @@ private inline val Dialog.viewFinder: Dialog.(Int) -> View?
get() = { findViewById(it) }
private inline val DialogFragment.viewFinder: DialogFragment.(Int) -> View?
get() = { dialog.findViewById(it) }
-private inline val android.support.v4.app.DialogFragment.viewFinder: android.support.v4.app.DialogFragment.(Int) -> View?
+private inline val SupportDialogFragment.viewFinder: SupportDialogFragment.(Int) -> View?
get() = { dialog.findViewById(it) }
private inline val Fragment.viewFinder: Fragment.(Int) -> View?
get() = { view.findViewById(it) }
-private inline val android.support.v4.app.Fragment.viewFinder: android.support.v4.app.Fragment.(Int) -> View?
+private inline val SupportFragment.viewFinder: SupportFragment.(Int) -> View?
get() = { view!!.findViewById(it) }
private inline val ViewHolder.viewFinder: ViewHolder.(Int) -> View?
get() = { itemView.findViewById(it) }
@@ -134,33 +137,173 @@ private inline val ViewHolder.viewFinder: ViewHolder.(Int) -> View?
private fun viewNotFound(id: Int, desc: KProperty<*>): Nothing =
throw IllegalStateException("View ID $id for '${desc.name}' not found.")
-@Suppress("UNCHECKED_CAST")
private fun <T, V : View> required(id: Int, finder: T.(Int) -> View?)
= Lazy { t: T, desc -> (t.finder(id) as V?)?.apply { } ?: viewNotFound(id, desc) }
-@Suppress("UNCHECKED_CAST")
private fun <T, V : View> optional(id: Int, finder: T.(Int) -> View?)
= Lazy { t: T, _ -> t.finder(id) as V? }
-@Suppress("UNCHECKED_CAST")
private fun <T, V : View> required(ids: IntArray, finder: T.(Int) -> View?)
= Lazy { t: T, desc -> ids.map { t.finder(it) as V? ?: viewNotFound(it, desc) } }
-@Suppress("UNCHECKED_CAST")
private fun <T, V : View> optional(ids: IntArray, finder: T.(Int) -> View?)
= Lazy { t: T, _ -> ids.map { t.finder(it) as V? }.filterNotNull() }
// Like Kotlin's lazy delegate but the initializer gets the target and metadata passed to it
-private class Lazy<T, V>(private val initializer: (T, KProperty<*>) -> V) : ReadOnlyProperty<T, V> {
- private object EMPTY
+private open class Lazy<in T, out V>(private val initializer: (T, KProperty<*>) -> V) : ReadOnlyProperty<T, V> {
+ protected object EMPTY
- private var value: Any? = EMPTY
+ protected var value: Any? = EMPTY
override fun getValue(thisRef: T, property: KProperty<*>): V {
- if (value == EMPTY) {
+ if (value == EMPTY)
value = initializer(thisRef, property)
- }
- @Suppress("UNCHECKED_CAST")
+
return value as V
}
+}
+
+/*
+ * The components below are a variant of the view bindings with lazy resettables
+ * All bindings are weakly held so that they may be reset through KotterknifeRegistry.reset
+ *
+ * This is typically only needed in cases such as Fragments,
+ * where their lifecycle doesn't match that of an Activity or View
+ *
+ * Credits to <a href="https://github.com/MichaelRocks">MichaelRocks</a>
+ */
+
+fun <V : View> View.bindViewResettable(id: Int)
+ : ReadOnlyProperty<View, V> = requiredResettable(id, viewFinder)
+
+fun <V : View> Activity.bindViewResettable(id: Int)
+ : ReadOnlyProperty<Activity, V> = requiredResettable(id, viewFinder)
+
+fun <V : View> Dialog.bindViewResettable(id: Int)
+ : ReadOnlyProperty<Dialog, V> = requiredResettable(id, viewFinder)
+
+fun <V : View> DialogFragment.bindViewResettable(id: Int)
+ : ReadOnlyProperty<DialogFragment, V> = requiredResettable(id, viewFinder)
+
+fun <V : View> SupportDialogFragment.bindViewResettable(id: Int)
+ : ReadOnlyProperty<android.support.v4.app.DialogFragment, V> = requiredResettable(id, viewFinder)
+
+fun <V : View> Fragment.bindViewResettable(id: Int)
+ : ReadOnlyProperty<Fragment, V> = requiredResettable(id, viewFinder)
+
+fun <V : View> SupportFragment.bindViewResettable(id: Int)
+ : ReadOnlyProperty<android.support.v4.app.Fragment, V> = requiredResettable(id, viewFinder)
+
+fun <V : View> ViewHolder.bindViewResettable(id: Int)
+ : ReadOnlyProperty<ViewHolder, V> = requiredResettable(id, viewFinder)
+
+fun <V : View> View.bindOptionalViewResettable(id: Int)
+ : ReadOnlyProperty<View, V?> = optionalResettable(id, viewFinder)
+
+fun <V : View> Activity.bindOptionalViewResettable(id: Int)
+ : ReadOnlyProperty<Activity, V?> = optionalResettable(id, viewFinder)
+
+fun <V : View> Dialog.bindOptionalViewResettable(id: Int)
+ : ReadOnlyProperty<Dialog, V?> = optionalResettable(id, viewFinder)
+
+fun <V : View> DialogFragment.bindOptionalViewResettable(id: Int)
+ : ReadOnlyProperty<DialogFragment, V?> = optionalResettable(id, viewFinder)
+
+fun <V : View> SupportDialogFragment.bindOptionalViewResettable(id: Int)
+ : ReadOnlyProperty<android.support.v4.app.DialogFragment, V?> = optionalResettable(id, viewFinder)
+
+fun <V : View> Fragment.bindOptionalViewResettable(id: Int)
+ : ReadOnlyProperty<Fragment, V?> = optionalResettable(id, viewFinder)
+
+fun <V : View> SupportFragment.bindOptionalViewResettable(id: Int)
+ : ReadOnlyProperty<android.support.v4.app.Fragment, V?> = optionalResettable(id, viewFinder)
+
+fun <V : View> ViewHolder.bindOptionalViewResettable(id: Int)
+ : ReadOnlyProperty<ViewHolder, V?> = optionalResettable(id, viewFinder)
+
+fun <V : View> View.bindViewsResettable(vararg ids: Int)
+ : ReadOnlyProperty<View, List<V>> = requiredResettable(ids, viewFinder)
+
+fun <V : View> Activity.bindViewsResettable(vararg ids: Int)
+ : ReadOnlyProperty<Activity, List<V>> = requiredResettable(ids, viewFinder)
+
+fun <V : View> Dialog.bindViewsResettable(vararg ids: Int)
+ : ReadOnlyProperty<Dialog, List<V>> = requiredResettable(ids, viewFinder)
+
+fun <V : View> DialogFragment.bindViewsResettable(vararg ids: Int)
+ : ReadOnlyProperty<DialogFragment, List<V>> = requiredResettable(ids, viewFinder)
+
+fun <V : View> SupportDialogFragment.bindViewsResettable(vararg ids: Int)
+ : ReadOnlyProperty<android.support.v4.app.DialogFragment, List<V>> = requiredResettable(ids, viewFinder)
+
+fun <V : View> Fragment.bindViewsResettable(vararg ids: Int)
+ : ReadOnlyProperty<Fragment, List<V>> = requiredResettable(ids, viewFinder)
+
+fun <V : View> SupportFragment.bindViewsResettable(vararg ids: Int)
+ : ReadOnlyProperty<android.support.v4.app.Fragment, List<V>> = requiredResettable(ids, viewFinder)
+
+fun <V : View> ViewHolder.bindViewsResettable(vararg ids: Int)
+ : ReadOnlyProperty<ViewHolder, List<V>> = requiredResettable(ids, viewFinder)
+
+fun <V : View> View.bindOptionalViewsResettable(vararg ids: Int)
+ : ReadOnlyProperty<View, List<V>> = optionalResettable(ids, viewFinder)
+
+fun <V : View> Activity.bindOptionalViewsResettable(vararg ids: Int)
+ : ReadOnlyProperty<Activity, List<V>> = optionalResettable(ids, viewFinder)
+
+fun <V : View> Dialog.bindOptionalViewsResettable(vararg ids: Int)
+ : ReadOnlyProperty<Dialog, List<V>> = optionalResettable(ids, viewFinder)
+
+fun <V : View> DialogFragment.bindOptionalViewsResettable(vararg ids: Int)
+ : ReadOnlyProperty<DialogFragment, List<V>> = optionalResettable(ids, viewFinder)
+
+fun <V : View> SupportDialogFragment.bindOptionalViewsResettable(vararg ids: Int)
+ : ReadOnlyProperty<android.support.v4.app.DialogFragment, List<V>> = optionalResettable(ids, viewFinder)
+
+fun <V : View> Fragment.bindOptionalViewsResettable(vararg ids: Int)
+ : ReadOnlyProperty<Fragment, List<V>> = optionalResettable(ids, viewFinder)
+
+fun <V : View> SupportFragment.bindOptionalViewsResettable(vararg ids: Int)
+ : ReadOnlyProperty<android.support.v4.app.Fragment, List<V>> = optionalResettable(ids, viewFinder)
+
+fun <V : View> ViewHolder.bindOptionalViewsResettable(vararg ids: Int)
+ : ReadOnlyProperty<ViewHolder, List<V>> = optionalResettable(ids, viewFinder)
+
+private fun <T, V : View> requiredResettable(id: Int, finder: T.(Int) -> View?)
+ = LazyResettable { t: T, desc -> (t.finder(id) as V?)?.apply { } ?: viewNotFound(id, desc) }
+
+private fun <T, V : View> optionalResettable(id: Int, finder: T.(Int) -> View?)
+ = LazyResettable { t: T, _ -> t.finder(id) as V? }
+
+private fun <T, V : View> requiredResettable(ids: IntArray, finder: T.(Int) -> View?)
+ = LazyResettable { t: T, desc -> ids.map { t.finder(it) as V? ?: viewNotFound(it, desc) } }
+
+private fun <T, V : View> optionalResettable(ids: IntArray, finder: T.(Int) -> View?)
+ = LazyResettable { t: T, _ -> ids.map { t.finder(it) as V? }.filterNotNull() }
+
+//Like Kotterknife's lazy delegate but is resettable
+private class LazyResettable<in T, out V>(initializer: (T, KProperty<*>) -> V) : Lazy<T, V>(initializer) {
+ override fun getValue(thisRef: T, property: KProperty<*>): V {
+ KotterknifeRegistry.register(thisRef!!, this)
+ return super.getValue(thisRef, property)
+ }
+
+ fun reset() {
+ value = EMPTY
+ }
+}
+
+object Kotterknife {
+ fun reset(target: Any) {
+ KotterknifeRegistry.reset(target)
+ }
+}
+
+private object KotterknifeRegistry {
+ private val lazyMap = WeakHashMap<Any, MutableCollection<LazyResettable<*, *>>>()
+
+ fun register(target: Any, lazy: LazyResettable<*, *>)
+ = lazyMap.getOrPut(target, { Collections.newSetFromMap(WeakHashMap()) }).add(lazy)
+
+ fun reset(target: Any) = lazyMap[target]?.forEach { it.reset() }
} \ No newline at end of file
diff --git a/core/src/main/kotlin/ca/allanwang/kau/utils/PackageUtils.kt b/core/src/main/kotlin/ca/allanwang/kau/utils/PackageUtils.kt
index 36bcc93..42d150e 100644
--- a/core/src/main/kotlin/ca/allanwang/kau/utils/PackageUtils.kt
+++ b/core/src/main/kotlin/ca/allanwang/kau/utils/PackageUtils.kt
@@ -1,5 +1,6 @@
package ca.allanwang.kau.utils
+import android.annotation.SuppressLint
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
@@ -31,11 +32,9 @@ import android.os.Build
}
inline val buildIsMarshmallowAndUp: Boolean
-
get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
inline val buildIsLollipopAndUp: Boolean
- @SuppressWarnings("NewApi")
get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
inline val buildIsNougatAndUp: Boolean
diff --git a/core/src/main/kotlin/ca/allanwang/kau/utils/ViewUtils.kt b/core/src/main/kotlin/ca/allanwang/kau/utils/ViewUtils.kt
index ead2cb7..53d711d 100644
--- a/core/src/main/kotlin/ca/allanwang/kau/utils/ViewUtils.kt
+++ b/core/src/main/kotlin/ca/allanwang/kau/utils/ViewUtils.kt
@@ -3,20 +3,21 @@
package ca.allanwang.kau.utils
import android.animation.ValueAnimator
+import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Color
import android.os.Build
-import android.support.annotation.*
+import android.support.annotation.ColorInt
+import android.support.annotation.ColorRes
+import android.support.annotation.RequiresApi
+import android.support.annotation.StringRes
import android.support.design.widget.FloatingActionButton
import android.support.design.widget.Snackbar
import android.support.design.widget.TextInputEditText
-import android.support.transition.AutoTransition
-import android.support.transition.Transition
-import android.support.transition.TransitionInflater
-import android.support.transition.TransitionManager
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
+import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager
@@ -135,7 +136,6 @@ fun FloatingActionButton.hideIf(hide: Boolean) = if (hide) hide() else show()
if (flag and KAU_RIGHT > 0) margin else p.rightMargin,
if (flag and KAU_BOTTOM > 0) margin else p.bottomMargin
)
- requestLayout()
return true
}
@@ -184,7 +184,6 @@ fun FloatingActionButton.hideIf(hide: Boolean) = if (hide) hide() else show()
if (flag and KAU_RIGHT > 0) padding else paddingRight,
if (flag and KAU_BOTTOM > 0) padding else paddingBottom
)
- requestLayout()
}
@KauUtils fun View.hideKeyboard() {
@@ -222,23 +221,19 @@ fun Context.fullLinearRecycler(rvAdapter: RecyclerView.Adapter<*>? = null, confi
}
/**
- * Animate a transition for a FloatinActionButton
+ * Animate a transition a given imageview
* If it is not shown, the action will be invoked directly and the fab will be shown
* If it is already shown, scaling and alpha animations will be added to the action
*/
-inline fun FloatingActionButton.transition(crossinline action: FloatingActionButton.() -> Unit) {
- if (isHidden) {
- action()
- show()
- } else {
+inline fun <T : ImageView> T.fadeScaleTransition(duration: Long = 500L, minScale: Float = 0.7f, crossinline action: T.() -> Unit) {
+ if (!isVisible) action()
+ else {
var transitioned = false
ValueAnimator.ofFloat(1.0f, 0.0f, 1.0f).apply {
- duration = 500L
+ this.duration = duration
addUpdateListener {
val x = it.animatedValue as Float
- val scale = x * 0.3f + 0.7f
- scaleX = scale
- scaleY = scale
+ scaleXY = x * (1 - minScale) + minScale
imageAlpha = (x * 255).toInt()
if (it.animatedFraction > 0.5f && !transitioned) {
transitioned = true
@@ -266,4 +261,29 @@ fun FloatingActionButton.hideOnDownwardsScroll(recycler: RecyclerView) {
else if (dy < 0 && isHidden) show()
}
})
+}
+
+inline var View.scaleXY
+ get() = Math.max(scaleX, scaleY)
+ set(value) {
+ scaleX = value
+ scaleY = value
+ }
+
+/**
+ * Creates an on touch listener that only emits on a short single tap
+ */
+@SuppressLint("ClickableViewAccessibility")
+inline fun View.setOnSingleTapListener(crossinline onSingleTap: (v: View, event: MotionEvent) -> Unit) {
+ setOnTouchListener { v, event ->
+ when (event.actionMasked) {
+ MotionEvent.ACTION_DOWN -> true
+ MotionEvent.ACTION_UP -> {
+ if (event.eventTime - event.downTime < 100)
+ onSingleTap(v, event)
+ true
+ }
+ else -> false
+ }
+ }
} \ No newline at end of file
diff --git a/core/src/main/kotlin/ca/allanwang/kau/changelog/Changelog.kt b/core/src/main/kotlin/ca/allanwang/kau/xml/Changelog.kt
index 43546ae..4bf1836 100644
--- a/core/src/main/kotlin/ca/allanwang/kau/changelog/Changelog.kt
+++ b/core/src/main/kotlin/ca/allanwang/kau/xml/Changelog.kt
@@ -1,4 +1,4 @@
-package ca.allanwang.kau.changelog
+package ca.allanwang.kau.xml
import android.content.Context
import android.content.res.XmlResourceParser
diff --git a/core/src/main/kotlin/ca/allanwang/kau/xml/FAQ.kt b/core/src/main/kotlin/ca/allanwang/kau/xml/FAQ.kt
new file mode 100644
index 0000000..dedfbbf
--- /dev/null
+++ b/core/src/main/kotlin/ca/allanwang/kau/xml/FAQ.kt
@@ -0,0 +1,42 @@
+package ca.allanwang.kau.xml
+
+import android.content.Context
+import android.content.res.XmlResourceParser
+import android.support.annotation.XmlRes
+import android.text.Html
+import android.text.Spanned
+import ca.allanwang.kau.utils.use
+import org.xmlpull.v1.XmlPullParser
+
+/**
+ * Created by Allan Wang on 2017-07-30.
+ */
+
+/**
+ * Parse an xml with two tags, <question>Text</question> and <answer>Text</answer>,
+ * and return them as a list of string pairs
+ */
+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
+ while (eventType != XmlPullParser.END_DOCUMENT) {
+ if (eventType == XmlPullParser.START_TAG) {
+ if (parser.name == "question") {
+ var q = parser.text.replace("\n", "<br/>")
+ if (withNumbering) q = "${items.size + 1}. $q"
+ question = Html.fromHtml(q)
+ } else if (parser.name == "answer") {
+ items.add(Pair(question ?: throw IllegalArgumentException("KAU FAQ answer found without a question"),
+ Html.fromHtml(parser.text.replace("\n", "<br/>"))))
+ question = null
+ }
+ }
+
+ eventType = parser.next()
+ }
+ }
+ return items
+} \ No newline at end of file
diff --git a/core/src/main/res-public/anim/kau_slide_out_left_top.xml b/core/src/main/res-public/anim/kau_slide_out_left_top.xml
new file mode 100644
index 0000000..0707c49
--- /dev/null
+++ b/core/src/main/res-public/anim/kau_slide_out_left_top.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:zAdjustment="top">
+ <translate
+ android:duration="@android:integer/config_shortAnimTime"
+ android:fromXDelta="0"
+ android:toXDelta="-100%p" />
+</set> \ No newline at end of file
diff --git a/core/src/main/res-public/transition-v21/kau_enter_slide_bottom.xml b/core/src/main/res-public/transition-v21/kau_enter_slide_bottom.xml
index 575e189..bbd22c5 100644
--- a/core/src/main/res-public/transition-v21/kau_enter_slide_bottom.xml
+++ b/core/src/main/res-public/transition-v21/kau_enter_slide_bottom.xml
@@ -1,9 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
-<transitionSet
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:transitionOrdering="together"
+<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="400"
- android:interpolator="@android:interpolator/linear_out_slow_in">
+ android:interpolator="@android:interpolator/linear_out_slow_in"
+ android:transitionOrdering="together">
<slide android:slideEdge="bottom">
<targets>
diff --git a/core/src/main/res-public/transition-v21/kau_enter_slide_left.xml b/core/src/main/res-public/transition-v21/kau_enter_slide_left.xml
new file mode 100644
index 0000000..f91a268
--- /dev/null
+++ b/core/src/main/res-public/transition-v21/kau_enter_slide_left.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
+ android:duration="400"
+ android:interpolator="@android:interpolator/linear_out_slow_in"
+ android:transitionOrdering="together">
+
+ <slide android:slideEdge="left">
+ <targets>
+ <target android:excludeId="@android:id/navigationBarBackground" />
+ <target android:excludeId="@android:id/statusBarBackground" />
+ </targets>
+ </slide>
+
+ <fade android:duration="@android:integer/config_mediumAnimTime">
+ <targets>
+ <target android:targetId="@android:id/navigationBarBackground" />
+ <target android:targetId="@android:id/statusBarBackground" />
+ </targets>
+ </fade>
+
+</transitionSet>
diff --git a/core/src/main/res-public/transition-v21/kau_enter_slide_right.xml b/core/src/main/res-public/transition-v21/kau_enter_slide_right.xml
new file mode 100644
index 0000000..3e8c173
--- /dev/null
+++ b/core/src/main/res-public/transition-v21/kau_enter_slide_right.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
+ android:duration="400"
+ android:interpolator="@android:interpolator/linear_out_slow_in"
+ android:transitionOrdering="together">
+
+ <slide android:slideEdge="right">
+ <targets>
+ <target android:excludeId="@android:id/navigationBarBackground" />
+ <target android:excludeId="@android:id/statusBarBackground" />
+ </targets>
+ </slide>
+
+ <fade android:duration="@android:integer/config_mediumAnimTime">
+ <targets>
+ <target android:targetId="@android:id/navigationBarBackground" />
+ <target android:targetId="@android:id/statusBarBackground" />
+ </targets>
+ </fade>
+
+</transitionSet>
diff --git a/core/src/main/res-public/transition-v21/kau_enter_slide_top.xml b/core/src/main/res-public/transition-v21/kau_enter_slide_top.xml
index 8cf613b..91dd5d6 100644
--- a/core/src/main/res-public/transition-v21/kau_enter_slide_top.xml
+++ b/core/src/main/res-public/transition-v21/kau_enter_slide_top.xml
@@ -1,9 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
-<transitionSet
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:transitionOrdering="together"
+<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="400"
- android:interpolator="@android:interpolator/linear_out_slow_in">
+ android:interpolator="@android:interpolator/linear_out_slow_in"
+ android:transitionOrdering="together">
<slide android:slideEdge="top">
<targets>
diff --git a/core/src/main/res-public/transition-v21/kau_exit_slide_bottom.xml b/core/src/main/res-public/transition-v21/kau_exit_slide_bottom.xml
index e0967ec..fa7d152 100644
--- a/core/src/main/res-public/transition-v21/kau_exit_slide_bottom.xml
+++ b/core/src/main/res-public/transition-v21/kau_exit_slide_bottom.xml
@@ -1,12 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
-<transitionSet
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:transitionOrdering="together"
- android:interpolator="@android:interpolator/fast_out_linear_in">
+<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
+ android:interpolator="@android:interpolator/fast_out_linear_in"
+ android:transitionOrdering="together">
<slide
- android:slideEdge="bottom"
- android:duration="400">
+ android:duration="400"
+ android:slideEdge="bottom">
<targets>
<target android:excludeId="@android:id/navigationBarBackground" />
<target android:excludeId="@android:id/statusBarBackground" />
diff --git a/core/src/main/res-public/transition-v21/kau_exit_slide_left.xml b/core/src/main/res-public/transition-v21/kau_exit_slide_left.xml
new file mode 100644
index 0000000..fe76552
--- /dev/null
+++ b/core/src/main/res-public/transition-v21/kau_exit_slide_left.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
+ android:interpolator="@android:interpolator/fast_out_linear_in"
+ android:transitionOrdering="together">
+
+ <slide
+ android:duration="400"
+ android:slideEdge="left">
+ <targets>
+ <target android:excludeId="@android:id/navigationBarBackground" />
+ <target android:excludeId="@android:id/statusBarBackground" />
+ </targets>
+ </slide>
+
+ <fade android:duration="400">
+ <targets>
+ <target android:targetId="@android:id/navigationBarBackground" />
+ <target android:targetId="@android:id/statusBarBackground" />
+ </targets>
+ </fade>
+
+</transitionSet>
diff --git a/core/src/main/res-public/transition-v21/kau_exit_slide_right.xml b/core/src/main/res-public/transition-v21/kau_exit_slide_right.xml
new file mode 100644
index 0000000..12c7503
--- /dev/null
+++ b/core/src/main/res-public/transition-v21/kau_exit_slide_right.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
+ android:interpolator="@android:interpolator/fast_out_linear_in"
+ android:transitionOrdering="together">
+
+ <slide
+ android:duration="400"
+ android:slideEdge="right">
+ <targets>
+ <target android:excludeId="@android:id/navigationBarBackground" />
+ <target android:excludeId="@android:id/statusBarBackground" />
+ </targets>
+ </slide>
+
+ <fade android:duration="400">
+ <targets>
+ <target android:targetId="@android:id/navigationBarBackground" />
+ <target android:targetId="@android:id/statusBarBackground" />
+ </targets>
+ </fade>
+
+</transitionSet>
diff --git a/core/src/main/res-public/transition-v21/kau_exit_slide_top.xml b/core/src/main/res-public/transition-v21/kau_exit_slide_top.xml
index a9849c0..9c81b78 100644
--- a/core/src/main/res-public/transition-v21/kau_exit_slide_top.xml
+++ b/core/src/main/res-public/transition-v21/kau_exit_slide_top.xml
@@ -1,12 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
-<transitionSet
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:transitionOrdering="together"
- android:interpolator="@android:interpolator/fast_out_linear_in">
+<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
+ android:interpolator="@android:interpolator/fast_out_linear_in"
+ android:transitionOrdering="together">
<slide
- android:slideEdge="top"
- android:duration="400">
+ android:duration="400"
+ android:slideEdge="top">
<targets>
<target android:excludeId="@android:id/navigationBarBackground" />
<target android:excludeId="@android:id/statusBarBackground" />
diff --git a/core/src/main/res-public/values/public.xml b/core/src/main/res-public/values/public.xml
index 7057f1f..fb1c9df 100644
--- a/core/src/main/res-public/values/public.xml
+++ b/core/src/main/res-public/values/public.xml
@@ -8,14 +8,19 @@
<public name='kau_slide_in_top' type='anim' />
<public name='kau_slide_out_bottom' type='anim' />
<public name='kau_slide_out_left' type='anim' />
+ <public name='kau_slide_out_left_top' type='anim' />
<public name='kau_slide_out_right' type='anim' />
<public name='kau_slide_out_right_top' type='anim' />
<public name='kau_slide_out_top' type='anim' />
<public name='kau_selectable_white' type='drawable' />
<public name='kau_transparent' type='drawable' />
<public name='kau_enter_slide_bottom' type='transition' />
+ <public name='kau_enter_slide_left' type='transition' />
+ <public name='kau_enter_slide_right' type='transition' />
<public name='kau_enter_slide_top' type='transition' />
<public name='kau_exit_slide_bottom' type='transition' />
+ <public name='kau_exit_slide_left' type='transition' />
+ <public name='kau_exit_slide_right' type='transition' />
<public name='kau_exit_slide_top' type='transition' />
<public name='kau_activity_horizontal_margin' type='dimen' />
<public name='kau_activity_vertical_margin' type='dimen' />
diff --git a/core/src/test/kotlin/ca/allanwang/kau/kotlin/LazyResettableTest.kt b/core/src/test/kotlin/ca/allanwang/kau/kotlin/LazyResettableTest.kt
new file mode 100644
index 0000000..1997bd1
--- /dev/null
+++ b/core/src/test/kotlin/ca/allanwang/kau/kotlin/LazyResettableTest.kt
@@ -0,0 +1,35 @@
+package ca.allanwang.kau.kotlin
+
+import org.junit.Before
+import org.junit.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertNotEquals
+
+/**
+ * Created by Allan Wang on 2017-07-29.
+ */
+class LazyResettableTest {
+
+ lateinit var registry: LazyResettableRegistry
+
+ @Before
+ fun init() {
+ registry = LazyResettableRegistry()
+ }
+
+ @Test
+ fun basic() {
+ val timeDelegate = lazyResettable { System.currentTimeMillis() }
+ val time: Long by timeDelegate
+ registry.add(timeDelegate)
+ val t1 = time
+ Thread.sleep(5)
+ val t2 = time
+ registry.invalidateAll()
+ Thread.sleep(5)
+ val t3 = time
+ assertEquals(t1, t2, "Lazy resettable not returning same value after second call")
+ assertNotEquals(t1, t3, "Lazy resettable not invalidated by registry")
+ }
+
+} \ No newline at end of file
diff --git a/core/src/test/kotlin/ca/allanwang/kprefs/library/UtilsTest.kt b/core/src/test/kotlin/ca/allanwang/kau/utils/UtilsTest.kt
index c2c96e4..071ee9c 100644
--- a/core/src/test/kotlin/ca/allanwang/kprefs/library/UtilsTest.kt
+++ b/core/src/test/kotlin/ca/allanwang/kau/utils/UtilsTest.kt
@@ -1,8 +1,6 @@
-package ca.allanwang.kprefs.library
+package ca.allanwang.kau.utils
import android.graphics.Color
-import ca.allanwang.kau.utils.round
-import ca.allanwang.kau.utils.toHexString
import org.junit.Test
import kotlin.test.assertEquals