From a83dd51f545dda905194ea84caed6e2906b0f06f Mon Sep 17 00:00:00 2001 From: AllanWang Date: Sun, 9 Sep 2018 13:06:49 -0400 Subject: Add initial progress animator change --- .../kotlin/ca/allanwang/kau/ui/ProgressAnimator.kt | 167 +++++++++++++++------ docs/Migration.md | 3 + sample/src/main/res/xml/kau_changelog.xml | 2 - 3 files changed, 120 insertions(+), 52 deletions(-) diff --git a/core/src/main/kotlin/ca/allanwang/kau/ui/ProgressAnimator.kt b/core/src/main/kotlin/ca/allanwang/kau/ui/ProgressAnimator.kt index e37e59f..39e8657 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/ui/ProgressAnimator.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/ui/ProgressAnimator.kt @@ -3,7 +3,7 @@ package ca.allanwang.kau.ui import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.animation.ValueAnimator -import android.view.animation.Interpolator +import ca.allanwang.kau.kotlin.kauRemoveIf /** * Created by Allan Wang on 2017-11-10. @@ -13,77 +13,144 @@ import android.view.animation.Interpolator * This differs in that everything can be done with simple listeners, which will be bundled * and added to the backing [ValueAnimator] */ -class ProgressAnimator private constructor(private vararg val values: Float) { +class ProgressAnimator private constructor() : ValueAnimator() { companion object { - inline fun ofFloat(crossinline builder: ProgressAnimator.() -> Unit) = ofFloat(0f, 1f) { builder() } - fun ofFloat(vararg values: Float, builder: ProgressAnimator.() -> Unit) = ProgressAnimator(*values).apply { - builder() - build() + fun ofFloat(builder: ProgressAnimator.() -> Unit): ProgressAnimator = ProgressAnimator().apply { + setFloatValues(0f, 1f) + addUpdateListener { apply(it.animatedValue as Float) } + addListener(object : AnimatorListenerAdapter() { + override fun onAnimationStart(animation: Animator?, isReverse: Boolean) { + isCancelled = false + startActions.runAll() + } + + override fun onAnimationCancel(animation: Animator?) { + isCancelled = true + cancelActions.runAll() + } + + override fun onAnimationEnd(animation: Animator?) { + endActions.runAll() + isCancelled = false + } + }) + apply(builder) + } + + /** + * Gets output of a linear function starting at [start] when [progress] is 0 and [end] when [progress] is 1 at point [progress]. + */ + fun progress(start: Float, end: Float, progress: Float): Float = start + (end - start) * progress + + fun progress(start: Float, end: Float, progress: Float, min: Float, max: Float): Float = when { + min == max -> throw IllegalArgumentException("Progress range cannot be 0 (min == max == $min") + progress <= min -> start + progress >= max -> end + else -> { + val trueProgress = (progress - min) / (max - min) + start + (end - start) * trueProgress + } } + } - private val animators: MutableList<(Float) -> Unit> = mutableListOf() - private val startActions: MutableList<() -> Unit> = mutableListOf() - private val endActions: MutableList<() -> Unit> = mutableListOf() + private val animators: MutableList = mutableListOf() + private val startActions: MutableList = mutableListOf() + private val cancelActions: MutableList = mutableListOf() + private val endActions: MutableList = mutableListOf() + var isCancelled: Boolean = false + private set - var duration: Long = -1L - var interpolator: Interpolator? = null /** - * Add more changes to the [ValueAnimator] before running + * Converts an action to a disposable action */ - var extraConfigs: ValueAnimator.() -> Unit = {} + private fun ProgressAction.asDisposable(): ProgressDisposableAction = { this(it); false } + + private fun ProgressRunnable.asDisposable(): ProgressDisposableRunnable = { this(); false } /** - * Range animator. Multiples the range by the current float progress before emission + * If [condition] applies, run the animator. + * @return [condition] */ - fun withAnimator(from: Float, to: Float, animator: (Float) -> Unit) = animators.add { - val range = to - from - animator(range * it + from) + private fun ProgressAction.runIf(condition: Boolean, progress: Float): Boolean { + if (condition) this(progress) + return condition } - /** - * Standard animator. Emits progress value as is - */ - fun withAnimator(animator: (Float) -> Unit) = animators.add(animator) + private fun MutableList.runAll() = kauRemoveIf { it() } - /** - * Start action to be called once when the animator first begins - */ - fun withStartAction(action: () -> Unit) = startActions.add(action) + internal fun apply(progress: Float) { + animators.kauRemoveIf { it(progress) } + } + + fun withAnimator(action: ProgressAction) = + withDisposableAnimator(action.asDisposable()) /** - * End action to be called once when the animator ends + * Range animator. Multiples the range by the current float progress before emission */ - fun withEndAction(action: () -> Unit) = endActions.add(action) - - fun build() { - ValueAnimator.ofFloat(*values).apply { - if (this@ProgressAnimator.duration > 0L) - duration = this@ProgressAnimator.duration - if (this@ProgressAnimator.interpolator != null) - interpolator = this@ProgressAnimator.interpolator - addUpdateListener { - val progress = it.animatedValue as Float - animators.forEach { it(progress) } + fun withAnimator(from: Float, to: Float, action: ProgressAction) = + withDisposableAnimator(from, to, action.asDisposable()) + + fun withDisposableAnimator(action: ProgressDisposableAction) = animators.add(action) + + fun withDisposableAnimator(from: Float, to: Float, action: ProgressDisposableAction) { + if (to != from) { + animators.add { + action(progress(from, to, it)) } - addListener(object : AnimatorListenerAdapter() { - override fun onAnimationStart(animation: Animator?) { - startActions.forEach { it() } - } + } + } - override fun onAnimationEnd(animation: Animator?) { - endActions.forEach { it() } + fun withRangeAnimator(min: Float, max: Float, start: Float, end: Float, progress: Float, action: ProgressAction) { + if (min >= max) { + throw IllegalArgumentException("Range animator must have min < max; currently min=$min, max=$max") + } + withDisposableAnimator { + when { + it > max -> true + it < min -> false + else -> { + action(progress(start, end, progress, min, max)) + false + } } + } + } - override fun onAnimationCancel(animation: Animator?) { - endActions.forEach { it() } - } - }) - extraConfigs() - start() + fun withPointAnimator(point: Float, action: ProgressAction) { + animators.add { + action.runIf(it >= point, it) } } -} \ No newline at end of file + + fun withDelayedStartAction(skipCount: Int, action: ProgressAction) { + var count = 0 + animators.add { + action.runIf(count++ >= skipCount, it) + } + } + + /** + * Start action to be called once when the animator first begins + */ + fun withStartAction(action: ProgressRunnable) = withDisposableStartAction(action.asDisposable()) + + fun withDisposableStartAction(action: ProgressDisposableRunnable) = startActions.add(action) + + fun withCancelAction(action:ProgressRunnable) = withDisposableCancelAction(action.asDisposable()) + + fun withDisposableCancelAction(action: ProgressDisposableRunnable) = cancelActions.add(action) + + fun withEndAction(action: ProgressRunnable) = withDisposableEndAction(action.asDisposable()) + + fun withDisposableEndAction(action: ProgressDisposableRunnable) = endActions.add(action) +} + +private typealias ProgressAction = (Float) -> Unit +private typealias ProgressDisposableAction = (Float) -> Boolean +private typealias ProgressRunnable = () -> Unit +private typealias ProgressDisposableRunnable = () -> Boolean \ No newline at end of file diff --git a/docs/Migration.md b/docs/Migration.md index 721cd0d..c9e39b8 100644 --- a/docs/Migration.md +++ b/docs/Migration.md @@ -8,6 +8,9 @@ Along with the update to support Android Studio 3.1, a lot of changes have occur * Resource ids can be negatives due to the bit overflow. Instead, `INVALID_ID` has been introduced to signify an unset or invalid id. Methods such as `Context.string(id, fallback)` now check against `INVALID_ID` through equality rather than using an inequality to address this. +* Kotterknife has been deprecated in favour of `kotlin-android-extensions`. See [official docs](https://kotlinlang.org/docs/tutorials/android-plugin.html#view-binding). +* `ProgressAnimator` has been completely rewritten to be an extension of `ValueAnimator`. +This for the most part is not a breaking change, apart from the fact that creating an animator will not start it immediately. # v3.6.0 diff --git a/sample/src/main/res/xml/kau_changelog.xml b/sample/src/main/res/xml/kau_changelog.xml index a5d0ea9..e5ddad5 100644 --- a/sample/src/main/res/xml/kau_changelog.xml +++ b/sample/src/main/res/xml/kau_changelog.xml @@ -12,8 +12,6 @@ - - -- cgit v1.2.3 From d64c47f32931dce1c196f0b7a26ae5d1fd1af72e Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Tue, 11 Sep 2018 13:37:12 -0400 Subject: Add more tests --- core-ui/src/main/res-public/values/public.xml | 2 +- .../kotlin/ca/allanwang/kau/ui/ProgressAnimator.kt | 43 +++++++++---- core/src/main/res-public/values/public.xml | 34 +++++----- .../ca/allanwang/kau/ui/ProgressAnimatorTest.kt | 75 ++++++++++++++++++++++ 4 files changed, 123 insertions(+), 31 deletions(-) create mode 100644 core/src/test/kotlin/ca/allanwang/kau/ui/ProgressAnimatorTest.kt diff --git a/core-ui/src/main/res-public/values/public.xml b/core-ui/src/main/res-public/values/public.xml index 193e088..f46b3eb 100644 --- a/core-ui/src/main/res-public/values/public.xml +++ b/core-ui/src/main/res-public/values/public.xml @@ -1,7 +1,7 @@ - + diff --git a/core/src/main/kotlin/ca/allanwang/kau/ui/ProgressAnimator.kt b/core/src/main/kotlin/ca/allanwang/kau/ui/ProgressAnimator.kt index 39e8657..b98a47e 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/ui/ProgressAnimator.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/ui/ProgressAnimator.kt @@ -3,6 +3,7 @@ package ca.allanwang.kau.ui import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.animation.ValueAnimator +import android.support.annotation.VisibleForTesting import ca.allanwang.kau.kotlin.kauRemoveIf /** @@ -57,12 +58,17 @@ class ProgressAnimator private constructor() : ValueAnimator() { } private val animators: MutableList = mutableListOf() - private val startActions: MutableList = mutableListOf() - private val cancelActions: MutableList = mutableListOf() - private val endActions: MutableList = mutableListOf() + @VisibleForTesting + internal val startActions: MutableList = mutableListOf() + @VisibleForTesting + internal val cancelActions: MutableList = mutableListOf() + @VisibleForTesting + internal val endActions: MutableList = mutableListOf() var isCancelled: Boolean = false private set + val animatorCount get() = animators.size + /** * Converts an action to a disposable action @@ -80,8 +86,10 @@ class ProgressAnimator private constructor() : ValueAnimator() { return condition } - private fun MutableList.runAll() = kauRemoveIf { it() } + @VisibleForTesting + internal fun MutableList.runAll() = kauRemoveIf { it() } + @VisibleForTesting internal fun apply(progress: Float) { animators.kauRemoveIf { it(progress) } } @@ -109,16 +117,16 @@ class ProgressAnimator private constructor() : ValueAnimator() { if (min >= max) { throw IllegalArgumentException("Range animator must have min < max; currently min=$min, max=$max") } - withDisposableAnimator { - when { - it > max -> true - it < min -> false - else -> { - action(progress(start, end, progress, min, max)) - false - } + withDisposableAnimator { + when { + it > max -> true + it < min -> false + else -> { + action(progress(start, end, progress, min, max)) + false } } + } } fun withPointAnimator(point: Float, action: ProgressAction) { @@ -141,13 +149,22 @@ class ProgressAnimator private constructor() : ValueAnimator() { fun withDisposableStartAction(action: ProgressDisposableRunnable) = startActions.add(action) - fun withCancelAction(action:ProgressRunnable) = withDisposableCancelAction(action.asDisposable()) + fun withCancelAction(action: ProgressRunnable) = withDisposableCancelAction(action.asDisposable()) fun withDisposableCancelAction(action: ProgressDisposableRunnable) = cancelActions.add(action) fun withEndAction(action: ProgressRunnable) = withDisposableEndAction(action.asDisposable()) fun withDisposableEndAction(action: ProgressDisposableRunnable) = endActions.add(action) + + fun reset() { + if (isRunning) cancel() + animators.clear() + startActions.clear() + cancelActions.clear() + endActions.clear() + isCancelled = false + } } private typealias ProgressAction = (Float) -> Unit diff --git a/core/src/main/res-public/values/public.xml b/core/src/main/res-public/values/public.xml index 2163b10..ea8ed73 100644 --- a/core/src/main/res-public/values/public.xml +++ b/core/src/main/res-public/values/public.xml @@ -1,27 +1,19 @@ - - - - - - - - + + - + + + + + + - - - - - - - - + @@ -115,4 +107,12 @@ + + + + + + + + \ No newline at end of file diff --git a/core/src/test/kotlin/ca/allanwang/kau/ui/ProgressAnimatorTest.kt b/core/src/test/kotlin/ca/allanwang/kau/ui/ProgressAnimatorTest.kt new file mode 100644 index 0000000..60f8680 --- /dev/null +++ b/core/src/test/kotlin/ca/allanwang/kau/ui/ProgressAnimatorTest.kt @@ -0,0 +1,75 @@ +package ca.allanwang.kau.ui + +import org.junit.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class ProgressAnimatorTest { + + private fun ProgressAnimator.test() { + startActions.runAll() + var value = 0f + while (value < 1f) { + apply(value) + value += 0.05f + } + apply(1f) + endActions.runAll() + } + + @Test + fun `basic run`() { + var i = 0f + ProgressAnimator.ofFloat { + withAnimator { + i = it + } + }.test() + assertEquals(1f, i) + } + + @Test + fun `start end hooks`() { + var i = 0 + ProgressAnimator.ofFloat { + withStartAction { i = 1 } + withDisposableAnimator { assertEquals(1, i); true } + withEndAction { + assertEquals(0, animatorCount, "Disposable animator not removed") + i = 2 + } + }.test() + assertEquals(2, i) + } + + @Test + fun `disposable actions`() { + var i = 0f + ProgressAnimator.ofFloat { + withDisposableAnimator { + i = if (it < 0.5f) it else 0.5f + i > 0.5f + } + withAnimator { + assertEquals(Math.min(it, 0.5f), i) + } + }.test() + } + + @Test + fun `point action`() { + var called = false + var i = 0f + ProgressAnimator.ofFloat { + withPointAnimator(0.5f) { + assertFalse(called) + i = it + called = true + } + }.test() + assertTrue(called) + assertTrue(i > 0.5f) + } + +} \ No newline at end of file -- cgit v1.2.3 From 4b38bf156c67e2d6b3a649624bbcb12edbb52232 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Wed, 26 Sep 2018 08:57:25 -0400 Subject: Update kotlin version --- buildSrc/src/main/groovy/ca/allanwang/kau/Versions.groovy | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/buildSrc/src/main/groovy/ca/allanwang/kau/Versions.groovy b/buildSrc/src/main/groovy/ca/allanwang/kau/Versions.groovy index 936e22b..7e1ff2c 100644 --- a/buildSrc/src/main/groovy/ca/allanwang/kau/Versions.groovy +++ b/buildSrc/src/main/groovy/ca/allanwang/kau/Versions.groovy @@ -9,13 +9,13 @@ class Versions { static def buildTools = '28.0.2' // https://developer.android.com/topic/libraries/support-library/revisions - static def supportLibs = '27.1.1' + static def supportLibs = '28.0.0-rc02' // https://kotlinlang.org/docs/reference/using-gradle.html - static def kotlin = '1.2.61' + static def kotlin = '1.2.71' // https://github.com/mikepenz/AboutLibraries/releases - static def aboutLibraries = '6.0.8' + static def aboutLibraries = '6.1.1' // https://github.com/Kotlin/anko/releases static def anko = '0.10.5' @@ -45,7 +45,7 @@ class Versions { static def junit = '4.12' static def testRunner = '1.0.1' - static def gradlePlugin = '3.2.0-rc02' + static def gradlePlugin = '3.2.0-rc03' static def mavenPlugin = '2.1' static def playPublishPlugin = '1.2.2' -- cgit v1.2.3 From f37d7782b7d0269755348b41bfa63dbd50593a6b Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Wed, 26 Sep 2018 09:49:46 -0400 Subject: Update versions and begin kpref update --- .../main/groovy/ca/allanwang/kau/Versions.groovy | 8 +-- .../kotlin/ca/allanwang/kau/ui/views/CutoutView.kt | 2 +- .../kotlin/ca/allanwang/kau/email/EmailBuilder.kt | 2 +- .../kotlin/ca/allanwang/kau/kpref/KPrefDelegate.kt | 66 +++++++++++----------- .../ca/allanwang/kau/kpref/KPrefTransaction.kt | 64 +++++++++++++++++++++ 5 files changed, 102 insertions(+), 40 deletions(-) create mode 100644 core/src/main/kotlin/ca/allanwang/kau/kpref/KPrefTransaction.kt diff --git a/buildSrc/src/main/groovy/ca/allanwang/kau/Versions.groovy b/buildSrc/src/main/groovy/ca/allanwang/kau/Versions.groovy index 7e1ff2c..33da690 100644 --- a/buildSrc/src/main/groovy/ca/allanwang/kau/Versions.groovy +++ b/buildSrc/src/main/groovy/ca/allanwang/kau/Versions.groovy @@ -9,7 +9,7 @@ class Versions { static def buildTools = '28.0.2' // https://developer.android.com/topic/libraries/support-library/revisions - static def supportLibs = '28.0.0-rc02' + static def supportLibs = '28.0.0' // https://kotlinlang.org/docs/reference/using-gradle.html static def kotlin = '1.2.71' @@ -27,14 +27,14 @@ class Versions { static def constraintLayout = '1.1.3' // https://github.com/mikepenz/FastAdapter#using-maven - static def fastAdapter = '3.2.5' + static def fastAdapter = '3.2.9' static def fastAdapterCommons = fastAdapter // https://github.com/bumptech/glide/releases static def glide = '4.7.1' // https://github.com/mikepenz/Android-Iconics#1-provide-the-gradle-dependency - static def iconics = '3.0.3' + static def iconics = '3.0.4' static def iconicsGoogle = '3.0.1.2' static def iconicsMaterial = '2.2.0.4' static def iconicsCommunity = '2.0.46.1' @@ -45,7 +45,7 @@ class Versions { static def junit = '4.12' static def testRunner = '1.0.1' - static def gradlePlugin = '3.2.0-rc03' + static def gradlePlugin = '3.2.0' static def mavenPlugin = '2.1' static def playPublishPlugin = '1.2.2' diff --git a/core-ui/src/main/kotlin/ca/allanwang/kau/ui/views/CutoutView.kt b/core-ui/src/main/kotlin/ca/allanwang/kau/ui/views/CutoutView.kt index eeaac6e..f6f7864 100644 --- a/core-ui/src/main/kotlin/ca/allanwang/kau/ui/views/CutoutView.kt +++ b/core-ui/src/main/kotlin/ca/allanwang/kau/ui/views/CutoutView.kt @@ -82,7 +82,7 @@ class CutoutView @JvmOverloads constructor( if (attrs != null) { val a = context.obtainStyledAttributes(attrs, R.styleable.CutoutView, 0, 0) if (a.hasValue(R.styleable.CutoutView_font)) - paint.typeface = context.getFont(a.getString(R.styleable.CutoutView_font)) + paint.typeface = context.getFont(a.getString(R.styleable.CutoutView_font)!!) foregroundColor = a.getColor(R.styleable.CutoutView_foregroundColor, foregroundColor) text = a.getString(R.styleable.CutoutView_android_text) ?: text minHeight = a.getDimension(R.styleable.CutoutView_android_minHeight, minHeight) diff --git a/core/src/main/kotlin/ca/allanwang/kau/email/EmailBuilder.kt b/core/src/main/kotlin/ca/allanwang/kau/email/EmailBuilder.kt index 804eacb..c94b06d 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/email/EmailBuilder.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/email/EmailBuilder.kt @@ -71,7 +71,7 @@ class EmailBuilder(val email: String, val subject: String) { val appInfo = context.packageManager.getPackageInfo(context.packageName, 0) emailBuilder.append("\nApp: ").append(context.packageName) .append("\nApp Version Name: ").append(appInfo.versionName) - .append("\nApp Version Code: ").append(appInfo.versionCode).append("\n") + .append("\nApp Version Code: ").append(appInfo.longVersionCode).append("\n") } catch (e: PackageManager.NameNotFoundException) { KL.e { "EmailBuilder packageInfo not found" } } 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 33ba807..9dbca49 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/kpref/KPrefDelegate.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/kpref/KPrefDelegate.kt @@ -3,15 +3,26 @@ package ca.allanwang.kau.kpref import ca.allanwang.kau.kotlin.ILazyResettable -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) -fun KPref.kpref(key: String, fallback: Float, postSetter: (value: Float) -> Unit = {}) = KPrefDelegate(key, fallback, this, postSetter) -fun KPref.kpref(key: String, fallback: Int, postSetter: (value: Int) -> Unit = {}) = KPrefDelegate(key, fallback, this, postSetter) -fun KPref.kpref(key: String, fallback: Long, postSetter: (value: Long) -> Unit = {}) = KPrefDelegate(key, fallback, this, postSetter) -fun KPref.kpref(key: String, fallback: Set, postSetter: (value: Set) -> Unit = {}) = KPrefDelegate(key, StringSet(fallback), this, postSetter) -fun KPref.kpref(key: String, fallback: String, postSetter: (value: String) -> Unit = {}) = KPrefDelegate(key, fallback, this, postSetter) +fun KPref.kpref(key: String, fallback: Boolean, postSetter: (value: Boolean) -> Unit = {}) = + KPrefDelegate(key, fallback, this, KPrefBooleanTransaction, postSetter) -class StringSet(set: Collection) : LinkedHashSet(set) +fun KPref.kpref(key: String, fallback: Float, postSetter: (value: Float) -> Unit = {}) = + KPrefDelegate(key, fallback, this, KPrefFloatTransaction, postSetter) + +fun KPref.kpref(key: String, fallback: Double, postSetter: (value: Float) -> Unit = {}) = + kpref(key, fallback.toFloat(), postSetter) + +fun KPref.kpref(key: String, fallback: Int, postSetter: (value: Int) -> Unit = {}) = + KPrefDelegate(key, fallback, this, KPrefIntTransaction, postSetter) + +fun KPref.kpref(key: String, fallback: Long, postSetter: (value: Long) -> Unit = {}) = + KPrefDelegate(key, fallback, this, KPrefLongTransaction, postSetter) + +fun KPref.kpref(key: String, fallback: Set?, postSetter: (value: Set) -> Unit = {}) = + KPrefDelegate(key, fallback, this, KPrefSetTransaction) { postSetter(it ?: emptySet()) } + +fun KPref.kpref(key: String, fallback: String, postSetter: (value: String) -> Unit = {}) = + KPrefDelegate(key, fallback, this, KPrefStringTransaction, postSetter) /** * Created by Allan Wang on 2017-06-07. @@ -20,19 +31,24 @@ class StringSet(set: Collection) : LinkedHashSet(set) * Contains a unique key for the shared preference as well as a nonnull fallback item * Also contains an optional mutable postSetter that will be called every time a new value is given */ -class KPrefDelegate internal constructor( - private val key: String, private val fallback: T, private val pref: KPref, private var postSetter: (value: T) -> Unit = {}, lock: Any? = null +class KPrefDelegate internal constructor( + private val key: String, + private val fallback: T, + private val pref: KPref, + private val transaction: KPrefTransaction, + private var postSetter: (value: T) -> Unit = {} ) : ILazyResettable { private object UNINITIALIZED - @Volatile private var _value: Any = UNINITIALIZED - private val lock = lock ?: this + @Volatile + private var _value: Any? = UNINITIALIZED + private val lock = this init { if (pref.prefMap.containsKey(key)) throw KPrefException("$key is already used elsewhere in preference ${pref.PREFERENCE_NAME}") - pref.prefMap.put(key, this@KPrefDelegate) + pref.prefMap[key] = this@KPrefDelegate } override fun invalidate() { @@ -52,15 +68,7 @@ class KPrefDelegate internal constructor( @Suppress("UNCHECKED_CAST") _v2 as T } else { - _value = when (fallback) { - is Boolean -> pref.sp.getBoolean(key, fallback) - is Float -> pref.sp.getFloat(key, fallback) - is Int -> pref.sp.getInt(key, fallback) - is Long -> pref.sp.getLong(key, fallback) - is StringSet -> StringSet(pref.sp.getStringSet(key, fallback)) - is String -> pref.sp.getString(key, fallback) - else -> throw KPrefException(fallback) - } + _value = transaction.get(pref.sp, key, fallback) @Suppress("UNCHECKED_CAST") _value as T } @@ -74,20 +82,10 @@ class KPrefDelegate internal constructor( operator fun setValue(any: Any, property: kotlin.reflect.KProperty<*>, t: T) { _value = t val editor = pref.sp.edit() - when (t) { - is Boolean -> editor.putBoolean(key, t) - is Float -> editor.putFloat(key, t) - is Int -> editor.putInt(key, t) - is Long -> editor.putLong(key, t) - is StringSet -> editor.putStringSet(key, t) - is String -> editor.putString(key, t) - else -> throw KPrefException(t) - } + transaction.set(editor, key, t) editor.apply() postSetter(t) } } -class KPrefException(message: String) : IllegalAccessException(message) { - constructor(element: Any?) : this("Invalid type in pref cache: ${element?.javaClass?.simpleName ?: "null"}") -} \ No newline at end of file +class KPrefException(message: String) : IllegalAccessException(message) \ No newline at end of file diff --git a/core/src/main/kotlin/ca/allanwang/kau/kpref/KPrefTransaction.kt b/core/src/main/kotlin/ca/allanwang/kau/kpref/KPrefTransaction.kt new file mode 100644 index 0000000..d02cf06 --- /dev/null +++ b/core/src/main/kotlin/ca/allanwang/kau/kpref/KPrefTransaction.kt @@ -0,0 +1,64 @@ +package ca.allanwang.kau.kpref + +import android.content.SharedPreferences + +internal interface KPrefTransaction { + fun get(prefs: SharedPreferences, key: String, fallback: T): T + fun set(editor: SharedPreferences.Editor, key: String, data: T) +} + +internal object KPrefBooleanTransaction : KPrefTransaction { + override fun get(prefs: SharedPreferences, key: String, fallback: Boolean) = + prefs.getBoolean(key, fallback) + + override fun set(editor: SharedPreferences.Editor, key: String, data: Boolean) { + editor.putBoolean(key, data) + } +} + +internal object KPrefIntTransaction : KPrefTransaction { + override fun get(prefs: SharedPreferences, key: String, fallback: Int) = + prefs.getInt(key, fallback) + + override fun set(editor: SharedPreferences.Editor, key: String, data: Int) { + editor.putInt(key, data) + } +} + +internal object KPrefLongTransaction : KPrefTransaction { + override fun get(prefs: SharedPreferences, key: String, fallback: Long) = + prefs.getLong(key, fallback) + + override fun set(editor: SharedPreferences.Editor, key: String, data: Long) { + editor.putLong(key, data) + } +} + +internal object KPrefFloatTransaction : KPrefTransaction { + override fun get(prefs: SharedPreferences, key: String, fallback: Float) = + prefs.getFloat(key, fallback) + + override fun set(editor: SharedPreferences.Editor, key: String, data: Float) { + editor.putFloat(key, data) + } +} + +internal object KPrefStringTransaction : KPrefTransaction { + override fun get(prefs: SharedPreferences, key: String, fallback: String) = + prefs.getString(key, fallback) + + override fun set(editor: SharedPreferences.Editor, key: String, data: String) { + editor.putString(key, data) + } +} + +internal object KPrefSetTransaction : KPrefTransaction?> { + override fun get(prefs: SharedPreferences, key: String, fallback: Set?) = + prefs.getStringSet(key, fallback) + + override fun set(editor: SharedPreferences.Editor, key: String, data: Set?) { + editor.putStringSet(key, data) + } +} + + -- cgit v1.2.3 From 101e025f37828a9e4100dde899fc03e0a6054aac Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Wed, 26 Sep 2018 09:53:48 -0400 Subject: Deprecate kpref double --- core/src/main/kotlin/ca/allanwang/kau/kpref/KPrefDelegate.kt | 3 +++ 1 file changed, 3 insertions(+) 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 9dbca49..2630884 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/kpref/KPrefDelegate.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/kpref/KPrefDelegate.kt @@ -9,6 +9,9 @@ fun KPref.kpref(key: String, fallback: Boolean, postSetter: (value: Boolean) -> fun KPref.kpref(key: String, fallback: Float, postSetter: (value: Float) -> Unit = {}) = KPrefDelegate(key, fallback, this, KPrefFloatTransaction, postSetter) +@Deprecated("Double is not supported in SharedPreferences; cast to float yourself", + ReplaceWith("kpref(key, fallback.toFloat(), postSetter)"), + DeprecationLevel.WARNING) fun KPref.kpref(key: String, fallback: Double, postSetter: (value: Float) -> Unit = {}) = kpref(key, fallback.toFloat(), postSetter) -- cgit v1.2.3 From 3ac791b744a56138501c9530fa6df8dcb0756739 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Wed, 26 Sep 2018 10:21:30 -0400 Subject: Update kpref tests and migration --- .../kotlin/ca/allanwang/kau/kpref/KPrefTest.kt | 26 ++++++++++++++-------- .../kotlin/ca/allanwang/kau/email/EmailBuilder.kt | 7 +++++- .../kotlin/ca/allanwang/kau/kpref/KPrefDelegate.kt | 4 ++-- .../ca/allanwang/kau/kpref/KPrefTransaction.kt | 8 +++---- docs/Migration.md | 1 + 5 files changed, 30 insertions(+), 16 deletions(-) diff --git a/core/src/androidTest/kotlin/ca/allanwang/kau/kpref/KPrefTest.kt b/core/src/androidTest/kotlin/ca/allanwang/kau/kpref/KPrefTest.kt index e806a3f..2a9263a 100644 --- a/core/src/androidTest/kotlin/ca/allanwang/kau/kpref/KPrefTest.kt +++ b/core/src/androidTest/kotlin/ca/allanwang/kau/kpref/KPrefTest.kt @@ -16,40 +16,46 @@ import kotlin.test.assertTrue */ @RunWith(AndroidJUnit4::class) @MediumTest -class ChangelogTest { +class KPrefTest { lateinit var pref: TestPref class TestPref : KPref() { + init { initialize(InstrumentationRegistry.getTargetContext(), "kpref_test_${System.currentTimeMillis()}") } - var one: Int by kpref("one", 1) + var one by kpref("one", 1) + + var two by kpref("two", 2f) - var `true`: Boolean by kpref("true", true) + var `true` by kpref("true", true) - var hello: String by kpref("hello", "hello") + var hello by kpref("hello", "hello") - var set: StringSet by kpref("set", setOf("po", "ta", "to")) + var set by kpref("set", setOf("po", "ta", "to")) - val oneShot: Boolean by kprefSingle("asdf") + val oneShot by kprefSingle("asdf") } @Before fun init() { pref = TestPref() + pref.sp.edit().clear().commit() } @Test fun getDefaults() { assertEquals(1, pref.one) + assertEquals(2f, pref.two) assertEquals(true, pref.`true`) assertEquals("hello", pref.hello) assertEquals(3, pref.set.size) assertTrue(pref.set.contains("po")) assertTrue(pref.set.contains("ta")) assertTrue(pref.set.contains("to")) + assertEquals(0, pref.sp.all.size, "Defaults should not be set automatically") } @Test @@ -60,6 +66,7 @@ class ChangelogTest { pref.hello = "goodbye" assertEquals("goodbye", pref.hello) assertEquals(pref.hello, pref.sp.getString("hello", "hello")) + assertEquals(2, pref.sp.all.size) } @SuppressLint("CommitPrefEdits") @@ -67,12 +74,13 @@ class ChangelogTest { fun reset() { pref.one = 2 assertEquals(2, pref.one) + assertEquals(6, pref.prefMap.size, "Prefmap does not have all elements") pref.reset() //only invalidates our lazy delegate; doesn't change the actual pref - assertEquals(2, pref.one) + assertEquals(2, pref.one, "Kpref did not properly fetch from shared prefs") pref.sp.edit().putInt("one", -1).commit() - assertEquals(2, pref.one) //our lazy delegate still retains the old value + assertEquals(2, pref.one, "Lazy kpref should still retain old value") pref.reset() - assertEquals(-1, pref.one) //back in sync with sp + assertEquals(-1, pref.one, "Kpref did not refetch from shared prefs upon reset") } diff --git a/core/src/main/kotlin/ca/allanwang/kau/email/EmailBuilder.kt b/core/src/main/kotlin/ca/allanwang/kau/email/EmailBuilder.kt index c94b06d..dbdcf09 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/email/EmailBuilder.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/email/EmailBuilder.kt @@ -69,9 +69,14 @@ class EmailBuilder(val email: String, val subject: String) { if (appInfo) { try { val appInfo = context.packageManager.getPackageInfo(context.packageName, 0) + val versionCode = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + appInfo.longVersionCode.toString() + } else { + appInfo.versionCode.toString() + } emailBuilder.append("\nApp: ").append(context.packageName) .append("\nApp Version Name: ").append(appInfo.versionName) - .append("\nApp Version Code: ").append(appInfo.longVersionCode).append("\n") + .append("\nApp Version Code: ").append(versionCode).append("\n") } catch (e: PackageManager.NameNotFoundException) { KL.e { "EmailBuilder packageInfo not found" } } 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 2630884..b80479e 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/kpref/KPrefDelegate.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/kpref/KPrefDelegate.kt @@ -21,8 +21,8 @@ fun KPref.kpref(key: String, fallback: Int, postSetter: (value: Int) -> Unit = { fun KPref.kpref(key: String, fallback: Long, postSetter: (value: Long) -> Unit = {}) = KPrefDelegate(key, fallback, this, KPrefLongTransaction, postSetter) -fun KPref.kpref(key: String, fallback: Set?, postSetter: (value: Set) -> Unit = {}) = - KPrefDelegate(key, fallback, this, KPrefSetTransaction) { postSetter(it ?: emptySet()) } +fun KPref.kpref(key: String, fallback: Set, postSetter: (value: Set) -> Unit = {}) = + KPrefDelegate(key, fallback, this, KPrefSetTransaction, postSetter) fun KPref.kpref(key: String, fallback: String, postSetter: (value: String) -> Unit = {}) = KPrefDelegate(key, fallback, this, KPrefStringTransaction, postSetter) diff --git a/core/src/main/kotlin/ca/allanwang/kau/kpref/KPrefTransaction.kt b/core/src/main/kotlin/ca/allanwang/kau/kpref/KPrefTransaction.kt index d02cf06..773d208 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/kpref/KPrefTransaction.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/kpref/KPrefTransaction.kt @@ -52,11 +52,11 @@ internal object KPrefStringTransaction : KPrefTransaction { } } -internal object KPrefSetTransaction : KPrefTransaction?> { - override fun get(prefs: SharedPreferences, key: String, fallback: Set?) = - prefs.getStringSet(key, fallback) +internal object KPrefSetTransaction : KPrefTransaction> { + override fun get(prefs: SharedPreferences, key: String, fallback: Set) = + prefs.getStringSet(key, fallback)!! - override fun set(editor: SharedPreferences.Editor, key: String, data: Set?) { + override fun set(editor: SharedPreferences.Editor, key: String, data: Set) { editor.putStringSet(key, data) } } diff --git a/docs/Migration.md b/docs/Migration.md index c9e39b8..ce27225 100644 --- a/docs/Migration.md +++ b/docs/Migration.md @@ -11,6 +11,7 @@ Methods such as `Context.string(id, fallback)` now check against `INVALID_ID` th * Kotterknife has been deprecated in favour of `kotlin-android-extensions`. See [official docs](https://kotlinlang.org/docs/tutorials/android-plugin.html#view-binding). * `ProgressAnimator` has been completely rewritten to be an extension of `ValueAnimator`. This for the most part is not a breaking change, apart from the fact that creating an animator will not start it immediately. +* KPref has been slightly refactored internally. Preferences backed by `StringSet` can now go back to `Set` # v3.6.0 -- cgit v1.2.3 From 5b3762b3e04042447b9bf2758c37237d0c8af1b6 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Wed, 26 Sep 2018 10:24:28 -0400 Subject: Suppress version code deprecation --- core/src/main/kotlin/ca/allanwang/kau/email/EmailBuilder.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/main/kotlin/ca/allanwang/kau/email/EmailBuilder.kt b/core/src/main/kotlin/ca/allanwang/kau/email/EmailBuilder.kt index dbdcf09..2b9d91c 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/email/EmailBuilder.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/email/EmailBuilder.kt @@ -51,7 +51,7 @@ class EmailBuilder(val email: String, val subject: String) { val emailBuilder = StringBuilder() emailBuilder.append(message).append("\n\n") if (deviceDetails) { - val deviceItems = mutableMapOf( + val deviceItems = linkedMapOf( "OS Version" to "${System.getProperty("os.version")} (${Build.VERSION.INCREMENTAL})", "OS SDK" to Build.VERSION.SDK_INT, "Device (Manufacturer)" to "${Build.DEVICE} (${Build.MANUFACTURER})", @@ -72,6 +72,7 @@ class EmailBuilder(val email: String, val subject: String) { val versionCode = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { appInfo.longVersionCode.toString() } else { + @Suppress("DEPRECATION") appInfo.versionCode.toString() } emailBuilder.append("\nApp: ").append(context.packageName) -- cgit v1.2.3 From fc799370e3aba1797648dd0b68a0f529d018051e Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Mon, 8 Oct 2018 15:51:57 -0400 Subject: Update travis --- .travis.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 22743c3..3dd7449 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,9 +3,11 @@ jdk: - oraclejdk8 env: global: - - ANDROID_API=27 + - ANDROID_API=28 - EMULATOR_API=24 - - ANDROID_BUILD_TOOLS=27.0.3 + - ANDROID_BUILD_TOOLS=28.0.3 +git: + depth: 500 android: components: - tools -- cgit v1.2.3 From be1d7bef2299824ebf9f4f6250838c3d215913cd Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Mon, 8 Oct 2018 15:52:39 -0400 Subject: Update migration doc --- docs/Migration.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/Migration.md b/docs/Migration.md index fe04270..e112850 100644 --- a/docs/Migration.md +++ b/docs/Migration.md @@ -11,10 +11,6 @@ Along with the update to support Android Studio 3.1, a lot of changes have occur Resource ids can be negatives due to the bit overflow. Instead, `INVALID_ID` has been introduced to signify an unset or invalid id. Methods such as `Context.string(id, fallback)` now check against `INVALID_ID` through equality rather than using an inequality to address this. -* Kotterknife has been deprecated in favour of `kotlin-android-extensions`. See [official docs](https://kotlinlang.org/docs/tutorials/android-plugin.html#view-binding). -* `ProgressAnimator` has been completely rewritten to be an extension of `ValueAnimator`. -This for the most part is not a breaking change, apart from the fact that creating an animator will not start it immediately. -* KPref has been slightly refactored internally. Preferences backed by `StringSet` can now go back to `Set` ## Deprecate Kotterknife @@ -26,6 +22,11 @@ See [official docs](https://kotlinlang.org/docs/tutorials/android-plugin.html#vi KPref has been slightly refactored internally. Preferences backed by `StringSet` can now go back to `Set` +## Update ProgressAnimator + +`ProgressAnimator` has been completely rewritten to be an extension of `ValueAnimator`. +This for the most part is not a breaking change, apart from the fact that creating an animator will not start it immediately. + # v3.6.0 ## startActivity -- cgit v1.2.3 From 81739fa76510003b2acc51822d1e9258ddc8d536 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Mon, 8 Oct 2018 15:56:52 -0400 Subject: Move progress animator migration to 4.0.0 --- docs/Migration.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/Migration.md b/docs/Migration.md index e112850..ea30c0d 100644 --- a/docs/Migration.md +++ b/docs/Migration.md @@ -2,6 +2,13 @@ Below are some highlights on major refactoring/breaking changes +# v4.0.0 + +## Update ProgressAnimator + +`ProgressAnimator` has been completely rewritten to be an extension of `ValueAnimator`. +This for the most part is not a breaking change, apart from the fact that creating an animator will not start it immediately. + # v3.8.0 Along with the update to support Android Studio 3.1, a lot of changes have occurred with other dependencies and with lint. @@ -22,11 +29,6 @@ See [official docs](https://kotlinlang.org/docs/tutorials/android-plugin.html#vi KPref has been slightly refactored internally. Preferences backed by `StringSet` can now go back to `Set` -## Update ProgressAnimator - -`ProgressAnimator` has been completely rewritten to be an extension of `ValueAnimator`. -This for the most part is not a breaking change, apart from the fact that creating an animator will not start it immediately. - # v3.6.0 ## startActivity -- cgit v1.2.3 From fdbbe45e1d02a836fc40e919c6c9f13f3e008b71 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Mon, 8 Oct 2018 16:05:03 -0400 Subject: Update public xml generator --- artifacts.gradle | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/artifacts.gradle b/artifacts.gradle index c0c344a..ab291f7 100644 --- a/artifacts.gradle +++ b/artifacts.gradle @@ -31,8 +31,6 @@ artifacts { // We assume resources within res-public are public task generatepublicxml { - println "Generating public XML" - def resDir = project.projectDir.absolutePath + "/src/main/res-public" def publicFolder = file(resDir + "/values") @@ -51,10 +49,12 @@ task generatepublicxml { '**/values/*.xml' ], exclude: '**/public.xml' - ); + ) + + println "Generating public XML: ${project.name}" // Create new public.xml with writer - new File(resDir + "/values/public.xml").withWriter { writer -> + file(resDir + "/values/public.xml").withWriter { writer -> // Create MarkupBuilder with 4 space indent def destXml = new MarkupBuilder(new IndentPrinter(writer, " ", true)); def destXmlMkp = destXml.getMkp(); -- cgit v1.2.3 From 11f0d0fed07806965555490836d564d042c7b3ec Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Mon, 8 Oct 2018 16:11:01 -0400 Subject: Update versions --- buildSrc/src/main/groovy/ca/allanwang/kau/Versions.groovy | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/buildSrc/src/main/groovy/ca/allanwang/kau/Versions.groovy b/buildSrc/src/main/groovy/ca/allanwang/kau/Versions.groovy index e055c46..49efbe7 100644 --- a/buildSrc/src/main/groovy/ca/allanwang/kau/Versions.groovy +++ b/buildSrc/src/main/groovy/ca/allanwang/kau/Versions.groovy @@ -51,6 +51,7 @@ class Versions { static def playPublishPlugin = '1.2.2' // https://github.com/KeepSafe/dexcount-gradle-plugin/releases - static def dexCountPlugin = '0.8.3' - static def gitVersionPlugin = '0.4.4' + static def dexCountPlugin = '0.8.4' + // https://github.com/gladed/gradle-android-git-version/releases + static def gitVersionPlugin = '0.4.5' } \ No newline at end of file -- cgit v1.2.3