aboutsummaryrefslogtreecommitdiff
path: root/core/src/main
diff options
context:
space:
mode:
authorAllan Wang <me@allanwang.ca>2018-12-24 20:05:06 -0500
committerGitHub <noreply@github.com>2018-12-24 20:05:06 -0500
commit8447b1ae8ce89b3f1bbe79dbae8847d901831c12 (patch)
treece516950e452581766e905ead32970d891bb46f6 /core/src/main
parent701b94ab09ff53aca682fac6c4ef5364566339be (diff)
downloadkau-8447b1ae8ce89b3f1bbe79dbae8847d901831c12.tar.gz
kau-8447b1ae8ce89b3f1bbe79dbae8847d901831c12.tar.bz2
kau-8447b1ae8ce89b3f1bbe79dbae8847d901831c12.zip
Enhancement/coroutines (#180)
* Add coroutine dependency * Add coroutines to kprefactivity * Change base job to supervisor * Update coroutines for faq * Update changelog * Use preloading in media picker core * Make test logging internal * Remove anko
Diffstat (limited to 'core/src/main')
-rw-r--r--core/src/main/kotlin/ca/allanwang/kau/internal/KauBaseActivity.kt30
-rw-r--r--core/src/main/kotlin/ca/allanwang/kau/kotlin/Zip.kt105
-rw-r--r--core/src/main/kotlin/ca/allanwang/kau/logging/KL.kt7
-rw-r--r--core/src/main/kotlin/ca/allanwang/kau/utils/ActivityUtils.kt13
-rw-r--r--core/src/main/kotlin/ca/allanwang/kau/utils/BundleUtils.kt55
-rw-r--r--core/src/main/kotlin/ca/allanwang/kau/utils/ContextUtils.kt27
-rw-r--r--core/src/main/kotlin/ca/allanwang/kau/utils/FragmentUtils.kt3
-rw-r--r--core/src/main/kotlin/ca/allanwang/kau/xml/Changelog.kt21
-rw-r--r--core/src/main/kotlin/ca/allanwang/kau/xml/FAQ.kt71
9 files changed, 170 insertions, 162 deletions
diff --git a/core/src/main/kotlin/ca/allanwang/kau/internal/KauBaseActivity.kt b/core/src/main/kotlin/ca/allanwang/kau/internal/KauBaseActivity.kt
index 85e711b..bf977f2 100644
--- a/core/src/main/kotlin/ca/allanwang/kau/internal/KauBaseActivity.kt
+++ b/core/src/main/kotlin/ca/allanwang/kau/internal/KauBaseActivity.kt
@@ -15,8 +15,14 @@
*/
package ca.allanwang.kau.internal
+import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import ca.allanwang.kau.permissions.kauOnRequestPermissionsResult
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.SupervisorJob
+import kotlin.coroutines.CoroutineContext
/**
* Created by Allan Wang on 2017-08-01.
@@ -26,11 +32,31 @@ import ca.allanwang.kau.permissions.kauOnRequestPermissionsResult
* Ensures that some singleton methods are called.
* This is simply a convenience class;
* you can always copy and paste this to your own class.
+ *
+ * This also implements [CoroutineScope] that adheres to the activity lifecycle.
+ * Note that by default, [SupervisorJob] is used, to avoid exceptions in one child from affecting that of another.
+ * The default job can be overridden within [defaultJob]
*/
-abstract class KauBaseActivity : AppCompatActivity() {
+abstract class KauBaseActivity : AppCompatActivity(), CoroutineScope {
+
+ open lateinit var job: Job
+ override val coroutineContext: CoroutineContext
+ get() = Dispatchers.Main + job
+
+ open fun defaultJob(): Job = SupervisorJob()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ job = defaultJob()
+ }
+
+ override fun onDestroy() {
+ job.cancel()
+ super.onDestroy()
+ }
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
kauOnRequestPermissionsResult(permissions, grantResults)
}
-}
+} \ No newline at end of file
diff --git a/core/src/main/kotlin/ca/allanwang/kau/kotlin/Zip.kt b/core/src/main/kotlin/ca/allanwang/kau/kotlin/Zip.kt
deleted file mode 100644
index b767b30..0000000
--- a/core/src/main/kotlin/ca/allanwang/kau/kotlin/Zip.kt
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright 2018 Allan Wang
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-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 {
- 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 f92edb3..52e5415 100644
--- a/core/src/main/kotlin/ca/allanwang/kau/logging/KL.kt
+++ b/core/src/main/kotlin/ca/allanwang/kau/logging/KL.kt
@@ -22,4 +22,9 @@ import ca.allanwang.kau.BuildConfig
*
* Internal KAU logger
*/
-object KL : KauLogger("KAU", { BuildConfig.DEBUG })
+object KL : KauLogger("KAU", { BuildConfig.DEBUG }) {
+ internal inline fun test(message: () -> Any?) {
+ if (BuildConfig.DEBUG)
+ d { "Test1234 ${message()}" }
+ }
+}
diff --git a/core/src/main/kotlin/ca/allanwang/kau/utils/ActivityUtils.kt b/core/src/main/kotlin/ca/allanwang/kau/utils/ActivityUtils.kt
index a655e5b..3dd4bb9 100644
--- a/core/src/main/kotlin/ca/allanwang/kau/utils/ActivityUtils.kt
+++ b/core/src/main/kotlin/ca/allanwang/kau/utils/ActivityUtils.kt
@@ -28,13 +28,13 @@ import android.os.Build
import android.os.Bundle
import android.view.Menu
import android.view.View
+import android.view.ViewGroup
import androidx.annotation.ColorInt
import androidx.annotation.RequiresApi
import androidx.annotation.StringRes
import ca.allanwang.kau.R
import com.google.android.material.snackbar.Snackbar
import com.mikepenz.iconics.typeface.IIcon
-import org.jetbrains.anko.contentView
/**
* Created by Allan Wang on 2017-06-21.
@@ -160,6 +160,17 @@ inline fun Activity.showKeyboard() {
currentFocus?.showKeyboard()
}
+/**
+ * Gets the view set by [Activity.setContentView] if it exists.
+ *
+ * Taken courtesy of <a href="https://github.com/Kotlin/anko">Anko</a>
+ *
+ * Previously, Anko was a dependency in KAU, but has been removed on 12/24/2018
+ * as most of the methods weren't used
+ */
+inline val Activity.contentView: View?
+ get() = (findViewById(android.R.id.content) as? ViewGroup)?.getChildAt(0)
+
inline fun Activity.snackbar(
text: String,
duration: Int = Snackbar.LENGTH_LONG,
diff --git a/core/src/main/kotlin/ca/allanwang/kau/utils/BundleUtils.kt b/core/src/main/kotlin/ca/allanwang/kau/utils/BundleUtils.kt
index 314ca60..5b4b188 100644
--- a/core/src/main/kotlin/ca/allanwang/kau/utils/BundleUtils.kt
+++ b/core/src/main/kotlin/ca/allanwang/kau/utils/BundleUtils.kt
@@ -20,10 +20,12 @@ import android.app.Activity
import android.app.ActivityOptions
import android.content.Context
import android.os.Bundle
+import android.os.Parcelable
import android.util.Pair
import android.view.View
import androidx.annotation.AnimRes
import ca.allanwang.kau.R
+import java.io.Serializable
/**
* Created by Allan Wang on 10/12/17.
@@ -37,6 +39,56 @@ infix fun Bundle.with(bundle: Bundle?): Bundle {
}
/**
+ * Saves all bundle args based on their respective types.
+ *
+ * Taken courtesy of <a href="https://github.com/Kotlin/anko">Anko</a>
+ *
+ * Previously, Anko was a dependency in KAU, but has been removed on 12/24/2018
+ * as most of the methods weren't used
+ */
+fun bundleOf(vararg params: kotlin.Pair<String, Any?>): Bundle {
+ val b = Bundle()
+ for (p in params) {
+ val (k, v) = p
+ when (v) {
+ null -> b.putSerializable(k, null)
+ is Boolean -> b.putBoolean(k, v)
+ is Byte -> b.putByte(k, v)
+ is Char -> b.putChar(k, v)
+ is Short -> b.putShort(k, v)
+ is Int -> b.putInt(k, v)
+ is Long -> b.putLong(k, v)
+ is Float -> b.putFloat(k, v)
+ is Double -> b.putDouble(k, v)
+ is String -> b.putString(k, v)
+ is CharSequence -> b.putCharSequence(k, v)
+ is Parcelable -> b.putParcelable(k, v)
+ is Serializable -> b.putSerializable(k, v)
+ is BooleanArray -> b.putBooleanArray(k, v)
+ is ByteArray -> b.putByteArray(k, v)
+ is CharArray -> b.putCharArray(k, v)
+ is DoubleArray -> b.putDoubleArray(k, v)
+ is FloatArray -> b.putFloatArray(k, v)
+ is IntArray -> b.putIntArray(k, v)
+ is LongArray -> b.putLongArray(k, v)
+ is Array<*> -> {
+ @Suppress("UNCHECKED_CAST")
+ when {
+ v.isArrayOf<Parcelable>() -> b.putParcelableArray(k, v as Array<out Parcelable>)
+ v.isArrayOf<CharSequence>() -> b.putCharSequenceArray(k, v as Array<out CharSequence>)
+ v.isArrayOf<String>() -> b.putStringArray(k, v as Array<out String>)
+ else -> throw KauException("Unsupported bundle component (${v.javaClass})")
+ }
+ }
+ is ShortArray -> b.putShortArray(k, v)
+ is Bundle -> b.putBundle(k, v)
+ else -> throw KauException("Unsupported bundle component (${v.javaClass})")
+ }
+ }
+ return b
+}
+
+/**
* Adds transition bundle if context is activity and build is lollipop+
*/
@SuppressLint("NewApi")
@@ -62,7 +114,8 @@ fun Bundle.withSceneTransitionAnimation(parent: View, data: Map<Int, String>) =
@SuppressLint("NewApi")
fun Bundle.withSceneTransitionAnimation(context: Context, data: Map<View, String>) {
if (context !is Activity || !buildIsLollipopAndUp) return
- val options = ActivityOptions.makeSceneTransitionAnimation(context,
+ val options = ActivityOptions.makeSceneTransitionAnimation(
+ context,
*data.map { (view, tag) -> Pair(view, tag) }.toTypedArray()
)
putAll(options.toBundle())
diff --git a/core/src/main/kotlin/ca/allanwang/kau/utils/ContextUtils.kt b/core/src/main/kotlin/ca/allanwang/kau/utils/ContextUtils.kt
index 134126d..60ef236 100644
--- a/core/src/main/kotlin/ca/allanwang/kau/utils/ContextUtils.kt
+++ b/core/src/main/kotlin/ca/allanwang/kau/utils/ContextUtils.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2018 Allan Wang
+ * Copyright 2017 Allan Wang
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,6 +26,8 @@ import android.content.pm.PackageManager
import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Bundle
+import android.os.Handler
+import android.os.Looper
import android.util.TypedValue
import android.view.View
import android.view.animation.AnimationUtils
@@ -45,10 +47,33 @@ import androidx.core.content.ContextCompat
import ca.allanwang.kau.R
import ca.allanwang.kau.logging.KL
import com.afollestad.materialdialogs.MaterialDialog
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlin.coroutines.CoroutineContext
/**
* Created by Allan Wang on 2017-06-03.
*/
+private object ContextHelper: CoroutineScope {
+
+ val handler = Handler(Looper.getMainLooper())
+
+ override val coroutineContext: CoroutineContext
+ get() = Dispatchers.Main
+}
+
+/**
+ * Most context items implement [CoroutineScope] by default.
+ * We will add a fallback just in case.
+ * It is expected that the scope returned always has the Android main dispatcher as part of the context.
+ */
+internal inline val Context.ctxCoroutine: CoroutineScope
+ get() = this as? CoroutineScope ?: ContextHelper
+
+fun Context.runOnUiThread(f: Context.() -> Unit) {
+ if (Looper.getMainLooper() === Looper.myLooper()) f() else ContextHelper.handler.post { f() }
+}
/**
* Helper class to launch an activity from a context
diff --git a/core/src/main/kotlin/ca/allanwang/kau/utils/FragmentUtils.kt b/core/src/main/kotlin/ca/allanwang/kau/utils/FragmentUtils.kt
index 1c97900..75dc1c1 100644
--- a/core/src/main/kotlin/ca/allanwang/kau/utils/FragmentUtils.kt
+++ b/core/src/main/kotlin/ca/allanwang/kau/utils/FragmentUtils.kt
@@ -16,12 +16,11 @@
package ca.allanwang.kau.utils
import androidx.fragment.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 {
+fun <T : Fragment> T.withArguments(vararg params: Pair<String, Any?>): T {
arguments = bundleOf(*params)
return this
}
diff --git a/core/src/main/kotlin/ca/allanwang/kau/xml/Changelog.kt b/core/src/main/kotlin/ca/allanwang/kau/xml/Changelog.kt
index 6a75aa9..51e63f9 100644
--- a/core/src/main/kotlin/ca/allanwang/kau/xml/Changelog.kt
+++ b/core/src/main/kotlin/ca/allanwang/kau/xml/Changelog.kt
@@ -26,11 +26,12 @@ import androidx.annotation.LayoutRes
import androidx.annotation.XmlRes
import androidx.recyclerview.widget.RecyclerView
import ca.allanwang.kau.R
+import ca.allanwang.kau.utils.ctxCoroutine
import ca.allanwang.kau.utils.materialDialog
import ca.allanwang.kau.utils.use
import com.afollestad.materialdialogs.MaterialDialog
-import org.jetbrains.anko.doAsync
-import org.jetbrains.anko.uiThread
+import kotlinx.coroutines.async
+import kotlinx.coroutines.launch
import org.xmlpull.v1.XmlPullParser
/**
@@ -39,15 +40,13 @@ import org.xmlpull.v1.XmlPullParser
* Easy changelog loader
*/
fun Context.showChangelog(@XmlRes xmlRes: Int, @ColorInt textColor: Int? = null, customize: MaterialDialog.Builder.() -> Unit = {}) {
- doAsync {
- val items = parse(this@showChangelog, xmlRes)
- uiThread {
- materialDialog {
- title(R.string.kau_changelog)
- positiveText(R.string.kau_great)
- adapter(ChangelogAdapter(items, textColor), null)
- customize()
- }
+ ctxCoroutine.launch {
+ val items = async { parse(this@showChangelog, xmlRes) }.await()
+ materialDialog {
+ title(R.string.kau_changelog)
+ positiveText(R.string.kau_great)
+ adapter(ChangelogAdapter(items, textColor), null)
+ customize()
}
}
}
diff --git a/core/src/main/kotlin/ca/allanwang/kau/xml/FAQ.kt b/core/src/main/kotlin/ca/allanwang/kau/xml/FAQ.kt
index cb57216..73d7d6c 100644
--- a/core/src/main/kotlin/ca/allanwang/kau/xml/FAQ.kt
+++ b/core/src/main/kotlin/ca/allanwang/kau/xml/FAQ.kt
@@ -21,8 +21,6 @@ import android.text.Html
import android.text.Spanned
import androidx.annotation.XmlRes
import ca.allanwang.kau.utils.use
-import org.jetbrains.anko.doAsync
-import org.jetbrains.anko.uiThread
import org.xmlpull.v1.XmlPullParser
/**
@@ -30,8 +28,8 @@ import org.xmlpull.v1.XmlPullParser
*/
/**
- * Parse an xml asynchronously with two tags, <question>Text</question> and <answer>Text</answer>,
- * and invoke the [callback] on the ui thread
+ * Parse an xml asynchronously with two tags, <question>Text</question> and <answer>Text</answer>.
+ * Note that this should executed in a background thread.
*/
@Suppress("DEPRECATION")
fun Context.kauParseFaq(
@@ -39,47 +37,44 @@ fun Context.kauParseFaq(
/**
* If \n is used, it will automatically be converted to </br>
*/
- parseNewLine: Boolean = true,
- callback: (items: List<FaqItem>) -> Unit
-) {
- doAsync {
- val items = mutableListOf<FaqItem>()
- resources.getXml(xmlRes).use { parser: XmlResourceParser ->
- var eventType = parser.eventType
- var question: Spanned? = null
- var flag = -1 //-1, 0, 1 -> invalid, question, answer
- while (eventType != XmlPullParser.END_DOCUMENT) {
- if (eventType == XmlPullParser.START_TAG) {
- flag = when (parser.name) {
- "question" -> 0
- "answer" -> 1
- else -> -1
+ parseNewLine: Boolean = true
+): List<FaqItem> {
+ val items = mutableListOf<FaqItem>()
+ resources.getXml(xmlRes).use { parser: XmlResourceParser ->
+ var eventType = parser.eventType
+ var question: Spanned? = null
+ var flag = -1 //-1, 0, 1 -> invalid, question, answer
+ while (eventType != XmlPullParser.END_DOCUMENT) {
+ if (eventType == XmlPullParser.START_TAG) {
+ flag = when (parser.name) {
+ "question" -> 0
+ "answer" -> 1
+ else -> -1
+ }
+ } else if (eventType == XmlPullParser.TEXT) {
+ when (flag) {
+ 0 -> {
+ question = Html.fromHtml(parser.text.replace("\n", if (parseNewLine) "<br/>" else ""))
+ flag = -1
}
- } else if (eventType == XmlPullParser.TEXT) {
- when (flag) {
- 0 -> {
- question = Html.fromHtml(parser.text.replace("\n", if (parseNewLine) "<br/>" else ""))
- flag = -1
- }
- 1 -> {
- items.add(
- FaqItem(
- items.size + 1,
- question
- ?: throw IllegalArgumentException("KAU FAQ answer found without a question"),
- Html.fromHtml(parser.text.replace("\n", if (parseNewLine) "<br/>" else ""))
- )
+ 1 -> {
+ items.add(
+ FaqItem(
+ items.size + 1,
+ question
+ ?: throw IllegalArgumentException("KAU FAQ answer found without a question"),
+ Html.fromHtml(parser.text.replace("\n", if (parseNewLine) "<br/>" else ""))
)
- question = null
- flag = -1
- }
+ )
+ question = null
+ flag = -1
}
}
- eventType = parser.next()
}
+ eventType = parser.next()
}
- uiThread { callback(items) }
}
+ return items
}
data class FaqItem(val number: Int, val question: Spanned, val answer: Spanned)