aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml2
-rw-r--r--artifacts.gradle8
-rw-r--r--buildSrc/src/main/groovy/ca/allanwang/kau/Versions.groovy57
-rw-r--r--core-ui/src/main/res-public/values/public.xml10
-rw-r--r--core/src/main/kotlin/ca/allanwang/kau/email/EmailBuilder.kt4
-rw-r--r--core/src/main/kotlin/ca/allanwang/kau/ui/ProgressAnimator.kt179
-rw-r--r--core/src/main/res-public/values/public.xml118
-rw-r--r--core/src/test/kotlin/ca/allanwang/kau/ui/ProgressAnimatorTest.kt75
-rw-r--r--docs/Migration.md16
9 files changed, 411 insertions, 58 deletions
diff --git a/.travis.yml b/.travis.yml
index ee679b3..7bd509f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -6,6 +6,8 @@ env:
- ANDROID_API=28
- EMULATOR_API=19
- ANDROID_BUILD_TOOLS=28.0.3
+git:
+ depth: 500
android:
components:
- tools
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();
diff --git a/buildSrc/src/main/groovy/ca/allanwang/kau/Versions.groovy b/buildSrc/src/main/groovy/ca/allanwang/kau/Versions.groovy
new file mode 100644
index 0000000..d767a8a
--- /dev/null
+++ b/buildSrc/src/main/groovy/ca/allanwang/kau/Versions.groovy
@@ -0,0 +1,57 @@
+package ca.allanwang.kau
+
+class Versions {
+ static def coreMinSdk = 19
+ static def minSdk = 21
+ static def targetSdk = 28
+
+ // https://developer.android.com/studio/releases/build-tools
+ static def buildTools = '28.0.3'
+
+ // https://developer.android.com/topic/libraries/support-library/revisions
+ static def supportLibs = '28.0.0'
+
+ // https://kotlinlang.org/docs/reference/using-gradle.html
+ static def kotlin = '1.2.71'
+
+ // https://github.com/mikepenz/AboutLibraries/releases
+ static def aboutLibraries = '6.1.1'
+
+ // https://github.com/Kotlin/anko/releases
+ static def anko = '0.10.5'
+
+ // https://github.com/wasabeef/Blurry/releases
+ static def blurry = '2.1.1'
+
+ // https://dl.google.com/dl/android/maven2/com/android/support/constraint/group-index.xml
+ static def constraintLayout = '1.1.3'
+
+ // https://github.com/mikepenz/FastAdapter#using-maven
+ static def fastAdapter = '3.2.9'
+ static def fastAdapterCommons = fastAdapter
+
+ // https://github.com/bumptech/glide/releases
+ static def glide = '4.8.0'
+
+ // https://github.com/mikepenz/Android-Iconics#1-provide-the-gradle-dependency
+ 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'
+
+ // https://github.com/afollestad/material-dialogs/releases
+ static def materialDialog = '0.9.6.0'
+
+ static def espresso = '3.0.1'
+ static def junit = '4.12'
+ static def testRunner = '1.0.1'
+
+ static def gradlePlugin = '3.2.1'
+ static def mavenPlugin = '2.1'
+ static def playPublishPlugin = '1.2.2'
+
+ // https://github.com/KeepSafe/dexcount-gradle-plugin/releases
+ 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
diff --git a/core-ui/src/main/res-public/values/public.xml b/core-ui/src/main/res-public/values/public.xml
new file mode 100644
index 0000000..f46b3eb
--- /dev/null
+++ b/core-ui/src/main/res-public/values/public.xml
@@ -0,0 +1,10 @@
+<resources xmlns:tools='http://schemas.android.com/tools' tools:ignore='ResourceName'>
+<!-- AUTO-GENERATED FILE. DO NOT MODIFY. public.xml is generated by the generatepublicxml gradle task -->
+ <public name='kau_recycler_detached_background' type='layout' />
+ <public name='kau_elastic_recycler_activity' type='layout' />
+ <public name='kau_recycler_textslider' type='layout' />
+ <public name='Kau.Translucent' type='style' />
+ <public name='Kau.Translucent.NoAnimation' type='style' />
+ <public name='Kau.Translucent.SlideBottom' type='style' />
+ <public name='Kau.Translucent.SlideTop' type='style' />
+</resources> \ No newline at end of file
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 1f959ab..78661b1 100644
--- a/core/src/main/kotlin/ca/allanwang/kau/email/EmailBuilder.kt
+++ b/core/src/main/kotlin/ca/allanwang/kau/email/EmailBuilder.kt
@@ -69,7 +69,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 = mutableListOf(
"OS Version" to "${System.getProperty("os.version")} (${Build.VERSION.INCREMENTAL})",
"OS SDK" to Build.VERSION.SDK_INT,
"Device (Manufacturer)" to "${Build.DEVICE} (${Build.MANUFACTURER})",
@@ -80,7 +80,7 @@ class EmailBuilder(val email: String, val subject: String) {
if (context is Activity) {
val metric = DisplayMetrics()
context.windowManager.defaultDisplay.getMetrics(metric)
- deviceItems["Screen Dimensions"] = "${metric.widthPixels} x ${metric.heightPixels}"
+ deviceItems.add("Screen Dimensions" to "${metric.widthPixels} x ${metric.heightPixels}")
}
deviceItems.forEach { (k, v) -> emailBuilder.append("$k: $v\n") }
}
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 6f8bbc1..a46e6c5 100644
--- a/core/src/main/kotlin/ca/allanwang/kau/ui/ProgressAnimator.kt
+++ b/core/src/main/kotlin/ca/allanwang/kau/ui/ProgressAnimator.kt
@@ -18,7 +18,8 @@ package ca.allanwang.kau.ui
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
-import android.view.animation.Interpolator
+import androidx.annotation.VisibleForTesting
+import ca.allanwang.kau.kotlin.kauRemoveIf
/**
* Created by Allan Wang on 2017-11-10.
@@ -28,77 +29,157 @@ 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<ProgressDisposableAction> = mutableListOf()
+ @VisibleForTesting
+ internal val startActions: MutableList<ProgressDisposableRunnable> = mutableListOf()
+ @VisibleForTesting
+ internal val cancelActions: MutableList<ProgressDisposableRunnable> = mutableListOf()
+ @VisibleForTesting
+ internal val endActions: MutableList<ProgressDisposableRunnable> = mutableListOf()
+ var isCancelled: Boolean = false
+ private set
- var duration: Long = -1L
- var interpolator: Interpolator? = null
+ val animatorCount get() = animators.size
/**
- * 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)
+ @VisibleForTesting
+ internal fun MutableList<ProgressDisposableRunnable>.runAll() = kauRemoveIf { it() }
- /**
- * Start action to be called once when the animator first begins
- */
- fun withStartAction(action: () -> Unit) = startActions.add(action)
+ @VisibleForTesting
+ 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)
+ }
+ }
+
+ 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)
+
+ fun reset() {
+ if (isRunning) cancel()
+ animators.clear()
+ startActions.clear()
+ cancelActions.clear()
+ endActions.clear()
+ isCancelled = false
+ }
}
+
+private typealias ProgressAction = (Float) -> Unit
+private typealias ProgressDisposableAction = (Float) -> Boolean
+private typealias ProgressRunnable = () -> Unit
+private typealias ProgressDisposableRunnable = () -> Boolean
diff --git a/core/src/main/res-public/values/public.xml b/core/src/main/res-public/values/public.xml
new file mode 100644
index 0000000..ea8ed73
--- /dev/null
+++ b/core/src/main/res-public/values/public.xml
@@ -0,0 +1,118 @@
+<resources xmlns:tools='http://schemas.android.com/tools' tools:ignore='ResourceName'>
+<!-- AUTO-GENERATED FILE. DO NOT MODIFY. public.xml is generated by the generatepublicxml gradle task -->
+ <public name='kau_slide_in_top' type='anim' />
+ <public name='kau_slide_in_left' type='anim' />
+ <public name='kau_slide_out_right' type='anim' />
+ <public name='kau_slide_out_right_top' type='anim' />
+ <public name='kau_fade_in' type='anim' />
+ <public name='kau_slide_out_top' type='anim' />
+ <public name='kau_slide_out_bottom' type='anim' />
+ <public name='kau_fade_out' type='anim' />
+ <public name='kau_slide_out_left' type='anim' />
+ <public name='kau_slide_out_left_top' type='anim' />
+ <public name='kau_slide_in_bottom' type='anim' />
+ <public name='kau_slide_in_right' type='anim' />
+ <public name='kau_transparent' type='drawable' />
+ <public name='kau_selectable_white' type='drawable' />
+ <public name='kau_shadow_overlay' type='color' />
+ <public name='kau_activity_horizontal_margin' type='dimen' />
+ <public name='kau_activity_vertical_margin' type='dimen' />
+ <public name='kau_dialog_margin' type='dimen' />
+ <public name='kau_dialog_margin_bottom' type='dimen' />
+ <public name='kau_fab_margin' type='dimen' />
+ <public name='kau_appbar_padding_top' type='dimen' />
+ <public name='kau_splash_logo' type='dimen' />
+ <public name='kau_progress_bar_height' type='dimen' />
+ <public name='kau_account_image_size' type='dimen' />
+ <public name='kau_status_bar_height' type='dimen' />
+ <public name='kau_drag_dismiss_distance' type='dimen' />
+ <public name='kau_drag_dismiss_distance_large' type='dimen' />
+ <public name='kau_spacing_normal' type='dimen' />
+ <public name='kau_spacing_micro' type='dimen' />
+ <public name='kau_spacing_large' type='dimen' />
+ <public name='kau_spacing_xlarge' type='dimen' />
+ <public name='kau_spacing_huge' type='dimen' />
+ <public name='kau_padding_small' type='dimen' />
+ <public name='kau_padding_normal' type='dimen' />
+ <public name='kau_padding_large' type='dimen' />
+ <public name='kau_fab_size' type='dimen' />
+ <public name='kau_fab_radius' type='dimen' />
+ <public name='kau_display_4_text_size' type='dimen' />
+ <public name='kau_avatar_size' type='dimen' />
+ <public name='kau_avatar_bounds' type='dimen' />
+ <public name='kau_avatar_padding' type='dimen' />
+ <public name='kau_avatar_margin' type='dimen' />
+ <public name='kau_avatar_ripple_radius' type='dimen' />
+ <public name='kau_about_app' type='string' />
+ <public name='kau_about_x' type='string' />
+ <public name='kau_add_account' type='string' />
+ <public name='kau_amoled' type='string' />
+ <public name='kau_back' type='string' />
+ <public name='kau_cancel' type='string' />
+ <public name='kau_changelog' type='string' />
+ <public name='kau_close' type='string' />
+ <public name='kau_contact_us' type='string' />
+ <public name='kau_copy' type='string' />
+ <public name='kau_custom' type='string' />
+ <public name='kau_dark' type='string' />
+ <public name='kau_default' type='string' />
+ <public name='kau_do_not_show_again' type='string' />
+ <public name='kau_done' type='string' />
+ <public name='kau_error' type='string' />
+ <public name='kau_exit' type='string' />
+ <public name='kau_exit_confirmation' type='string' />
+ <public name='kau_exit_confirmation_x' type='string' />
+ <public name='kau_glass' type='string' />
+ <public name='kau_got_it' type='string' />
+ <public name='kau_great' type='string' />
+ <public name='kau_hide' type='string' />
+ <public name='kau_light' type='string' />
+ <public name='kau_login' type='string' />
+ <public name='kau_logout' type='string' />
+ <public name='kau_logout_confirm_as_x' type='string' />
+ <public name='kau_lorem_ipsum' type='string' />
+ <public name='kau_manage_account' type='string' />
+ <public name='kau_maybe' type='string' />
+ <public name='kau_menu' type='string' />
+ <public name='kau_no' type='string' />
+ <public name='kau_no_results_found' type='string' />
+ <public name='kau_none' type='string' />
+ <public name='kau_ok' type='string' />
+ <public name='kau_play_store' type='string' />
+ <public name='kau_rate' type='string' />
+ <public name='kau_report_bug' type='string' />
+ <public name='kau_search' type='string' />
+ <public name='kau_send_feedback' type='string' />
+ <public name='kau_send_via' type='string' />
+ <public name='kau_settings' type='string' />
+ <public name='kau_share' type='string' />
+ <public name='kau_text_copied' type='string' />
+ <public name='kau_thank_you' type='string' />
+ <public name='kau_uh_oh' type='string' />
+ <public name='kau_warning' type='string' />
+ <public name='kau_x_days' type='plurals' />
+ <public name='kau_x_hours' type='plurals' />
+ <public name='kau_x_minutes' type='plurals' />
+ <public name='kau_x_seconds' type='plurals' />
+ <public name='kau_yes' type='string' />
+ <public name='kau_permission_denied' type='string' />
+ <public name='kau_0' type='string' />
+ <public name='kau_bullet_point' type='string' />
+ <public name='Kau' type='style' />
+ <public name='Kau.Translucent' type='style' />
+ <public name='KauFadeIn' type='style' />
+ <public name='KauFadeInFadeOut' type='style' />
+ <public name='KauSlideInRight' type='style' />
+ <public name='KauSlideInBottom' type='style' />
+ <public name='KauSlideInFadeOut' type='style' />
+ <public name='KauSlideInSlideOutRight' type='style' />
+ <public name='KauSlideInSlideOutBottom' type='style' />
+ <public name='kau_enter_slide_bottom' type='transition' />
+ <public name='kau_enter_slide_top' type='transition' />
+ <public name='kau_exit_slide_bottom' type='transition' />
+ <public name='kau_exit_slide_top' type='transition' />
+ <public name='kau_enter_slide_right' type='transition' />
+ <public name='kau_exit_slide_right' type='transition' />
+ <public name='kau_exit_slide_left' type='transition' />
+ <public name='kau_enter_slide_left' type='transition' />
+</resources> \ 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
diff --git a/docs/Migration.md b/docs/Migration.md
index c0816d4..593056e 100644
--- a/docs/Migration.md
+++ b/docs/Migration.md
@@ -4,9 +4,19 @@ Below are some highlights on major refactoring/breaking changes
# v5.0.0
-* Material Dialog is now 3.x. This leads to a whole new API, but fortunately it is based around kotlin. Please refer to [MD's documents](https://github.com/afollestad/material-dialogs/tree/3.0.0-rc2/documentation) for the new methods.
- * Alongside such changes, `:colorpicker` is no longer as necessary. It exists mainly to provide an internal interface for other submodules.
-
+## Material Dialog Update
+
+Material Dialog is now 3.x.
+This leads to a whole new API, but fortunately it is based around kotlin.
+Please refer to [MD's documents](https://github.com/afollestad/material-dialogs/tree/3.0.0-rc2/documentation) for the new methods.
+
+Alongside such changes, `:colorpicker` is no longer as necessary. It exists mainly to provide an internal interface for other submodules.
+
+## 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.
+Make sure to call `.start()` to begin the animation.
# v4.0.1-alpha02