aboutsummaryrefslogtreecommitdiff
path: root/core/src/main/kotlin
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/main/kotlin
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/main/kotlin')
-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
12 files changed, 356 insertions, 98 deletions
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