diff options
Diffstat (limited to 'core/src/main/kotlin/ca/allanwang/kau/utils')
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 |