aboutsummaryrefslogtreecommitdiff
path: root/core
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
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')
-rw-r--r--core/README.md33
-rw-r--r--core/build.gradle4
-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
29 files changed, 770 insertions, 126 deletions
diff --git a/core/README.md b/core/README.md
index 63313ac..c26836e 100644
--- a/core/README.md
+++ b/core/README.md
@@ -6,10 +6,13 @@
* [KPrefs](#kprefs)
* [Changelog XML](#changelog)
+* [Kotterknife](#kotterknife)
* [Ripple Canvas](#ripple-canvas)
+* [MeasureSpecDelegate](#measure-spec-delegate)
* [Timber Logger](#timber-logger)
* [Email Builder](#email-builder)
* [Extensions](#extensions)
+* [Lazy Resettable](#lazy-resettable)
<a name="kprefs"></a>
## KPrefs
@@ -103,6 +106,17 @@ Here is a template xml changelog file:
</resources>
```
+<a name="kotterknife"></a>
+## Kotterknife
+
+KAU comes shipped with [Kotterknife](https://github.com/JakeWharton/kotterknife) by Jake Wharton.
+It is a powerful collection of lazy view bindings that only calls the expensive `findViewById` once.
+
+In KAU, there are also resettable versions (suffixed with `Resettable`) for all bindings.
+These variants are weakly held in the private `KotterknifeRegistry` object, and can be used to invalidate the lazy
+values through the `Kotterknife.reset` method. This is typically useful for Fragments, as they do not follow
+the same lifecycle as Activities and Views.
+
<a name="ripple-canvas"></a>
## Ripple Canvas
@@ -114,6 +128,13 @@ They can be used as transitions, or as a toolbar background to replicate the loo
Many ripples can be stacked on top of each other to run at the same time from different locations.
The canvas also supports color fading and direct color setting so it can effectively replace any background.
+<a name="measure-spec-delegate"></a>
+## Measure Spec Delegate
+
+If you ever have a view needing exact aspect ratios with its parent and/or itself, this delegate is here to help.
+Implementing this in any view class unlocks its attributes, giving you three layers of view measuring to ensure exact sizing.
+More information can be found in the [klass file](https://github.com/AllanWang/KAU/blob/master/core/src/main/kotlin/ca/allanwang/kau/ui/views/MeasureSpecDelegate.kt)
+
<a name="timber-logger"></a>
## Timber Logger
@@ -134,4 +155,14 @@ Include your email and subject, along with other optional configurations such as
Note that since KAU depends on [ANKO](https://github.com/Kotlin/anko), all of the extensions in its core package is also in KAU.
KAU's vast collection of extensions is one of its strongest features.
-There are too many to explain here, but you may check out the [utils package](https://github.com/AllanWang/KAU/tree/master/core/src/main/kotlin/ca/allanwang/kau/utils) \ No newline at end of file
+There are too many to explain here, but you may check out the [utils package](https://github.com/AllanWang/KAU/tree/master/core/src/main/kotlin/ca/allanwang/kau/utils)
+
+<a name="lazy-resettable></a>
+## Lazy Resettable
+
+In the spirit of Kotlin's Lazy delegate, KAU supports a resettable version. Calling `lazyResettable` produces the same delegate,
+but with an additional `invalidate` method.
+
+To further simplify this, there is also a `LazyResettableRegistry` class that can be used to hold all resettables.
+The instance can be passed through the `lazyResettable` method, or classes can provide their own extension functions to
+register the delegates by default. \ No newline at end of file
diff --git a/core/build.gradle b/core/build.gradle
index ee413ba..0960d59 100644
--- a/core/build.gradle
+++ b/core/build.gradle
@@ -3,11 +3,7 @@ ext.kauSubModuleMinSdk = project.CORE_MIN_SDK
apply from: '../android-lib.gradle'
dependencies {
- compile fileTree(dir: 'libs', include: ['*.jar'])
- testCompile 'junit:junit:4.12'
-
compile "org.jetbrains.kotlin:kotlin-stdlib:${KOTLIN}"
- testCompile "org.jetbrains.kotlin:kotlin-test-junit:${KOTLIN}"
compile "com.android.support:appcompat-v7:${ANDROID_SUPPORT_LIBS}"
compile "com.android.support:support-v13:${ANDROID_SUPPORT_LIBS}"
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