From 02e1dbc84425b0ac7f771c82f70444f742397452 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Mon, 7 Aug 2017 22:20:57 -0700 Subject: Release 3.3.0 (#32) * Rewrite Logger (#29) * Remove dependency on timber * Update logger * Reorder throwabl * Fix lint * Update readme * Blank target * Create Zip (#30) * Finish zips with tests * Finalize * Update changelog * Add log hooks * Open most logging functions * Remap kpref items (#31) * Update readme * Generate files and prepare release * Kpref - --- .../src/main/kotlin/ca/allanwang/kau/kotlin/Zip.kt | 90 ++++++++++++++++++++++ .../src/main/kotlin/ca/allanwang/kau/logging/KL.kt | 2 +- .../ca/allanwang/kau/logging/TimberLogger.kt | 74 ++++++++++++++---- .../main/kotlin/ca/allanwang/kau/utils/Utils.kt | 8 -- .../kotlin/ca/allanwang/kau/utils/ViewUtils.kt | 20 +++-- 5 files changed, 165 insertions(+), 29 deletions(-) create mode 100644 core/src/main/kotlin/ca/allanwang/kau/kotlin/Zip.kt (limited to 'core/src/main/kotlin') diff --git a/core/src/main/kotlin/ca/allanwang/kau/kotlin/Zip.kt b/core/src/main/kotlin/ca/allanwang/kau/kotlin/Zip.kt new file mode 100644 index 0000000..cff520f --- /dev/null +++ b/core/src/main/kotlin/ca/allanwang/kau/kotlin/Zip.kt @@ -0,0 +1,90 @@ +package ca.allanwang.kau.kotlin + +import org.jetbrains.anko.doAsync +import java.util.concurrent.atomic.AtomicInteger + +/** + * Created by Allan Wang on 2017-08-06. + * + * Collection of zip methods that aim to replicate + * Reactive Zips + * For unit returning functions + * + * Typically, the functions will execute asynchronously and call their given callbacks when finished. + * Once all callbacks are called, the final onFinish callback will be executed. + * + * There is also a helper zipper to wrap synchronous functions with Anko's doAsync to achieve the same results + * + * Note that not wrapping synchronous functions will render these methods useless, + * as you can simply define an inline callback after all functions are finished + */ + +/** + * Callback which will only execute the first time + */ +open class ZipCallbackBase { + var completed: Boolean = false + + inline operator fun invoke(callback: () -> Unit) { + if (completed) return + completed = true + callback() + } +} + +class ZipCallback(val onReceived: (T) -> Unit) : ZipCallbackBase() { + operator fun invoke(result: T) = invoke { onReceived(result) } +} + +class ZipEmptyCallback(val onReceived: () -> Unit) : ZipCallbackBase() { + operator fun invoke() = invoke(onReceived) +} + +/** + * Given a default result, a series of tasks, and a finished callback, + * this method will run all tasks and wait until all tasks emit a response + * The response will then be sent back to the callback + * + * ALl tasks must invoke the task callback for [onFinished] to execute + */ +inline fun Collection<(ZipCallback) -> Unit>.zip( + defaultResult: T, crossinline onFinished: (results: Array) -> Unit +) { + val result = Array(size) { defaultResult } + val countDown = AtomicInteger(size) + forEachIndexed { index, asyncFun -> + asyncFun(ZipCallback { + result[index] = it + if (countDown.decrementAndGet() <= 0) + onFinished(result) + }) + } +} + +/** + * Simplified zip method with no finished callback arguments + */ +inline fun Collection<(ZipEmptyCallback) -> Unit>.zip(crossinline onFinished: () -> Unit) { + val countDown = AtomicInteger(size) + forEach { asyncFun -> + asyncFun(ZipEmptyCallback { + if (countDown.decrementAndGet() <= 0) + onFinished() + }) + } +} + +/** + * Converts a collection of synchronous tasks to asynchronous tasks with a common callback + */ +inline fun Collection<() -> Unit>.zipAsync(crossinline onFinished: () -> Unit) { + map { synchronousFun -> + { + callback: ZipEmptyCallback -> + doAsync { + synchronousFun() + callback() + }; Unit + } + }.zip(onFinished) +} diff --git a/core/src/main/kotlin/ca/allanwang/kau/logging/KL.kt b/core/src/main/kotlin/ca/allanwang/kau/logging/KL.kt index 4fa3360..24146b0 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/logging/KL.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/logging/KL.kt @@ -3,4 +3,4 @@ package ca.allanwang.kau.logging /** * Created by Allan Wang on 2017-06-19. */ -object KL : TimberLogger("KAU") \ No newline at end of file +object KL : KauLogger("KAU") \ No newline at end of file diff --git a/core/src/main/kotlin/ca/allanwang/kau/logging/TimberLogger.kt b/core/src/main/kotlin/ca/allanwang/kau/logging/TimberLogger.kt index 4cf566a..2fbecf5 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/logging/TimberLogger.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/logging/TimberLogger.kt @@ -2,23 +2,71 @@ package ca.allanwang.kau.logging -import timber.log.Timber +import android.os.Looper +import android.util.Log /** * Created by Allan Wang on 2017-05-28. * - * Timber extension that will embed the tag as part of the message for each log item + * Base logger class with a predefined tag + * This may be extended by an object to effectively replace [Log] */ -open class TimberLogger(tag: String) { - val TAG = "$tag: %s" - inline fun e(s: String) = Timber.e(TAG, s) - inline fun e(t: Throwable?, s: String = "error") = if (t == null) e(s) else Timber.e(t, TAG, s) - inline fun d(s: String) = Timber.d(TAG, s) - inline fun i(s: String) = Timber.i(TAG, s) - inline fun v(s: String) = Timber.v(TAG, s) - inline fun eThrow(s: String) = e(Throwable(s)) -// fun plant() { -// Timber.plant(Timber.Tree()) -// } +open class KauLogger(val tag: String) { + + open var enabled = true + open var showPrivateText = false + + /** + * Filter pass-through to decide what we wish to log + * By default, we will ignore verbose and debug logs + * @returns {@code true} to log the message, {@code false} to ignore + */ + open var filter: (Int) -> Boolean = { it != Log.VERBOSE && it != Log.DEBUG } + + open fun disable(disable: Boolean = true): KauLogger { + enabled = !disable + return this + } + + open fun debug(enable: Boolean) { + filter = if (enable) { _ -> true } else { i -> i != Log.VERBOSE && i != Log.DEBUG } + showPrivateText = enable + } + + open fun log(priority: Int, message: String?, privateMessage: String?, t: Throwable? = null) { + if (!shouldLog(priority, message, privateMessage, t)) return + logImpl(priority, message, privateMessage, t) + } + + protected open fun shouldLog(priority: Int, message: String?, privateMessage: String?, t: Throwable?): Boolean + = enabled && filter(priority) + + protected open fun logImpl(priority: Int, message: String?, privateMessage: String?, t: Throwable?) { + var text = message ?: "" + if (showPrivateText && privateMessage != null) + text += "\n-\t$privateMessage" + if (t != null) Log.e(tag, text, t) + else if (text.isNotBlank()) Log.println(priority, tag, text) + } + + open fun v(text: String?, privateText: String? = null) = log(Log.VERBOSE, text, privateText) + open fun d(text: String?, privateText: String? = null) = log(Log.DEBUG, text, privateText) + open fun i(text: String?, privateText: String? = null) = log(Log.INFO, text, privateText) + open fun e(text: String?, privateText: String? = null) = log(Log.ERROR, text, privateText) + open fun a(text: String?, privateText: String? = null) = log(Log.ASSERT, text, privateText) + open fun e(t: Throwable?, text: String?, privateText: String? = null) = log(Log.ERROR, text, privateText, t) + open fun eThrow(text: String?) { + if (text != null) + e(Throwable(text), text) + } + + /** + * Log the looper + */ + open fun checkThread(id: Int) { + val name = Thread.currentThread().name + val status = if (Looper.myLooper() == Looper.getMainLooper()) "is" else "is not" + d("$id $status in the main thread - thread name: $name") + } } \ 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 index e8f385a..2f3e9a5 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/utils/Utils.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/utils/Utils.kt @@ -38,14 +38,6 @@ annotation class KauUtils @KauUtils inline 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 diff --git a/core/src/main/kotlin/ca/allanwang/kau/utils/ViewUtils.kt b/core/src/main/kotlin/ca/allanwang/kau/utils/ViewUtils.kt index 53d711d..3620a4a 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/utils/ViewUtils.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/utils/ViewUtils.kt @@ -211,13 +211,19 @@ inline val TextInputEditText.value: String get() = text.toString().trim() /** * 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() - } +fun Context.fullLinearRecycler(rvAdapter: RecyclerView.Adapter<*>? = null, configs: RecyclerView.() -> Unit = {}) = RecyclerView(this).apply { + layoutManager = LinearLayoutManager(this@fullLinearRecycler) + layoutParams = RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.MATCH_PARENT) + if (rvAdapter != null) adapter = rvAdapter + configs() +} + +/** + * Sets a linear layout manager along with an adapter + */ +fun RecyclerView.withLinearAdapter(rvAdapter: RecyclerView.Adapter<*>) = apply { + layoutManager = LinearLayoutManager(context) + adapter = rvAdapter } /** -- cgit v1.2.3