aboutsummaryrefslogtreecommitdiff
path: root/core/src
diff options
context:
space:
mode:
Diffstat (limited to 'core/src')
-rw-r--r--core/src/main/kotlin/ca/allanwang/kau/kotlin/Zip.kt90
-rw-r--r--core/src/main/kotlin/ca/allanwang/kau/logging/KL.kt2
-rw-r--r--core/src/main/kotlin/ca/allanwang/kau/logging/TimberLogger.kt74
-rw-r--r--core/src/main/kotlin/ca/allanwang/kau/utils/Utils.kt8
-rw-r--r--core/src/main/kotlin/ca/allanwang/kau/utils/ViewUtils.kt20
-rw-r--r--core/src/test/kotlin/ca/allanwang/kau/kotlin/ZipTest.kt72
6 files changed, 237 insertions, 29 deletions
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
+ * <a href="http://reactivex.io/documentation/operators/zip.html">Reactive Zips</a>
+ * 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<T>(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 <reified T> Collection<(ZipCallback<T>) -> Unit>.zip(
+ defaultResult: T, crossinline onFinished: (results: Array<T>) -> Unit
+) {
+ val result = Array(size) { defaultResult }
+ val countDown = AtomicInteger(size)
+ forEachIndexed { index, asyncFun ->
+ asyncFun(ZipCallback<T> {
+ 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
@@ -39,14 +39,6 @@ annotation class KauUtils
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
}
/**
diff --git a/core/src/test/kotlin/ca/allanwang/kau/kotlin/ZipTest.kt b/core/src/test/kotlin/ca/allanwang/kau/kotlin/ZipTest.kt
new file mode 100644
index 0000000..4a04142
--- /dev/null
+++ b/core/src/test/kotlin/ca/allanwang/kau/kotlin/ZipTest.kt
@@ -0,0 +1,72 @@
+package ca.allanwang.kau.kotlin
+
+import org.jetbrains.anko.doAsync
+import org.junit.Test
+import java.util.*
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+import kotlin.test.assertTrue
+
+/**
+ * Created by Allan Wang on 2017-08-06.
+ */
+class ZipTest {
+
+ val debug = false
+
+ fun p(text: String) {
+ if (debug) println(text)
+ }
+
+ @Test
+ fun basic() {
+ val start = System.currentTimeMillis()
+ val latch = CountDownLatch(1)
+ val rnd = Random()
+ (0..10).map {
+ {
+ callback: ZipCallback<Int> ->
+ doAsync {
+ val sleepTime = rnd.nextInt(100) + 200L
+ p("Task $it will sleep for ${sleepTime}ms")
+ Thread.sleep(sleepTime)
+ val finish = System.currentTimeMillis()
+ p("Task $it finished in ${finish - start}ms at $finish")
+ callback(it)
+ }; Unit
+ }
+ }.zip(-1) {
+ results ->
+ val finish = System.currentTimeMillis()
+ println("Results ${results.contentToString()} received in ${finish - start}ms at $finish")
+ assertTrue((0..10).toList().toTypedArray().contentEquals(results), "Basic zip results do not match")
+ assertTrue(finish - start < 1000L, "Basic zip does not seem to be running asynchronously")
+ latch.countDown()
+
+ }
+ latch.await(1100, TimeUnit.MILLISECONDS)
+ }
+
+ @Test
+ fun basicAsync() {
+ val start = System.currentTimeMillis()
+ val latch = CountDownLatch(1)
+ val rnd = Random()
+ (0..10).map {
+ {
+ val sleepTime = rnd.nextInt(100) + 200L
+ p("Task $it will sleep for ${sleepTime}ms")
+ Thread.sleep(sleepTime)
+ val finish = System.currentTimeMillis()
+ p("Task $it finished in ${finish - start}ms at $finish")
+ }
+ }.zipAsync {
+ val finish = System.currentTimeMillis()
+ println("Results received in ${finish - start}ms at $finish")
+ assertTrue(finish - start < 1000L, "BasicAsync does not seem to be wrapping the tasks asynchronously")
+ latch.countDown()
+ }
+ latch.await(1100, TimeUnit.MILLISECONDS)
+ }
+
+} \ No newline at end of file