aboutsummaryrefslogtreecommitdiff
path: root/core/src/main/kotlin/ca/allanwang/kau/utils
diff options
context:
space:
mode:
Diffstat (limited to 'core/src/main/kotlin/ca/allanwang/kau/utils')
-rw-r--r--core/src/main/kotlin/ca/allanwang/kau/utils/ActivityUtils.kt71
-rw-r--r--core/src/main/kotlin/ca/allanwang/kau/utils/AnimHolder.kt15
-rw-r--r--core/src/main/kotlin/ca/allanwang/kau/utils/AnimUtils.kt153
-rw-r--r--core/src/main/kotlin/ca/allanwang/kau/utils/ColorUtils.kt235
-rw-r--r--core/src/main/kotlin/ca/allanwang/kau/utils/Const.kt6
-rw-r--r--core/src/main/kotlin/ca/allanwang/kau/utils/ContextUtils.kt165
-rw-r--r--core/src/main/kotlin/ca/allanwang/kau/utils/Either.kt32
-rw-r--r--core/src/main/kotlin/ca/allanwang/kau/utils/FontUtils.kt29
-rw-r--r--core/src/main/kotlin/ca/allanwang/kau/utils/FragmentUtils.kt12
-rw-r--r--core/src/main/kotlin/ca/allanwang/kau/utils/IIconUtils.kt20
-rw-r--r--core/src/main/kotlin/ca/allanwang/kau/utils/Kotterknife.kt166
-rw-r--r--core/src/main/kotlin/ca/allanwang/kau/utils/PackageUtils.kt48
-rw-r--r--core/src/main/kotlin/ca/allanwang/kau/utils/StringHolder.kt22
-rw-r--r--core/src/main/kotlin/ca/allanwang/kau/utils/TransitionUtils.kt19
-rw-r--r--core/src/main/kotlin/ca/allanwang/kau/utils/Utils.kt111
-rw-r--r--core/src/main/kotlin/ca/allanwang/kau/utils/ViewUtils.kt126
16 files changed, 1230 insertions, 0 deletions
diff --git a/core/src/main/kotlin/ca/allanwang/kau/utils/ActivityUtils.kt b/core/src/main/kotlin/ca/allanwang/kau/utils/ActivityUtils.kt
new file mode 100644
index 0000000..ec51bfd
--- /dev/null
+++ b/core/src/main/kotlin/ca/allanwang/kau/utils/ActivityUtils.kt
@@ -0,0 +1,71 @@
+package ca.allanwang.kau.utils
+
+import android.app.Activity
+import android.content.Intent
+import android.graphics.Color
+import android.support.annotation.ColorInt
+import android.support.annotation.StringRes
+import android.support.design.widget.Snackbar
+import android.support.v4.app.Fragment
+import android.view.Menu
+import ca.allanwang.kau.R
+import com.mikepenz.iconics.typeface.IIcon
+import org.jetbrains.anko.contentView
+import org.jetbrains.anko.withArguments
+
+/**
+ * Created by Allan Wang on 2017-06-21.
+ */
+
+/**
+ * Restarts an activity from itself without animations
+ * Keeps its existing extra bundles and has a builder to accept other parameters
+ */
+fun Activity.restart(builder: Intent.() -> Unit = {}) {
+ val i = Intent(this, this::class.java)
+ i.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
+ i.putExtras(intent.extras)
+ i.builder()
+ startActivity(i)
+ overridePendingTransition(0, 0) //No transitions
+ finish()
+ overridePendingTransition(0, 0)
+}
+
+fun Activity.finishSlideOut() {
+ finish()
+ overridePendingTransition(R.anim.kau_fade_in, R.anim.kau_slide_out_right_top)
+}
+
+var Activity.navigationBarColor: Int
+ get() = if (buildIsLollipopAndUp) window.navigationBarColor else Color.BLACK
+ set(value) {
+ if (buildIsLollipopAndUp) window.navigationBarColor = value
+ }
+
+var Activity.statusBarColor: Int
+ get() = if (buildIsLollipopAndUp) window.statusBarColor else Color.BLACK
+ set(value) {
+ if (buildIsLollipopAndUp) window.statusBarColor = value
+ }
+
+/**
+ * Themes the base menu icons and adds iicons programmatically based on ids
+ *
+ * Call in [Activity.onCreateOptionsMenu]
+ */
+fun Activity.setMenuIcons(menu: Menu, @ColorInt color: Int = Color.WHITE, vararg iicons: Pair<Int, IIcon>) {
+ iicons.forEach { (id, iicon) ->
+ menu.findItem(id).icon = iicon.toDrawable(this, sizeDp = 18, color = color)
+ }
+}
+
+fun Activity.hideKeyboard() = currentFocus.hideKeyboard()
+
+fun Activity.showKeyboard() = currentFocus.showKeyboard()
+
+fun Activity.snackbar(text: String, duration: Int = Snackbar.LENGTH_LONG, builder: Snackbar.() -> Unit = {})
+ = contentView!!.snackbar(text, duration, builder)
+
+fun Activity.snackbar(@StringRes textId: Int, duration: Int = Snackbar.LENGTH_LONG, builder: Snackbar.() -> Unit = {})
+ = contentView!!.snackbar(textId, duration, builder) \ No newline at end of file
diff --git a/core/src/main/kotlin/ca/allanwang/kau/utils/AnimHolder.kt b/core/src/main/kotlin/ca/allanwang/kau/utils/AnimHolder.kt
new file mode 100644
index 0000000..3db8b9c
--- /dev/null
+++ b/core/src/main/kotlin/ca/allanwang/kau/utils/AnimHolder.kt
@@ -0,0 +1,15 @@
+package ca.allanwang.kau.utils
+
+import ca.allanwang.kau.kotlin.lazyInterpolator
+
+/**
+ * Created by Allan Wang on 2017-06-28.
+ *
+ * Holder for a bunch of common animators/interpolators used throughout this library
+ */
+object AnimHolder {
+
+ val fastOutSlowInInterpolator = lazyInterpolator(android.R.interpolator.fast_out_linear_in)
+ val decelerateInterpolator = lazyInterpolator(android.R.interpolator.decelerate_cubic)
+
+} \ No newline at end of file
diff --git a/core/src/main/kotlin/ca/allanwang/kau/utils/AnimUtils.kt b/core/src/main/kotlin/ca/allanwang/kau/utils/AnimUtils.kt
new file mode 100644
index 0000000..86b049e
--- /dev/null
+++ b/core/src/main/kotlin/ca/allanwang/kau/utils/AnimUtils.kt
@@ -0,0 +1,153 @@
+package ca.allanwang.kau.utils
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.content.Context
+import android.support.annotation.StringRes
+import android.view.View
+import android.view.ViewAnimationUtils
+import android.view.animation.Animation
+import android.view.animation.AnimationUtils
+import android.view.animation.DecelerateInterpolator
+import android.view.animation.Interpolator
+import android.widget.TextView
+import ca.allanwang.kau.kotlin.lazyContext
+
+/**
+ * Created by Allan Wang on 2017-06-01.
+ *
+ * Animation extension @KauUtils functions for Views
+ */
+@KauUtils fun View.rootCircularReveal(x: Int = 0, y: Int = 0, duration: Long = 500L, onStart: (() -> Unit)? = null, onFinish: (() -> Unit)? = null) {
+ this.addOnLayoutChangeListener(object : View.OnLayoutChangeListener {
+ override @KauUtils fun onLayoutChange(v: View, left: Int, top: Int, right: Int, bottom: Int, oldLeft: Int, oldTop: Int,
+ oldRight: Int, oldBottom: Int) {
+ v.removeOnLayoutChangeListener(this)
+ var x2 = x
+ var y2 = y
+ if (x2 > right) x2 = 0
+ if (y2 > bottom) y2 = 0
+ val radius = Math.hypot(Math.max(x2, right - x2).toDouble(), Math.max(y2, bottom - y2).toDouble()).toInt()
+ val reveal = ViewAnimationUtils.createCircularReveal(v, x2, y2, 0f, radius.toFloat())
+ reveal.interpolator = DecelerateInterpolator(1f)
+ reveal.duration = duration
+ reveal.addListener(object : AnimatorListenerAdapter() {
+ override @KauUtils fun onAnimationStart(animation: Animator?) {
+ visible()
+ onStart?.invoke()
+ }
+
+ override @KauUtils fun onAnimationEnd(animation: Animator?) = onFinish?.invoke() ?: Unit
+ override @KauUtils fun onAnimationCancel(animation: Animator?) = onFinish?.invoke() ?: Unit
+ })
+ reveal.start()
+ }
+ })
+}
+
+@KauUtils fun View.circularReveal(x: Int = 0, y: Int = 0, offset: Long = 0L, radius: Float = -1.0f, duration: Long = 500L, onStart: (() -> Unit)? = null, onFinish: (() -> Unit)? = null) {
+ if (!isAttachedToWindow) {
+ onStart?.invoke()
+ visible()
+ onFinish?.invoke()
+ return
+ }
+ var r = radius
+ if (r < 0.0f) {
+ r = Math.max(Math.hypot(x.toDouble(), y.toDouble()), Math.hypot((width - x.toDouble()), (height - y.toDouble()))).toFloat()
+ }
+ val anim = ViewAnimationUtils.createCircularReveal(this, x, y, 0f, r).setDuration(duration)
+ anim.startDelay = offset
+ anim.addListener(object : AnimatorListenerAdapter() {
+ override @KauUtils fun onAnimationStart(animation: Animator?) {
+ visible()
+ onStart?.invoke()
+ }
+
+ override @KauUtils fun onAnimationEnd(animation: Animator?) = onFinish?.invoke() ?: Unit
+ override @KauUtils fun onAnimationCancel(animation: Animator?) = onFinish?.invoke() ?: Unit
+ })
+ anim.start()
+}
+
+@KauUtils fun View.circularHide(x: Int = 0, y: Int = 0, offset: Long = 0L, radius: Float = -1.0f, duration: Long = 500L, onStart: (() -> Unit)? = null, onFinish: (() -> Unit)? = null) {
+ if (!isAttachedToWindow) {
+ onStart?.invoke()
+ invisible()
+ onFinish?.invoke()
+ return
+ }
+ var r = radius
+ if (r < 0.0f) {
+ r = Math.max(Math.hypot(x.toDouble(), y.toDouble()), Math.hypot((width - x.toDouble()), (height - y.toDouble()))).toFloat()
+ }
+ val anim = ViewAnimationUtils.createCircularReveal(this, x, y, r, 0f).setDuration(duration)
+ anim.startDelay = offset
+ anim.addListener(object : AnimatorListenerAdapter() {
+ override @KauUtils fun onAnimationStart(animation: Animator?) = onStart?.invoke() ?: Unit
+
+ override @KauUtils fun onAnimationEnd(animation: Animator?) {
+ invisible()
+ onFinish?.invoke() ?: Unit
+ }
+
+ override @KauUtils fun onAnimationCancel(animation: Animator?) = onFinish?.invoke() ?: Unit
+ })
+ anim.start()
+}
+
+@KauUtils fun View.fadeIn(offset: Long = 0L, duration: Long = 200L, onStart: (() -> Unit)? = null, onFinish: (() -> Unit)? = null) {
+ if (!isAttachedToWindow) {
+ onStart?.invoke()
+ visible()
+ onFinish?.invoke()
+ return
+ }
+ if (isAttachedToWindow) {
+ val anim = AnimationUtils.loadAnimation(context, android.R.anim.fade_in)
+ anim.startOffset = offset
+ anim.duration = duration
+ anim.setAnimationListener(object : Animation.AnimationListener {
+ override @KauUtils fun onAnimationRepeat(animation: Animation?) {}
+ override @KauUtils fun onAnimationEnd(animation: Animation?) = onFinish?.invoke() ?: Unit
+ override @KauUtils fun onAnimationStart(animation: Animation?) {
+ visible()
+ onStart?.invoke()
+ }
+ })
+ startAnimation(anim)
+ }
+}
+
+@KauUtils fun View.fadeOut(offset: Long = 0L, duration: Long = 200L, onStart: (() -> Unit)? = null, onFinish: (() -> Unit)? = null) {
+ if (!isAttachedToWindow) {
+ onStart?.invoke()
+ invisible()
+ onFinish?.invoke()
+ return
+ }
+ val anim = AnimationUtils.loadAnimation(context, android.R.anim.fade_out)
+ anim.startOffset = offset
+ anim.duration = duration
+ anim.setAnimationListener(object : Animation.AnimationListener {
+ override @KauUtils fun onAnimationRepeat(animation: Animation?) {}
+ override @KauUtils fun onAnimationEnd(animation: Animation?) {
+ invisible()
+ onFinish?.invoke()
+ }
+
+ override @KauUtils fun onAnimationStart(animation: Animation?) {
+ onStart?.invoke()
+ }
+ })
+ startAnimation(anim)
+}
+
+@KauUtils fun TextView.setTextWithFade(text: String, duration: Long = 200, onFinish: (() -> Unit)? = null) {
+ fadeOut(duration = duration, onFinish = {
+ setText(text)
+ fadeIn(duration = duration, onFinish = onFinish)
+ })
+}
+
+@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
diff --git a/core/src/main/kotlin/ca/allanwang/kau/utils/ColorUtils.kt b/core/src/main/kotlin/ca/allanwang/kau/utils/ColorUtils.kt
new file mode 100644
index 0000000..8590d6f
--- /dev/null
+++ b/core/src/main/kotlin/ca/allanwang/kau/utils/ColorUtils.kt
@@ -0,0 +1,235 @@
+package ca.allanwang.kau.utils
+
+import android.content.Context
+import android.content.res.ColorStateList
+import android.graphics.Color
+import android.graphics.PorterDuff
+import android.graphics.drawable.Drawable
+import android.os.Build
+import android.support.annotation.ColorInt
+import android.support.annotation.FloatRange
+import android.support.annotation.IntRange
+import android.support.v4.content.ContextCompat
+import android.support.v4.graphics.drawable.DrawableCompat
+import android.support.v7.widget.AppCompatEditText
+import android.support.v7.widget.Toolbar
+import android.widget.*
+import com.afollestad.materialdialogs.R
+
+/**
+ * Created by Allan Wang on 2017-06-08.
+ */
+fun Int.isColorDark(): Boolean
+ = (0.299 * Color.red(this) + 0.587 * Color.green(this) + 0.114 * Color.blue(this)) / 255.0 < 0.5
+
+fun Int.toHexString(withAlpha: Boolean = false, withHexPrefix: Boolean = true): String {
+ val hex = if (withAlpha) String.format("#%08X", this)
+ else String.format("#%06X", 0xFFFFFF and this)
+ return if (withHexPrefix) hex else hex.substring(1)
+}
+
+fun Int.toRgbaString(): String = "rgba(${Color.red(this)}, ${Color.green(this)}, ${Color.blue(this)}, ${(Color.alpha(this) / 255f).round(3)})"
+
+fun Int.toHSV(): FloatArray {
+ val hsv = FloatArray(3)
+ Color.colorToHSV(this, hsv)
+ return hsv
+}
+
+fun FloatArray.toColor(): Int = Color.HSVToColor(this)
+
+fun Int.isColorVisibleOn(@ColorInt color: Int, @IntRange(from = 0L, to = 255L) delta: Int = 25,
+ @IntRange(from = 0L, to = 255L) minAlpha: Int = 50): Boolean =
+ if (Color.alpha(this) < minAlpha) false
+ else !(Math.abs(Color.red(this) - Color.red(color)) < delta
+ && Math.abs(Color.green(this) - Color.green(color)) < delta
+ && Math.abs(Color.blue(this) - Color.blue(color)) < delta)
+
+
+@ColorInt
+fun Context.getDisabledColor(): Int {
+ val primaryColor = resolveColor(android.R.attr.textColorPrimary)
+ val disabledColor = if (primaryColor.isColorDark()) Color.BLACK else Color.WHITE
+ return disabledColor.adjustAlpha(0.3f)
+}
+
+@ColorInt
+fun Int.adjustAlpha(factor: Float): Int {
+ val alpha = Math.round(Color.alpha(this) * factor)
+ return Color.argb(alpha, Color.red(this), Color.green(this), Color.blue(this))
+}
+
+val Int.isColorTransparent: Boolean
+ get() = Color.alpha(this) != 255
+
+@ColorInt
+fun Int.withAlpha(@IntRange(from = 0L, to = 255L) alpha: Int): Int
+ = Color.argb(alpha, Color.red(this), Color.green(this), Color.blue(this))
+
+@ColorInt
+fun Int.withMinAlpha(@IntRange(from = 0L, to = 255L) alpha: Int): Int
+ = Color.argb(Math.max(alpha, Color.alpha(this)), Color.red(this), Color.green(this), Color.blue(this))
+
+@ColorInt
+fun Int.lighten(@FloatRange(from = 0.0, to = 1.0) factor: Float = 0.1f): Int {
+ val (red, green, blue) = intArrayOf(Color.red(this), Color.green(this), Color.blue(this))
+ .map { (it * (1f - factor) + 255f * factor).toInt() }
+ return Color.argb(Color.alpha(this), red, green, blue)
+}
+
+@ColorInt
+fun Int.darken(@FloatRange(from = 0.0, to = 1.0) factor: Float = 0.1f): Int {
+ val (red, green, blue) = intArrayOf(Color.red(this), Color.green(this), Color.blue(this))
+ .map { (it * (1f - factor)).toInt() }
+ return Color.argb(Color.alpha(this), red, green, blue)
+}
+
+@ColorInt
+fun Int.colorToBackground(@FloatRange(from = 0.0, to = 1.0) factor: Float = 0.1f): Int
+ = if (isColorDark()) darken(factor) else lighten(factor)
+
+@ColorInt
+fun Int.colorToForeground(@FloatRange(from = 0.0, to = 1.0) factor: Float = 0.1f): Int
+ = if (isColorDark()) lighten(factor) else darken(factor)
+
+@Throws(IllegalArgumentException::class)
+fun String.toColor(): Int {
+ val toParse: String
+ if (startsWith("#") && length == 4)
+ toParse = "#${this[1]}${this[1]}${this[2]}${this[2]}${this[3]}${this[3]}"
+ else
+ toParse = this
+ return Color.parseColor(toParse)
+}
+
+//Get ColorStateList
+fun Context.colorStateList(@ColorInt color: Int): ColorStateList {
+ val disabledColor = color.adjustAlpha(0.3f)
+ return ColorStateList(arrayOf(intArrayOf(android.R.attr.state_enabled, -android.R.attr.state_checked),
+ intArrayOf(android.R.attr.state_enabled, android.R.attr.state_checked),
+ intArrayOf(-android.R.attr.state_enabled, -android.R.attr.state_checked),
+ intArrayOf(-android.R.attr.state_enabled, android.R.attr.state_checked)),
+ intArrayOf(color.adjustAlpha(0.8f), color, disabledColor, disabledColor))
+}
+
+/*
+ * Tint Helpers
+ * Kotlin tint bindings that start with 'tint' so it doesn't conflict with existing methods
+ * Largely based on MDTintHelper
+ * https://github.com/afollestad/material-dialogs/blob/master/core/src/main/java/com/afollestad/materialdialogs/internal/MDTintHelper.java
+ */
+fun RadioButton.tint(colors: ColorStateList) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ buttonTintList = colors
+ } else {
+ val radioDrawable = context.drawable(R.drawable.abc_btn_radio_material)
+ val d = DrawableCompat.wrap(radioDrawable)
+ DrawableCompat.setTintList(d, colors)
+ buttonDrawable = d
+ }
+}
+
+fun RadioButton.tint(@ColorInt color: Int) = tint(context.colorStateList(color))
+
+fun CheckBox.tint(colors: ColorStateList) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ buttonTintList = colors
+ } else {
+ val checkDrawable = context.drawable(R.drawable.abc_btn_check_material)
+ val drawable = DrawableCompat.wrap(checkDrawable)
+ DrawableCompat.setTintList(drawable, colors)
+ buttonDrawable = drawable
+ }
+}
+
+fun CheckBox.tint(@ColorInt color: Int) = tint(context.colorStateList(color))
+
+fun SeekBar.tint(@ColorInt color: Int) {
+ val s1 = ColorStateList.valueOf(color)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ thumbTintList = s1
+ progressTintList = s1
+ } else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.GINGERBREAD_MR1) {
+ val progressDrawable = DrawableCompat.wrap(progressDrawable)
+ this.progressDrawable = progressDrawable
+ DrawableCompat.setTintList(progressDrawable, s1)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ val thumbDrawable = DrawableCompat.wrap(thumb)
+ DrawableCompat.setTintList(thumbDrawable, s1)
+ thumb = thumbDrawable
+ }
+ } else {
+ val mode: PorterDuff.Mode = if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1)
+ PorterDuff.Mode.MULTIPLY else PorterDuff.Mode.SRC_IN
+ indeterminateDrawable?.setColorFilter(color, mode)
+ progressDrawable?.setColorFilter(color, mode)
+ }
+}
+
+fun ProgressBar.tint(@ColorInt color: Int, skipIndeterminate: Boolean = false) {
+ val sl = ColorStateList.valueOf(color)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ progressTintList = sl
+ secondaryProgressTintList = sl
+ if (!skipIndeterminate) indeterminateTintList = sl
+ } else {
+ val mode: PorterDuff.Mode = if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1)
+ PorterDuff.Mode.MULTIPLY else PorterDuff.Mode.SRC_IN
+ indeterminateDrawable?.setColorFilter(color, mode)
+ progressDrawable?.setColorFilter(color, mode)
+ }
+}
+
+fun Context.textColorStateList(@ColorInt color: Int): ColorStateList {
+ val states = arrayOf(
+ intArrayOf(-android.R.attr.state_enabled),
+ intArrayOf(-android.R.attr.state_pressed, -android.R.attr.state_focused),
+ intArrayOf()
+ )
+ val colors = intArrayOf(
+ resolveColor(R.attr.colorControlNormal),
+ resolveColor(R.attr.colorControlNormal),
+ color
+ )
+ return ColorStateList(states, colors)
+}
+
+fun EditText.tint(@ColorInt color: Int) {
+ val editTextColorStateList = context.textColorStateList(color)
+ if (this is AppCompatEditText) {
+ supportBackgroundTintList = editTextColorStateList
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ backgroundTintList = editTextColorStateList
+ }
+ tintCursor(color)
+}
+
+fun EditText.tintCursor(@ColorInt color: Int) {
+ try {
+ val fCursorDrawableRes = TextView::class.java.getDeclaredField("mCursorDrawableRes")
+ fCursorDrawableRes.isAccessible = true
+ val mCursorDrawableRes = fCursorDrawableRes.getInt(this)
+ val fEditor = TextView::class.java.getDeclaredField("mEditor")
+ fEditor.isAccessible = true
+ val editor = fEditor.get(this)
+ val clazz = editor.javaClass
+ val fCursorDrawable = clazz.getDeclaredField("mCursorDrawable")
+ fCursorDrawable.isAccessible = true
+ val drawables: Array<Drawable> = Array(2, {
+ val drawable = ContextCompat.getDrawable(context, mCursorDrawableRes)
+ drawable.setColorFilter(color, PorterDuff.Mode.SRC_IN)
+ drawable
+ })
+ fCursorDrawable.set(editor, drawables)
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+}
+
+fun Toolbar.tint(@ColorInt color: Int, tintTitle: Boolean = true) {
+ if (tintTitle) {
+ setTitleTextColor(color)
+ setSubtitleTextColor(color)
+ }
+ (0 until childCount).asSequence().forEach { (getChildAt(it) as? ImageButton)?.setColorFilter(color) }
+} \ No newline at end of file
diff --git a/core/src/main/kotlin/ca/allanwang/kau/utils/Const.kt b/core/src/main/kotlin/ca/allanwang/kau/utils/Const.kt
new file mode 100644
index 0000000..944caa4
--- /dev/null
+++ b/core/src/main/kotlin/ca/allanwang/kau/utils/Const.kt
@@ -0,0 +1,6 @@
+package ca.allanwang.kau.utils
+
+/**
+ * Created by Allan Wang on 2017-06-08.
+ */
+const val ANDROID_NAMESPACE = "http://schemas.android.com/apk/res/android" \ No newline at end of file
diff --git a/core/src/main/kotlin/ca/allanwang/kau/utils/ContextUtils.kt b/core/src/main/kotlin/ca/allanwang/kau/utils/ContextUtils.kt
new file mode 100644
index 0000000..21021e2
--- /dev/null
+++ b/core/src/main/kotlin/ca/allanwang/kau/utils/ContextUtils.kt
@@ -0,0 +1,165 @@
+package ca.allanwang.kau.utils
+
+import android.app.Activity
+import android.app.ActivityOptions
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.graphics.drawable.Drawable
+import android.net.ConnectivityManager
+import android.net.Uri
+import android.os.Bundle
+import android.support.annotation.*
+import android.support.v4.app.ActivityOptionsCompat
+import android.support.v4.content.ContextCompat
+import android.util.TypedValue
+import android.view.View
+import android.widget.Toast
+import ca.allanwang.kau.R
+import ca.allanwang.kau.logging.KL
+import com.afollestad.materialdialogs.MaterialDialog
+
+
+/**
+ * Created by Allan Wang on 2017-06-03.
+ */
+fun Context.startActivity(
+ clazz: Class<out Activity>,
+ clearStack: Boolean = false,
+ transition: Boolean = false,
+ bundle: Bundle? = null,
+ intentBuilder: Intent.() -> Unit = {}) {
+ val intent = (Intent(this, clazz))
+ if (clearStack) intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK)
+ intent.intentBuilder()
+ val fullBundle = if (transition && this is Activity) ActivityOptions.makeSceneTransitionAnimation(this).toBundle() else Bundle()
+ if (transition && this !is Activity) KL.d("Cannot make scene transition when context is not an instance of an Activity")
+ if (bundle != null) fullBundle.putAll(bundle)
+ ContextCompat.startActivity(this, intent, if (fullBundle.isEmpty) null else fullBundle)
+ if (this is Activity && clearStack) finish()
+}
+
+/**
+ * Bring in activity from the right
+ */
+fun Context.startActivitySlideIn(clazz: Class<out Activity>, clearStack: Boolean = false, intentBuilder: Intent.() -> Unit = {}, bundleBuilder: Bundle.() -> Unit = {}) {
+ val bundle = ActivityOptionsCompat.makeCustomAnimation(this, R.anim.kau_slide_in_right, R.anim.kau_fade_out).toBundle()
+ bundle.bundleBuilder()
+ startActivity(clazz, clearStack, intentBuilder = intentBuilder, bundle = bundle)
+}
+
+/**
+ * Bring in activity from behind while pushing the current activity to the right
+ * This replicates the exit animation of a sliding activity, but is a forward creation
+ * For the animation to work, the previous activity should not be in the stack (otherwise you wouldn't need this in the first place)
+ * Consequently, the stack will be cleared by default
+ */
+fun Context.startActivitySlideOut(clazz: Class<out Activity>, clearStack: Boolean = true, intentBuilder: Intent.() -> Unit = {}, bundleBuilder: Bundle.() -> Unit = {}) {
+ val bundle = ActivityOptionsCompat.makeCustomAnimation(this, R.anim.kau_fade_in, R.anim.kau_slide_out_right_top).toBundle()
+ bundle.bundleBuilder()
+ startActivity(clazz, clearStack, intentBuilder = intentBuilder, bundle = bundle)
+}
+
+fun Context.startPlayStoreLink(@StringRes packageIdRes: Int) = startPlayStoreLink(string(packageIdRes))
+
+fun Context.startPlayStoreLink(packageId: String) {
+ startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=$packageId")))
+}
+
+/**
+ * Starts a url
+ * If given a series of links, will open the first one that isn't null
+ */
+fun Context.startLink(vararg url: String?) {
+ val link = url.firstOrNull { !it.isNullOrBlank() } ?: return
+ val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(link))
+ startActivity(browserIntent)
+}
+
+//Toast helpers
+fun Context.toast(@StringRes id: Int, duration: Int = Toast.LENGTH_LONG) = toast(this.string(id), duration)
+
+fun Context.toast(text: String, duration: Int = Toast.LENGTH_LONG) {
+ Toast.makeText(this, text, duration).show()
+}
+
+//Resource retrievers
+fun Context.string(@StringRes id: Int): String = getString(id)
+
+fun Context.string(@StringRes id: Int, fallback: String?): String? = if (id > 0) string(id) else fallback
+fun Context.string(holder: StringHolder?): String? = holder?.getString(this)
+fun Context.color(@ColorRes id: Int): Int = ContextCompat.getColor(this, id)
+fun Context.integer(@IntegerRes id: Int): Int = resources.getInteger(id)
+fun Context.dimen(@DimenRes id: Int): Float = resources.getDimension(id)
+fun Context.dimenPixelSize(@DimenRes id: Int): Int = resources.getDimensionPixelSize(id)
+fun Context.drawable(@DrawableRes id: Int): Drawable = ContextCompat.getDrawable(this, id)
+fun Context.drawable(@DrawableRes id: Int, fallback: Drawable?): Drawable? = if (id > 0) drawable(id) else fallback
+
+//Attr retrievers
+fun Context.resolveColor(@AttrRes attr: Int, fallback: Int = 0): Int {
+ val a = theme.obtainStyledAttributes(intArrayOf(attr))
+ try {
+ return a.getColor(0, fallback)
+ } finally {
+ a.recycle()
+ }
+}
+
+fun Context.resolveDrawable(@AttrRes attr: Int): Drawable? {
+ val a = theme.obtainStyledAttributes(intArrayOf(attr))
+ try {
+ return a.getDrawable(0)
+ } finally {
+ a.recycle()
+ }
+}
+
+fun Context.resolveBoolean(@AttrRes attr: Int, fallback: Boolean = false): Boolean {
+ val a = theme.obtainStyledAttributes(intArrayOf(attr))
+ try {
+ return a.getBoolean(0, fallback)
+ } finally {
+ a.recycle()
+ }
+}
+
+fun Context.resolveString(@AttrRes attr: Int, fallback: String = ""): String {
+ val v = TypedValue()
+ return if (theme.resolveAttribute(attr, v, true)) v.string.toString() else fallback
+}
+
+/**
+ * Wrapper function for the MaterialDialog adapterBuilder
+ * There is no need to call build() or show() as those are done by default
+ */
+inline fun Context.materialDialog(action: MaterialDialog.Builder.() -> Unit): MaterialDialog {
+ val builder = MaterialDialog.Builder(this)
+ builder.action()
+ return builder.show()
+}
+
+inline val Context.isNetworkAvailable: Boolean
+ get() {
+ val connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
+ val activeNetworkInfo = connectivityManager.activeNetworkInfo
+ return activeNetworkInfo != null && activeNetworkInfo.isConnectedOrConnecting
+ }
+
+fun Context.getDip(value: Float): Float = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value, resources.displayMetrics)
+
+inline val Context.isRtl: Boolean
+ get() = resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL
+
+/**
+ * Determine if the navigation bar will be on the bottom of the screen, based on logic in
+ * PhoneWindowManager.
+ */
+inline val Context.isNavBarOnBottom: Boolean
+ get() {
+ val cfg = resources.configuration
+ val dm = resources.displayMetrics
+ val canMove = dm.widthPixels != dm.heightPixels && cfg.smallestScreenWidthDp < 600
+ return !canMove || dm.widthPixels < dm.heightPixels
+ }
+
+fun Context.hasPermission(permissions: String) = !buildIsMarshmallowAndUp || ContextCompat.checkSelfPermission(this, permissions) == PackageManager.PERMISSION_GRANTED \ No newline at end of file
diff --git a/core/src/main/kotlin/ca/allanwang/kau/utils/Either.kt b/core/src/main/kotlin/ca/allanwang/kau/utils/Either.kt
new file mode 100644
index 0000000..dab5810
--- /dev/null
+++ b/core/src/main/kotlin/ca/allanwang/kau/utils/Either.kt
@@ -0,0 +1,32 @@
+package ca.allanwang.kau.utils
+
+/**
+ * Created by Allan Wang on 2017-06-17.
+ *
+ * Courtesy of adelnizamutdinov
+ *
+ * https://github.com/adelnizamutdinov/kotlin-either
+ */
+@Suppress("unused")
+sealed class Either<out L, out R>
+
+data class Left<out T>(val value: T) : Either<T, Nothing>()
+data class Right<out T>(val value: T) : Either<Nothing, T>()
+
+inline fun <L, R, T> Either<L, R>.fold(left: (L) -> T, right: (R) -> T): T =
+ when (this) {
+ is Left -> left(value)
+ is Right -> right(value)
+ }
+
+inline fun <L, R, T> Either<L, R>.flatMap(f: (R) -> Either<L, T>): Either<L, T> =
+ fold({ this as Left }, f)
+
+inline fun <L, R, T> Either<L, R>.map(f: (R) -> T): Either<L, T> =
+ flatMap { Right(f(it)) }
+
+val <T> Either<T, *>.isLeft: Boolean
+ get() = this is Left<T>
+
+val <T> Either<*, T>.isRight: Boolean
+ get() = this is Right<T> \ No newline at end of file
diff --git a/core/src/main/kotlin/ca/allanwang/kau/utils/FontUtils.kt b/core/src/main/kotlin/ca/allanwang/kau/utils/FontUtils.kt
new file mode 100644
index 0000000..3fc509d
--- /dev/null
+++ b/core/src/main/kotlin/ca/allanwang/kau/utils/FontUtils.kt
@@ -0,0 +1,29 @@
+package ca.allanwang.kau.utils
+
+import android.content.Context
+import android.graphics.Typeface
+
+/**
+ * Created by Allan Wang on 2017-06-28.
+ */
+object FontUtils {
+
+ val sTypefaceCache: MutableMap<String, Typeface> = mutableMapOf()
+
+ fun get(context: Context, font: String): Typeface {
+ synchronized(sTypefaceCache) {
+ if (!sTypefaceCache.containsKey(font)) {
+ val tf = Typeface.createFromAsset(
+ context.applicationContext.assets, "fonts/$font.ttf")
+ sTypefaceCache.put(font, tf)
+ }
+ return sTypefaceCache.get(font) ?: throw IllegalArgumentException("Font error; typeface does not exist at assets/fonts$font.ttf")
+ }
+ }
+
+ fun getName(typeface: Typeface): String? = sTypefaceCache.entries.firstOrNull { it.value == typeface }?.key
+
+}
+
+fun Context.getFont(font: String) = FontUtils.get(this, font)
+fun Context.getFontName(typeface: Typeface) = FontUtils.getName(typeface) \ No newline at end of file
diff --git a/core/src/main/kotlin/ca/allanwang/kau/utils/FragmentUtils.kt b/core/src/main/kotlin/ca/allanwang/kau/utils/FragmentUtils.kt
new file mode 100644
index 0000000..acc71f2
--- /dev/null
+++ b/core/src/main/kotlin/ca/allanwang/kau/utils/FragmentUtils.kt
@@ -0,0 +1,12 @@
+package ca.allanwang.kau.utils
+
+import android.support.v4.app.Fragment
+import org.jetbrains.anko.bundleOf
+
+/**
+ * Created by Allan Wang on 2017-07-02.
+ */
+fun <T : Fragment> T.withArguments(vararg params: Pair<String, Any>): T {
+ arguments = bundleOf(*params)
+ return this
+} \ No newline at end of file
diff --git a/core/src/main/kotlin/ca/allanwang/kau/utils/IIconUtils.kt b/core/src/main/kotlin/ca/allanwang/kau/utils/IIconUtils.kt
new file mode 100644
index 0000000..03a1605
--- /dev/null
+++ b/core/src/main/kotlin/ca/allanwang/kau/utils/IIconUtils.kt
@@ -0,0 +1,20 @@
+package ca.allanwang.kau.utils
+
+import android.content.Context
+import android.content.res.ColorStateList
+import android.graphics.Color
+import android.graphics.drawable.Drawable
+import android.support.annotation.ColorInt
+import com.mikepenz.iconics.IconicsDrawable
+import com.mikepenz.iconics.typeface.IIcon
+
+/**
+ * Created by Allan Wang on 2017-05-29.
+ */
+@KauUtils fun IIcon.toDrawable(c: Context, sizeDp: Int = 24, @ColorInt color: Int = Color.WHITE, builder: IconicsDrawable.() -> Unit = {}): Drawable {
+ val state = ColorStateList.valueOf(color)
+ val icon = IconicsDrawable(c).icon(this).sizeDp(sizeDp)
+ icon.setTintList(state)
+ icon.builder()
+ return icon
+} \ 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
new file mode 100644
index 0000000..247bbc7
--- /dev/null
+++ b/core/src/main/kotlin/ca/allanwang/kau/utils/Kotterknife.kt
@@ -0,0 +1,166 @@
+package ca.allanwang.kau.utils
+
+/**
+ * Created by Allan Wang on 2017-05-29.
+ *
+ * Courtesy of Jake Wharton
+ *
+ * https://github.com/JakeWharton/kotterknife/blob/master/src/main/kotlin/kotterknife/ButterKnife.kt
+ */
+import android.app.Activity
+import android.app.Dialog
+import android.app.DialogFragment
+import android.app.Fragment
+import android.support.v7.widget.RecyclerView.ViewHolder
+import android.view.View
+import kotlin.properties.ReadOnlyProperty
+import kotlin.reflect.KProperty
+import android.support.v4.app.DialogFragment as SupportDialogFragment
+import android.support.v4.app.Fragment as SupportFragment
+
+fun <V : View> View.bindView(id: Int)
+ : ReadOnlyProperty<View, V> = required(id, viewFinder)
+
+fun <V : View> Activity.bindView(id: Int)
+ : ReadOnlyProperty<Activity, V> = required(id, viewFinder)
+
+fun <V : View> Dialog.bindView(id: Int)
+ : ReadOnlyProperty<Dialog, V> = required(id, viewFinder)
+
+fun <V : View> DialogFragment.bindView(id: Int)
+ : ReadOnlyProperty<DialogFragment, V> = required(id, viewFinder)
+
+fun <V : View> android.support.v4.app.DialogFragment.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)
+ : ReadOnlyProperty<android.support.v4.app.Fragment, V> = required(id, viewFinder)
+
+fun <V : View> ViewHolder.bindView(id: Int)
+ : ReadOnlyProperty<ViewHolder, V> = required(id, viewFinder)
+
+fun <V : View> View.bindOptionalView(id: Int)
+ : ReadOnlyProperty<View, V?> = optional(id, viewFinder)
+
+fun <V : View> Activity.bindOptionalView(id: Int)
+ : ReadOnlyProperty<Activity, V?> = optional(id, viewFinder)
+
+fun <V : View> Dialog.bindOptionalView(id: Int)
+ : ReadOnlyProperty<Dialog, V?> = optional(id, viewFinder)
+
+fun <V : View> DialogFragment.bindOptionalView(id: Int)
+ : ReadOnlyProperty<DialogFragment, V?> = optional(id, viewFinder)
+
+fun <V : View> android.support.v4.app.DialogFragment.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)
+ : ReadOnlyProperty<android.support.v4.app.Fragment, V?> = optional(id, viewFinder)
+
+fun <V : View> ViewHolder.bindOptionalView(id: Int)
+ : ReadOnlyProperty<ViewHolder, V?> = optional(id, viewFinder)
+
+fun <V : View> View.bindViews(vararg ids: Int)
+ : ReadOnlyProperty<View, List<V>> = required(ids, viewFinder)
+
+fun <V : View> Activity.bindViews(vararg ids: Int)
+ : ReadOnlyProperty<Activity, List<V>> = required(ids, viewFinder)
+
+fun <V : View> Dialog.bindViews(vararg ids: Int)
+ : ReadOnlyProperty<Dialog, List<V>> = required(ids, viewFinder)
+
+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)
+ : 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)
+ : ReadOnlyProperty<android.support.v4.app.Fragment, List<V>> = required(ids, viewFinder)
+
+fun <V : View> ViewHolder.bindViews(vararg ids: Int)
+ : ReadOnlyProperty<ViewHolder, List<V>> = required(ids, viewFinder)
+
+fun <V : View> View.bindOptionalViews(vararg ids: Int)
+ : ReadOnlyProperty<View, List<V>> = optional(ids, viewFinder)
+
+fun <V : View> Activity.bindOptionalViews(vararg ids: Int)
+ : ReadOnlyProperty<Activity, List<V>> = optional(ids, viewFinder)
+
+fun <V : View> Dialog.bindOptionalViews(vararg ids: Int)
+ : ReadOnlyProperty<Dialog, List<V>> = optional(ids, viewFinder)
+
+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)
+ : 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)
+ : ReadOnlyProperty<android.support.v4.app.Fragment, List<V>> = optional(ids, viewFinder)
+
+fun <V : View> ViewHolder.bindOptionalViews(vararg ids: Int)
+ : ReadOnlyProperty<ViewHolder, List<V>> = optional(ids, viewFinder)
+
+private val View.viewFinder: View.(Int) -> View?
+ get() = { findViewById(it) }
+private val Activity.viewFinder: Activity.(Int) -> View?
+ get() = { findViewById(it) }
+private val Dialog.viewFinder: Dialog.(Int) -> View?
+ get() = { findViewById(it) }
+private val DialogFragment.viewFinder: DialogFragment.(Int) -> View?
+ get() = { dialog.findViewById(it) }
+private val android.support.v4.app.DialogFragment.viewFinder: android.support.v4.app.DialogFragment.(Int) -> View?
+ get() = { dialog.findViewById(it) }
+private val Fragment.viewFinder: Fragment.(Int) -> View?
+ get() = { view.findViewById(it) }
+private val android.support.v4.app.Fragment.viewFinder: android.support.v4.app.Fragment.(Int) -> View?
+ get() = { view!!.findViewById(it) }
+private val ViewHolder.viewFinder: ViewHolder.(Int) -> View?
+ get() = { itemView.findViewById(it) }
+
+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 var value: Any? = EMPTY
+
+ override fun getValue(thisRef: T, property: KProperty<*>): V {
+ if (value == EMPTY) {
+ value = initializer(thisRef, property)
+ }
+ @Suppress("UNCHECKED_CAST")
+ return value as V
+ }
+} \ 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
new file mode 100644
index 0000000..837c209
--- /dev/null
+++ b/core/src/main/kotlin/ca/allanwang/kau/utils/PackageUtils.kt
@@ -0,0 +1,48 @@
+package ca.allanwang.kau.utils
+
+import android.content.Context
+import android.content.pm.PackageManager
+import android.os.Build
+import android.support.annotation.RequiresApi
+
+/**
+ * Created by Allan Wang on 2017-06-23.
+ */
+
+/**
+ * Checks if a given package is installed
+ * @param packageName packageId
+ * @return true if installed with activity, false otherwise
+ */
+@KauUtils fun Context.isAppInstalled(packageName: String): Boolean {
+ val pm = packageManager
+ var installed: Boolean
+ try {
+ pm.getPackageInfo(packageName, PackageManager.GET_ACTIVITIES)
+ installed = true
+ } catch (e: PackageManager.NameNotFoundException) {
+ installed = false
+ }
+ return installed
+}
+
+val buildIsLollipopAndUp: Boolean
+ get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
+
+val buildIsMarshmallowAndUp: Boolean
+ get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
+
+val buildIsNougatAndUp: Boolean
+ get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
+
+const val INSTALLER_GOOGLE_PLAY_VENDING = "com.android.vending"
+const val INSTALLER_GOOGLE_PLAY_FEEDBACK = "com.google.android.feedback"
+
+val Context.installerPackageName: String?
+ get() = packageManager.getInstallerPackageName(packageName)
+
+val Context.isFromGooglePlay: Boolean
+ get() {
+ val installer = installerPackageName
+ return arrayOf(INSTALLER_GOOGLE_PLAY_FEEDBACK, INSTALLER_GOOGLE_PLAY_VENDING).any { it == installer }
+ } \ No newline at end of file
diff --git a/core/src/main/kotlin/ca/allanwang/kau/utils/StringHolder.kt b/core/src/main/kotlin/ca/allanwang/kau/utils/StringHolder.kt
new file mode 100644
index 0000000..e70a2d1
--- /dev/null
+++ b/core/src/main/kotlin/ca/allanwang/kau/utils/StringHolder.kt
@@ -0,0 +1,22 @@
+package ca.allanwang.kau.utils
+
+import android.content.Context
+import android.support.annotation.StringRes
+
+/**
+ * Created by Allan Wang on 2017-06-08.
+ */
+class StringHolder {
+ var text: String? = null
+ var textRes: Int = 0
+
+ constructor(@StringRes textRes: Int) {
+ this.textRes = textRes
+ }
+
+ constructor(text: String) {
+ this.text = text
+ }
+
+ fun getString(context: Context) = context.string(textRes, text)
+} \ No newline at end of file
diff --git a/core/src/main/kotlin/ca/allanwang/kau/utils/TransitionUtils.kt b/core/src/main/kotlin/ca/allanwang/kau/utils/TransitionUtils.kt
new file mode 100644
index 0000000..9e668d0
--- /dev/null
+++ b/core/src/main/kotlin/ca/allanwang/kau/utils/TransitionUtils.kt
@@ -0,0 +1,19 @@
+package ca.allanwang.kau.utils
+
+import android.support.transition.Transition
+import android.support.transition.TransitionSet
+
+/**
+ * Created by Allan Wang on 2017-06-24.
+ */
+class TransitionEndListener(val onEnd: (transition: Transition) -> Unit) : Transition.TransitionListener {
+ override fun onTransitionEnd(transition: Transition) = onEnd(transition)
+ override fun onTransitionResume(transition: Transition) {}
+ override fun onTransitionPause(transition: Transition) {}
+ override fun onTransitionCancel(transition: Transition) {}
+ override fun onTransitionStart(transition: Transition) {}
+}
+
+@KauUtils fun TransitionSet.addEndListener(onEnd: (transition: Transition) -> Unit) {
+ addListener(TransitionEndListener(onEnd))
+} \ No newline at end of file
diff --git a/core/src/main/kotlin/ca/allanwang/kau/utils/Utils.kt b/core/src/main/kotlin/ca/allanwang/kau/utils/Utils.kt
new file mode 100644
index 0000000..84794f9
--- /dev/null
+++ b/core/src/main/kotlin/ca/allanwang/kau/utils/Utils.kt
@@ -0,0 +1,111 @@
+package ca.allanwang.kau.utils
+
+import android.content.Context
+import android.content.res.Resources
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.Drawable
+import android.os.Handler
+import android.os.Looper
+import android.support.annotation.IntRange
+import ca.allanwang.kau.R
+import ca.allanwang.kau.logging.KL
+import java.math.RoundingMode
+import java.text.DecimalFormat
+
+
+/**
+ * Created by Allan Wang on 2017-05-28.
+ */
+
+/**
+ * Markers to isolate respective extension @KauUtils functions to their extended class
+ * Avoids having a whole bunch of methods for nested calls
+ */
+@DslMarker
+annotation class KauUtils
+
+@KauUtils val Int.dpToPx: Int
+ get() = (this * Resources.getSystem().displayMetrics.density).toInt()
+
+@KauUtils val Int.pxToDp: Int
+ get() = (this / Resources.getSystem().displayMetrics.density).toInt()
+
+/**
+ * Log whether current state is in the main thread
+ */
+@KauUtils fun checkThread(id: Int) {
+ val status = if (Looper.myLooper() == Looper.getMainLooper()) "is" else "is not"
+ KL.d("$id $status in the main thread")
+}
+
+/**
+ * Converts minute value to string
+ * Whole hours and days will be converted as such, otherwise it will default to x minutes
+ */
+@KauUtils fun Context.minuteToText(minutes: Long): String = with(minutes) {
+ if (this < 0L) string(R.string.kau_none)
+ else if (this == 60L) string(R.string.kau_one_hour)
+ else if (this == 1440L) string(R.string.kau_one_day)
+ else if (this % 1440L == 0L) String.format(string(R.string.kau_x_days), this / 1440L)
+ else if (this % 60L == 0L) String.format(string(R.string.kau_x_hours), this / 60L)
+ else String.format(string(R.string.kau_x_minutes), this)
+}
+
+@KauUtils fun Number.round(@IntRange(from = 1L) decimalCount: Int): String {
+ val expression = StringBuilder().append("#.")
+ (1..decimalCount).forEach { expression.append("#") }
+ val formatter = DecimalFormat(expression.toString())
+ formatter.roundingMode = RoundingMode.HALF_UP
+ return formatter.format(this)
+}
+
+/**
+ * Extracts the bitmap of a drawable, and applies a scale if given
+ * For solid colors, a 1 x 1 pixel will be generated
+ */
+@KauUtils fun Drawable.toBitmap(scaling: Float = 1f, config: Bitmap.Config = Bitmap.Config.ARGB_8888): Bitmap {
+ if (this is BitmapDrawable && bitmap != null) {
+ if (scaling == 1f) return bitmap
+ val width = (bitmap.width * scaling).toInt()
+ val height = (bitmap.height * scaling).toInt()
+ return Bitmap.createScaledBitmap(bitmap, width, height, false)
+ }
+ val bitmap = if (intrinsicWidth <= 0 || intrinsicHeight <= 0)
+ Bitmap.createBitmap(1, 1, config)
+ else
+ Bitmap.createBitmap((intrinsicWidth * scaling).toInt(), (intrinsicHeight * scaling).toInt(), config)
+ val canvas = Canvas(bitmap)
+ setBounds(0, 0, canvas.width, canvas.height)
+ draw(canvas)
+ return bitmap
+}
+
+/**
+ * Use block for autocloseables
+ */
+inline fun <T : AutoCloseable, R> T.use(block: (T) -> R): R {
+ var closed = false
+ try {
+ return block(this)
+ } catch (e: Exception) {
+ closed = true
+ try {
+ close()
+ } catch (closeException: Exception) {
+ e.addSuppressed(closeException)
+ }
+ throw e
+ } finally {
+ if (!closed) {
+ close()
+ }
+ }
+}
+
+fun postDelayed(delay: Long, action: () -> Unit) {
+ Handler().postDelayed(action, delay)
+}
+
+class KauException(message: String) : RuntimeException(message) \ No newline at end of file
diff --git a/core/src/main/kotlin/ca/allanwang/kau/utils/ViewUtils.kt b/core/src/main/kotlin/ca/allanwang/kau/utils/ViewUtils.kt
new file mode 100644
index 0000000..b4752a5
--- /dev/null
+++ b/core/src/main/kotlin/ca/allanwang/kau/utils/ViewUtils.kt
@@ -0,0 +1,126 @@
+package ca.allanwang.kau.utils
+
+import android.content.Context
+import android.graphics.Color
+import android.graphics.Outline
+import android.graphics.Rect
+import android.support.annotation.ColorInt
+import android.support.annotation.StringRes
+import android.support.annotation.TransitionRes
+import android.support.design.widget.Snackbar
+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.View
+import android.view.ViewGroup
+import android.view.ViewOutlineProvider
+import android.view.inputmethod.InputMethodManager
+import android.widget.ImageView
+import android.widget.TextView
+import ca.allanwang.kau.logging.KL
+import ca.allanwang.kau.views.createSimpleRippleDrawable
+import com.mikepenz.iconics.IconicsDrawable
+import com.mikepenz.iconics.typeface.IIcon
+
+
+/**
+ * Created by Allan Wang on 2017-05-31.
+ */
+@KauUtils fun <T : View> T.visible(): T {
+ visibility = View.VISIBLE
+ return this
+}
+
+@KauUtils fun <T : View> T.invisible(): T {
+ visibility = View.INVISIBLE
+ return this
+}
+
+@KauUtils fun <T : View> T.gone(): T {
+ visibility = View.GONE
+ return this
+}
+
+@KauUtils fun View.isVisible(): Boolean = visibility == View.VISIBLE
+@KauUtils fun View.isInvisible(): Boolean = visibility == View.INVISIBLE
+@KauUtils fun View.isGone(): Boolean = visibility == View.GONE
+
+fun View.snackbar(text: String, duration: Int = Snackbar.LENGTH_LONG, builder: Snackbar.() -> Unit = {}): Snackbar {
+ val snackbar = Snackbar.make(this, text, duration)
+ snackbar.builder()
+ snackbar.show()
+ return snackbar
+}
+
+fun View.snackbar(@StringRes textId: Int, duration: Int = Snackbar.LENGTH_LONG, builder: Snackbar.() -> Unit = {})
+ = snackbar(context.string(textId), duration, builder)
+
+@KauUtils fun TextView.setTextIfValid(@StringRes id: Int) {
+ if (id > 0) text = context.string(id)
+}
+
+@KauUtils fun ImageView.setIcon(icon: IIcon?, sizeDp: Int = 24, @ColorInt color: Int = Color.WHITE, builder: IconicsDrawable.() -> Unit = {}) {
+ if (icon == null) return
+ setImageDrawable(icon.toDrawable(context, sizeDp = sizeDp, color = color, builder = builder))
+}
+
+@KauUtils fun View.hideKeyboard() {
+ clearFocus()
+ (context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager).hideSoftInputFromWindow(windowToken, 0)
+}
+
+@KauUtils fun View.showKeyboard() {
+ requestFocus()
+ (context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager).showSoftInput(this, InputMethodManager.SHOW_IMPLICIT)
+}
+
+@KauUtils fun ViewGroup.transitionAuto(builder: AutoTransition.() -> Unit = {}) {
+ val transition = AutoTransition()
+ transition.builder()
+ TransitionManager.beginDelayedTransition(this, transition)
+}
+
+@KauUtils fun ViewGroup.transitionDelayed(@TransitionRes id: Int, builder: Transition.() -> Unit = {}) {
+ val transition = TransitionInflater.from(context).inflateTransition(id)
+ transition.builder()
+ TransitionManager.beginDelayedTransition(this, transition)
+}
+
+@KauUtils fun View.setRippleBackground(@ColorInt foregroundColor: Int, @ColorInt backgroundColor: Int) {
+ background = createSimpleRippleDrawable(foregroundColor, backgroundColor)
+}
+
+@KauUtils val View.parentViewGroup: ViewGroup
+ get() = parent as ViewGroup
+
+@KauUtils val View.parentVisibleHeight: Int
+ get() {
+ val r = Rect()
+ parentViewGroup.getWindowVisibleDisplayFrame(r)
+ return r.height()
+ }
+
+val CIRCULAR_OUTLINE: ViewOutlineProvider = object : ViewOutlineProvider() {
+ override fun getOutline(view: View, outline: Outline) {
+ KL.d("CIRCULAR OUTLINE")
+ outline.setOval(view.paddingLeft,
+ view.paddingTop,
+ view.width - view.paddingRight,
+ view.height - view.paddingBottom)
+ }
+}
+
+/**
+ * Generates a recycler view with match parent and a linearlayoutmanager, since it's so commonly used
+ */
+fun Context.fullLinearRecycler(rvAdapter: RecyclerView.Adapter<*>? = null, configs: RecyclerView.() -> Unit = {}): RecyclerView {
+ return RecyclerView(this).apply {
+ layoutManager = LinearLayoutManager(this@fullLinearRecycler)
+ layoutParams = RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.MATCH_PARENT)
+ if (rvAdapter != null) adapter = rvAdapter
+ configs()
+ }
+} \ No newline at end of file