From a380adea1052d39f23c9c4d432a9380ce347d6c4 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Mon, 24 Dec 2018 00:27:25 -0500 Subject: Migrate to androidx (#178) * Initial refactor * Remove alpha version usages * Update test code * Add tests for checkbox * Fix invalid card import * Remove more old support content * Update kotlin version * Add back kotterknife with new imports * Update docs * Use bold notice * Add changelog * Remove deprecation for kotterknife * Remove unused dependencies * Update changelog --- sample/src/main/res/xml/kau_changelog.xml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'sample/src/main/res/xml/kau_changelog.xml') diff --git a/sample/src/main/res/xml/kau_changelog.xml b/sample/src/main/res/xml/kau_changelog.xml index f6ec799..6c0fea1 100644 --- a/sample/src/main/res/xml/kau_changelog.xml +++ b/sample/src/main/res/xml/kau_changelog.xml @@ -6,6 +6,10 @@ --> + + + + @@ -13,7 +17,6 @@ - -- cgit v1.2.3 From 8447b1ae8ce89b3f1bbe79dbae8847d901831c12 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Mon, 24 Dec 2018 20:05:06 -0500 Subject: 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 --- README.md | 3 +- .../ca/allanwang/kau/about/AboutPanelDelegate.kt | 24 +++-- .../main/groovy/ca/allanwang/kau/Versions.groovy | 6 +- core/README.md | 4 +- core/build.gradle | 4 +- .../ca/allanwang/kau/internal/KauBaseActivity.kt | 30 +++++- .../src/main/kotlin/ca/allanwang/kau/kotlin/Zip.kt | 105 --------------------- .../src/main/kotlin/ca/allanwang/kau/logging/KL.kt | 7 +- .../kotlin/ca/allanwang/kau/utils/ActivityUtils.kt | 13 ++- .../kotlin/ca/allanwang/kau/utils/BundleUtils.kt | 55 ++++++++++- .../kotlin/ca/allanwang/kau/utils/ContextUtils.kt | 27 +++++- .../kotlin/ca/allanwang/kau/utils/FragmentUtils.kt | 3 +- .../main/kotlin/ca/allanwang/kau/xml/Changelog.kt | 21 ++--- core/src/main/kotlin/ca/allanwang/kau/xml/FAQ.kt | 71 +++++++------- .../test/kotlin/ca/allanwang/kau/kotlin/ZipTest.kt | 83 ---------------- docs/Changelog.md | 6 ++ docs/Migration.md | 13 +++ .../allanwang/kau/kpref/activity/KPrefActivity.kt | 31 +++--- .../allanwang/kau/mediapicker/MediaPickerCore.kt | 33 ++----- sample/src/main/res/xml/kau_changelog.xml | 10 ++ .../ca/allanwang/kau/searchview/SearchView.kt | 2 +- 21 files changed, 250 insertions(+), 301 deletions(-) delete mode 100644 core/src/main/kotlin/ca/allanwang/kau/kotlin/Zip.kt delete mode 100644 core/src/test/kotlin/ca/allanwang/kau/kotlin/ZipTest.kt (limited to 'sample/src/main/res/xml/kau_changelog.xml') diff --git a/README.md b/README.md index 4143ca8..6fe7590 100644 --- a/README.md +++ b/README.md @@ -103,10 +103,9 @@ This means that you'll need to explicitly include each submodule you'd like to u * [Extension Functions](core#extension-functions) * [Lazy Resettable](core#lazy-resettable) * Includes -[`AppCompat`](https://developer.android.com/topic/libraries/support-library/index.html), +[`AndroidX Components`](https://developer.android.com/topic/libraries/support-library/index.html), [`Material Dialogs (core)`](https://github.com/afollestad/material-dialogs), [`Iconics`](https://github.com/mikepenz/Android-Iconics), -[`Anko`](https://github.com/Kotlin/anko), [`Kotlin stdlib`](https://kotlinlang.org/api/latest/jvm/stdlib/) ## [Core UI](core-ui#readme) diff --git a/about/src/main/kotlin/ca/allanwang/kau/about/AboutPanelDelegate.kt b/about/src/main/kotlin/ca/allanwang/kau/about/AboutPanelDelegate.kt index 35c1322..f77bcf2 100644 --- a/about/src/main/kotlin/ca/allanwang/kau/about/AboutPanelDelegate.kt +++ b/about/src/main/kotlin/ca/allanwang/kau/about/AboutPanelDelegate.kt @@ -35,8 +35,9 @@ import ca.allanwang.kau.utils.withMarginDecoration import ca.allanwang.kau.xml.kauParseFaq import com.mikepenz.aboutlibraries.Libs import com.mikepenz.fastadapter.IItem -import org.jetbrains.anko.doAsync -import org.jetbrains.anko.uiThread +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.launch /** * Created by Allan Wang on 2017-08-02. @@ -174,13 +175,16 @@ open class AboutPanelLibs : AboutPanelRecycler() { } override fun loadItems(activity: AboutActivityBase, position: Int) { - doAsync { - with(activity) { - items = - getLibraries(if (rClass == null) Libs(activity) else Libs(this, Libs.toStringArray(rClass.fields))) - .map(::LibraryIItem) + with(activity) { + launch { + items = async { + getLibraries( + if (rClass == null) Libs(activity) + else Libs(activity, Libs.toStringArray(rClass.fields)) + ).map(::LibraryIItem) + }.await() if (pageStatus[position] == 1) - uiThread { addItems(activity, position) } + addItems(activity, position) } } } @@ -202,8 +206,8 @@ open class AboutPanelFaqs : AboutPanelRecycler() { override fun loadItems(activity: AboutActivityBase, position: Int) { with(activity) { - kauParseFaq(configs.faqXmlRes, configs.faqParseNewLine) { - items = it.map(::FaqIItem) + launch { + items = async { kauParseFaq(configs.faqXmlRes, configs.faqParseNewLine) }.await().map(::FaqIItem) if (pageStatus[position] == 1) addItems(activity, position) } diff --git a/buildSrc/src/main/groovy/ca/allanwang/kau/Versions.groovy b/buildSrc/src/main/groovy/ca/allanwang/kau/Versions.groovy index a85fe97..e6f3cd7 100644 --- a/buildSrc/src/main/groovy/ca/allanwang/kau/Versions.groovy +++ b/buildSrc/src/main/groovy/ca/allanwang/kau/Versions.groovy @@ -26,12 +26,12 @@ class Versions { // https://kotlinlang.org/docs/reference/using-gradle.html static def kotlin = '1.3.11' + // https://github.com/Kotlin/kotlinx.coroutines/releases + static def coroutines = '1.0.1' + // https://github.com/mikepenz/AboutLibraries/releases static def aboutLibraries = '6.2.0' - // https://github.com/Kotlin/anko/releases - static def anko = '0.10.5' - // https://github.com/wasabeef/Blurry/releases static def blurry = '2.1.1' diff --git a/core/README.md b/core/README.md index 39899dd..b9a10f5 100644 --- a/core/README.md +++ b/core/README.md @@ -131,6 +131,9 @@ These variants are weakly held in the private `KotterknifeRegistry` object, and values through the `Kotterknife.reset` method. This is typically useful for Fragments, as they do not follow the same lifecycle as Activities and Views. +Note that this is useful for views that have ids in multiple layout files or in `id.xml` files. +Kotlin has another solution, [`kotlin-android-extensions`](https://kotlinlang.org/docs/tutorials/android-plugin.html), which is more convenient. + ## Ripple Canvas Ripple canvas provides a way to create simultaneous ripples against a background color. @@ -210,7 +213,6 @@ Include your email and subject, along with other optional configurations such as ## Extension Functions > "[Extensions](https://kotlinlang.org/docs/reference/extensions.html) provide the ability to extend a class with new functionality without having to inherit from the class" -
Note that since KAU depends on [ANKO](https://github.com/Kotlin/anko), all of the extensions in its core package is also in KAU. KAU's vast collection of extensions is one of its strongest features. There are too many to explain here, but you may check out the [utils package](https://github.com/AllanWang/KAU/tree/master/core/src/main/kotlin/ca/allanwang/kau/utils) diff --git a/core/build.gradle b/core/build.gradle index a57ee6e..3ac4f36 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -11,12 +11,12 @@ dependencies { api "androidx.constraintlayout:constraintlayout:${kau.constraintLayout}" api "com.google.android.material:material:${kau.googleMaterial}" + api "org.jetbrains.kotlinx:kotlinx-coroutines-android:${kau.coroutines}" + api "com.mikepenz:iconics-core:${kau.iconics}@aar" api "com.mikepenz:google-material-typeface:${kau.iconicsGoogle}.original@aar" api "com.afollestad.material-dialogs:core:${kau.materialDialog}" - - api "org.jetbrains.anko:anko-commons:${kau.anko}" } apply from: '../artifacts.gradle' 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, 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 - * 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 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 Anko + * + * 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. @@ -36,6 +38,56 @@ infix fun Bundle.with(bundle: Bundle?): Bundle { return this } +/** + * Saves all bundle args based on their respective types. + * + * Taken courtesy of Anko + * + * 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): 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() -> b.putParcelableArray(k, v as Array) + v.isArrayOf() -> b.putCharSequenceArray(k, v as Array) + v.isArrayOf() -> b.putStringArray(k, v as Array) + 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+ */ @@ -62,7 +114,8 @@ fun Bundle.withSceneTransitionAnimation(parent: View, data: Map) = @SuppressLint("NewApi") fun Bundle.withSceneTransitionAnimation(context: Context, data: Map) { 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.withArguments(vararg params: Pair): T { +fun T.withArguments(vararg params: Pair): 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, Text and Text, - * and invoke the [callback] on the ui thread + * Parse an xml asynchronously with two tags, Text and Text. + * 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
*/ - parseNewLine: Boolean = true, - callback: (items: List) -> Unit -) { - doAsync { - val items = mutableListOf() - 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 { + val items = mutableListOf() + 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) "
" else "")) + flag = -1 } - } else if (eventType == XmlPullParser.TEXT) { - when (flag) { - 0 -> { - question = Html.fromHtml(parser.text.replace("\n", if (parseNewLine) "
" 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) "
" 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) "
" 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) diff --git a/core/src/test/kotlin/ca/allanwang/kau/kotlin/ZipTest.kt b/core/src/test/kotlin/ca/allanwang/kau/kotlin/ZipTest.kt deleted file mode 100644 index 1fbc38f..0000000 --- a/core/src/test/kotlin/ca/allanwang/kau/kotlin/ZipTest.kt +++ /dev/null @@ -1,83 +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 org.junit.Test -import java.util.Random -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 -> - 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) - } -} diff --git a/docs/Changelog.md b/docs/Changelog.md index 1e1b0d8..283759d 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -1,5 +1,11 @@ # Changelog +## v4.0.0-alpha02 +* :core: Remove anko dependency. Methods that used it now use coroutines; see the migration doc for minor changes +* :core: Add default CoroutineScope implementation to KauBaseActivity +* :core: Remove zip class. Coroutines and join can be used as an alternative +* :mediapicker: Use video preloading instead of full async loading + ## v4.0.0-alpha01 * Migrate to androidx. See migration for external dependency changes. * :core: Remove deprecation warning for Kotterknife diff --git a/docs/Migration.md b/docs/Migration.md index 8c5e016..cdaca4c 100644 --- a/docs/Migration.md +++ b/docs/Migration.md @@ -2,6 +2,19 @@ Below are some highlights on major refactoring/breaking changes +# v4.0.1-alpha02 + +* `kauParseFaq` is now synchronous. + +## Anko has been removed + +A lot of the methods are already implemented in KAU, and it was primarily imported for its `doAsync` methods. Now, they have been replaced with coroutines. +Some methods have been copied over: + +* import org.jetbrains.anko.runOnUiThread > import ca.allanwang.kau.utils.runOnUiThread +* import org.jetbrains.anko.contentView > import ca.allanwang.kau.utils.contentView +* import org.jetbrains.anko.bundleOf > import ca.allanwang.kau.utils.bundleOf + # v4.0.0-alpha01 This is the first introduction of androidx. The goal is to just do a migration with minimal changes. diff --git a/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/KPrefActivity.kt b/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/KPrefActivity.kt index 02b6e98..450bc6e 100644 --- a/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/KPrefActivity.kt +++ b/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/KPrefActivity.kt @@ -33,8 +33,8 @@ import ca.allanwang.kau.utils.statusBarColor import ca.allanwang.kau.utils.withLinearAdapter import com.mikepenz.fastadapter.commons.adapters.FastItemAdapter import kotlinx.android.synthetic.main.kau_pref_activity.* -import org.jetbrains.anko.doAsync -import org.jetbrains.anko.uiThread +import kotlinx.coroutines.async +import kotlinx.coroutines.launch import java.util.Stack abstract class KPrefActivity : KauBaseActivity(), KPrefActivityContract { @@ -104,19 +104,24 @@ abstract class KPrefActivity : KauBaseActivity(), KPrefActivityContract { builder: KPrefAdapterBuilder.() -> Unit, first: Boolean ) { - doAsync { - val items = KPrefAdapterBuilder(globalOptions) - builder(items) - kprefStack.push(toolbarTitleRes to items.list) + launch { + val items = async { + val items = KPrefAdapterBuilder(globalOptions) + builder(items) + kprefStack.push(toolbarTitleRes to items.list) + items.list + }.await() kau_recycler.itemAnimator = if (animate && !first) recyclerAnimatorNext else null - uiThread { - adapter.clear() - adapter.add(items.list.filter { it.core.visible() }) - toolbar.setTitle(toolbarTitleRes) - } + show(toolbarTitleRes, items) } } + private fun show(@StringRes toolbarTitleRes: Int, items: List) { + toolbar.setTitle(toolbarTitleRes) + adapter.clear() + adapter.add(items.filter { it.core.visible() }) + } + /** * Pops the stack and loads the next kpref list * Indices are not checked so ensure that this is possible first @@ -125,9 +130,7 @@ abstract class KPrefActivity : KauBaseActivity(), KPrefActivityContract { kprefStack.pop() val (title, list) = kprefStack.peek() kau_recycler.itemAnimator = if (animate) recyclerAnimatorPrev else null - adapter.clear() - adapter.add(list.filter { it.core.visible() }) - toolbar.setTitle(title) + show(title, list) } /** diff --git a/mediapicker/src/main/kotlin/ca/allanwang/kau/mediapicker/MediaPickerCore.kt b/mediapicker/src/main/kotlin/ca/allanwang/kau/mediapicker/MediaPickerCore.kt index abdc266..7004967 100644 --- a/mediapicker/src/main/kotlin/ca/allanwang/kau/mediapicker/MediaPickerCore.kt +++ b/mediapicker/src/main/kotlin/ca/allanwang/kau/mediapicker/MediaPickerCore.kt @@ -49,10 +49,8 @@ import com.mikepenz.fastadapter.adapters.ItemAdapter import com.mikepenz.google_material_typeface_library.GoogleMaterial import com.mikepenz.iconics.IconicsDrawable import com.mikepenz.iconics.typeface.IIcon -import org.jetbrains.anko.doAsync +import kotlinx.coroutines.CancellationException import java.io.File -import java.util.concurrent.ExecutionException -import java.util.concurrent.Future /** * Created by Allan Wang on 2017-07-23. @@ -121,7 +119,6 @@ abstract class MediaPickerCore>( lateinit var glide: RequestManager private var hasPreloaded = false - private var prefetcher: Future<*>? = null val adapter = ItemAdapter() @@ -137,7 +134,7 @@ abstract class MediaPickerCore>( fun initializeRecycler(recycler: RecyclerView) { val adapterHeader = ItemAdapter() - val fulladapter = fastAdapter(adapterHeader, adapter) + val fulladapter = fastAdapter>(adapterHeader, adapter) adapterHeader.add(mediaActions.map { MediaActionItem(it, mediaType) }) recycler.apply { val manager = object : GridLayoutManager(context, computeColumnCount(context)) { @@ -146,7 +143,6 @@ abstract class MediaPickerCore>( } } setItemViewCacheSize(CACHE_SIZE) - isDrawingCacheEnabled = true layoutManager = manager adapter = fulladapter setHasFixedSize(true) @@ -195,18 +191,14 @@ abstract class MediaPickerCore>( addItems(models.map { converter(it) }) if (!hasPreloaded && mediaType == MediaType.VIDEO) { hasPreloaded = true - prefetcher = doAsync { - models.subList(0, Math.min(models.size, 50)).map { it.data }.forEach { - val target = glide.load(it) - .applyMediaOptions(this@MediaPickerCore) - .submit() - try { - target.get() - } catch (ignored: InterruptedException) { - } catch (ignored: ExecutionException) { - } finally { - glide.clear(target) - } + val preloads = models.subList(0, Math.min(models.size, 50)).map { + glide.load(it.data) + .applyMediaOptions(this@MediaPickerCore) + .preload() + } + job.invokeOnCompletion { + if (it is CancellationException) { + preloads.forEach(glide::clear) } } } @@ -242,11 +234,6 @@ abstract class MediaPickerCore>( open fun onStatusChange(loaded: Boolean) {} - override fun onDestroy() { - prefetcher?.cancel(true) - super.onDestroy() - } - /** * Method used to retrieve uri data for API 19+ * See diff --git a/sample/src/main/res/xml/kau_changelog.xml b/sample/src/main/res/xml/kau_changelog.xml index 6c0fea1..3c49078 100644 --- a/sample/src/main/res/xml/kau_changelog.xml +++ b/sample/src/main/res/xml/kau_changelog.xml @@ -6,6 +6,16 @@ --> + + + + + + + + + + diff --git a/searchview/src/main/kotlin/ca/allanwang/kau/searchview/SearchView.kt b/searchview/src/main/kotlin/ca/allanwang/kau/searchview/SearchView.kt index 11880cd..169e914 100644 --- a/searchview/src/main/kotlin/ca/allanwang/kau/searchview/SearchView.kt +++ b/searchview/src/main/kotlin/ca/allanwang/kau/searchview/SearchView.kt @@ -53,6 +53,7 @@ import ca.allanwang.kau.utils.hideKeyboard import ca.allanwang.kau.utils.invisibleIf import ca.allanwang.kau.utils.isVisible import ca.allanwang.kau.utils.parentViewGroup +import ca.allanwang.kau.utils.runOnUiThread import ca.allanwang.kau.utils.setIcon import ca.allanwang.kau.utils.setMarginTop import ca.allanwang.kau.utils.showKeyboard @@ -65,7 +66,6 @@ import com.mikepenz.fastadapter.commons.adapters.FastItemAdapter import com.mikepenz.google_material_typeface_library.GoogleMaterial import com.mikepenz.iconics.typeface.IIcon import kotlinx.android.synthetic.main.kau_search_view.view.* -import org.jetbrains.anko.runOnUiThread /** * Created by Allan Wang on 2017-06-23. -- cgit v1.2.3 From ab349293e52d77f6fc4b44446e19dc80aa8e789f Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Sun, 30 Dec 2018 17:07:57 -0500 Subject: Emulator tests (#183) * Add connected check * Delete flyweight * Add skin tag * Try emulator api 22 * Try emulator api 19 * Remove emulator tests for now. Too flaky --- .travis.yml | 15 +++--- .../kotlin/ca/allanwang/kau/kotlin/FlyWeight.kt | 54 ---------------------- docs/Changelog.md | 1 + sample/src/main/res/xml/kau_changelog.xml | 2 +- 4 files changed, 11 insertions(+), 61 deletions(-) delete mode 100644 core/src/main/kotlin/ca/allanwang/kau/kotlin/FlyWeight.kt (limited to 'sample/src/main/res/xml/kau_changelog.xml') diff --git a/.travis.yml b/.travis.yml index 721eb72..ee679b3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,20 +4,20 @@ jdk: env: global: - ANDROID_API=28 - - EMULATOR_API=24 + - EMULATOR_API=19 - ANDROID_BUILD_TOOLS=28.0.3 android: components: - tools -# - android-$EMULATOR_API - platform-tools - tools - build-tools-$ANDROID_BUILD_TOOLS - android-$ANDROID_API - - extra-android-support +# - android-$EMULATOR_API - extra-android-m2repository - extra-google-m2repository -# - sys-img-armeabi-v7a-android-$EMULATOR_API +# - sys-img-armeabi-v7a-addon-google_apis-google-$ANDROID_API_LEVEL +# - sys-img-armeabi-v7a-addon-google_apis-google-$EMULATOR_API_LEVEL licenses: - ".+" #before_script: @@ -25,8 +25,11 @@ android: #- echo "y" | android update sdk -a --no-ui --filter sys-img-armeabi-v7a-android-$EMULATOR_API #- android list targets | grep -E '^id:' | awk -F '"' '{$1=""; print $2}' # list all targets #- echo no | android create avd --force -n test -t android-$EMULATOR_API --abi armeabi-v7a -#- emulator -avd test -no-skin -no-window & +#- emulator -avd test -skin -no-audio -no-window & #- android-wait-for-emulator +#- adb shell settings put global window_animation_scale 0 & +#- adb shell settings put global transition_animation_scale 0 & +#- adb shell settings put global animator_duration_scale 0 & #- adb shell input keyevent 82 & script: - chmod +x gradlew @@ -50,7 +53,7 @@ cache: - $HOME/.gradle/wrapper/ - $HOME/.android/build-cache before_install: -- yes | sdkmanager "platforms;android-28" +- yes | sdkmanager "platforms;android-$ANDROID_API" - openssl aes-256-cbc -K $encrypted_12e8842891a3_key -iv $encrypted_12e8842891a3_iv -in files/kau.tar.enc -out kau.tar -d - tar xvf kau.tar \ No newline at end of file diff --git a/core/src/main/kotlin/ca/allanwang/kau/kotlin/FlyWeight.kt b/core/src/main/kotlin/ca/allanwang/kau/kotlin/FlyWeight.kt deleted file mode 100644 index 165295a..0000000 --- a/core/src/main/kotlin/ca/allanwang/kau/kotlin/FlyWeight.kt +++ /dev/null @@ -1,54 +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 - -/** - * Created by Allan Wang on 26/12/17. - * - * Kotlin implementation of a flyweight design pattern - * Values inside the map will be returned directly - * Otherwise, it will be created with the supplier, saved, and returned - * In the event a default is provided, the default takes precedence - */ -fun flyweight(creator: (K) -> V) = FlyWeight(creator) - -class FlyWeight(private val creator: (key: K) -> V) : Map { - - private val map = mutableMapOf() - - override val entries: Set> - get() = map.entries - override val keys: Set - get() = map.keys - override val size: Int - get() = map.size - override val values: Collection - get() = map.values - - override fun containsKey(key: K) = map.containsKey(key) - - override fun containsValue(value: V) = map.containsValue(value) - - override fun getOrDefault(key: K, defaultValue: V) = map.getOrDefault(key, defaultValue) - - override fun isEmpty() = map.isEmpty() - - override fun get(key: K): V { - if (!map.containsKey(key)) - map.put(key, creator(key)) - return map[key]!! - } -} diff --git a/docs/Changelog.md b/docs/Changelog.md index 283759d..e6d51d2 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -4,6 +4,7 @@ * :core: Remove anko dependency. Methods that used it now use coroutines; see the migration doc for minor changes * :core: Add default CoroutineScope implementation to KauBaseActivity * :core: Remove zip class. Coroutines and join can be used as an alternative +* :core: Delete flyweight implementation. Kotlin already has getOrPut * :mediapicker: Use video preloading instead of full async loading ## v4.0.0-alpha01 diff --git a/sample/src/main/res/xml/kau_changelog.xml b/sample/src/main/res/xml/kau_changelog.xml index 3c49078..0283d7f 100644 --- a/sample/src/main/res/xml/kau_changelog.xml +++ b/sample/src/main/res/xml/kau_changelog.xml @@ -10,11 +10,11 @@ + - -- cgit v1.2.3 From d61ff7cb4f43d71d2170cdd25ceab2e3edcb81fc Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Fri, 4 Jan 2019 23:03:50 -0500 Subject: Coroutine tests (#185) * Add some coroutine tests for implicit cancellation * Create util test and new helper methods * Remove coroutinescope extension from withcontext * Update dependencies --- .../main/groovy/ca/allanwang/kau/Versions.groovy | 8 +-- .../kotlin/ca/allanwang/kau/utils/ContextUtils.kt | 27 ---------- .../ca/allanwang/kau/utils/CoroutineUtils.kt | 60 ++++++++++++++++++++++ .../ca/allanwang/kau/kotlin/CoroutineTest.kt | 58 +++++++++++++++++++++ docs/Changelog.md | 1 + sample/src/main/res/xml/kau_changelog.xml | 2 +- 6 files changed, 125 insertions(+), 31 deletions(-) create mode 100644 core/src/main/kotlin/ca/allanwang/kau/utils/CoroutineUtils.kt create mode 100644 core/src/test/kotlin/ca/allanwang/kau/kotlin/CoroutineTest.kt (limited to 'sample/src/main/res/xml/kau_changelog.xml') diff --git a/buildSrc/src/main/groovy/ca/allanwang/kau/Versions.groovy b/buildSrc/src/main/groovy/ca/allanwang/kau/Versions.groovy index e6f3cd7..4afec82 100644 --- a/buildSrc/src/main/groovy/ca/allanwang/kau/Versions.groovy +++ b/buildSrc/src/main/groovy/ca/allanwang/kau/Versions.groovy @@ -27,7 +27,7 @@ class Versions { static def kotlin = '1.3.11' // https://github.com/Kotlin/kotlinx.coroutines/releases - static def coroutines = '1.0.1' + static def coroutines = '1.1.0' // https://github.com/mikepenz/AboutLibraries/releases static def aboutLibraries = '6.2.0' @@ -72,6 +72,8 @@ class Versions { static def playPublishPlugin = '1.2.2' // https://github.com/KeepSafe/dexcount-gradle-plugin/releases - static def dexCountPlugin = '0.8.3' - static def gitVersionPlugin = '0.4.4' + static def dexCountPlugin = '0.8.5' + + // https://github.com/gladed/gradle-android-git-version/releases + static def gitVersionPlugin = '0.4.7' } \ 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 index 09ad4ea..fc8049d 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/utils/ContextUtils.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/utils/ContextUtils.kt @@ -26,7 +26,6 @@ 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 @@ -47,36 +46,10 @@ 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.android.asCoroutineDispatcher -import kotlin.coroutines.CoroutineContext /** * Created by Allan Wang on 2017-06-03. */ -object ContextHelper : CoroutineScope { - - val looper = Looper.getMainLooper() - - val handler = Handler(looper) - - /** - * Creating dispatcher from main handler to avoid IO - * See https://github.com/Kotlin/kotlinx.coroutines/issues/878 - */ - val dispatcher = handler.asCoroutineDispatcher("kau-main") - - override val coroutineContext: CoroutineContext get() = dispatcher -} - -/** - * 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 (ContextHelper.looper === Looper.myLooper()) f() else ContextHelper.handler.post { f() } } diff --git a/core/src/main/kotlin/ca/allanwang/kau/utils/CoroutineUtils.kt b/core/src/main/kotlin/ca/allanwang/kau/utils/CoroutineUtils.kt new file mode 100644 index 0000000..032c407 --- /dev/null +++ b/core/src/main/kotlin/ca/allanwang/kau/utils/CoroutineUtils.kt @@ -0,0 +1,60 @@ +package ca.allanwang.kau.utils + +import android.content.Context +import android.os.Handler +import android.os.Looper +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.CoroutineStart +import kotlinx.coroutines.android.asCoroutineDispatcher +import kotlinx.coroutines.async +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext + +object ContextHelper : CoroutineScope { + + val looper = Looper.getMainLooper() + + val handler = Handler(looper) + + /** + * Creating dispatcher from main handler to avoid IO + * See https://github.com/Kotlin/kotlinx.coroutines/issues/878 + */ + val dispatcher = handler.asCoroutineDispatcher("kau-main") + + override val coroutineContext: CoroutineContext get() = dispatcher +} + +/** + * 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 + +/** + * Calls [launch] with an explicit dispatcher for Android's main thread + */ +fun CoroutineScope.launchMain( + context: CoroutineContext = EmptyCoroutineContext, + start: CoroutineStart = CoroutineStart.DEFAULT, + block: suspend CoroutineScope.() -> Unit +) = launch(ContextHelper.dispatcher + context, start, block) + +/** + * Calls [async] with an explicit dispatcher for Android's main thread + */ +fun CoroutineScope.asyncMain( + context: CoroutineContext = EmptyCoroutineContext, + start: CoroutineStart = CoroutineStart.DEFAULT, + block: suspend CoroutineScope.() -> Unit +) = async(ContextHelper.dispatcher + context, start, block) + +/** + * Calls [withContext] with an explicit dispatcher for Android's main thread + */ +suspend fun withMainContext(block: suspend CoroutineScope.() -> T) = + withContext(ContextHelper.dispatcher, block) diff --git a/core/src/test/kotlin/ca/allanwang/kau/kotlin/CoroutineTest.kt b/core/src/test/kotlin/ca/allanwang/kau/kotlin/CoroutineTest.kt new file mode 100644 index 0000000..91d0174 --- /dev/null +++ b/core/src/test/kotlin/ca/allanwang/kau/kotlin/CoroutineTest.kt @@ -0,0 +1,58 @@ +package ca.allanwang.kau.kotlin + +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext +import org.junit.Test +import kotlin.test.assertEquals +import kotlin.test.fail + +/** + * Tests geared towards coroutines + */ +class CoroutineTest { + + /** + * If a job is cancelled, then a switch to a new context will not run + */ + @Test + fun implicitCancellationBefore() { + val job = Job() + var id = 0 + try { + runBlocking(job) { + id++ + job.cancel() + withContext(Dispatchers.IO) { + fail("Context switch should not be reached") + } + } + } catch (ignore: CancellationException) { + } finally { + assertEquals(1, id, "Launcher never executed") + } + } + + /** + * If a job is cancelled, then a switch from a new context will not run + */ + @Test + fun implicitCancellationAfter() { + val job = Job() + var id = 0 + try { + runBlocking(job) { + withContext(Dispatchers.IO) { + id++ + job.cancel() + } + fail("Post context switch should not be reached") + } + } catch (ignore: CancellationException) { + } finally { + assertEquals(1, id, "Context switch never executed") + } + } +} \ No newline at end of file diff --git a/docs/Changelog.md b/docs/Changelog.md index e6d51d2..7291753 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -5,6 +5,7 @@ * :core: Add default CoroutineScope implementation to KauBaseActivity * :core: Remove zip class. Coroutines and join can be used as an alternative * :core: Delete flyweight implementation. Kotlin already has getOrPut +* :core: Introduce ContextHelper, where you can get the default looper, handler, and dispatcher for Android * :mediapicker: Use video preloading instead of full async loading ## v4.0.0-alpha01 diff --git a/sample/src/main/res/xml/kau_changelog.xml b/sample/src/main/res/xml/kau_changelog.xml index 0283d7f..570a0b9 100644 --- a/sample/src/main/res/xml/kau_changelog.xml +++ b/sample/src/main/res/xml/kau_changelog.xml @@ -11,10 +11,10 @@ + - -- cgit v1.2.3 From 4c4996e0ead6c415e96dd19f1390140e59bb98a4 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Sat, 5 Jan 2019 22:46:03 -0500 Subject: Fix tests and prepare next release --- core/src/main/kotlin/ca/allanwang/kau/email/EmailBuilder.kt | 1 + docs/Changelog.md | 1 + .../src/main/kotlin/ca/allanwang/kau/mediapicker/MediaPickerCore.kt | 2 +- sample/src/main/res/xml/kau_changelog.xml | 2 +- 4 files changed, 4 insertions(+), 2 deletions(-) (limited to 'sample/src/main/res/xml/kau_changelog.xml') diff --git a/core/src/main/kotlin/ca/allanwang/kau/email/EmailBuilder.kt b/core/src/main/kotlin/ca/allanwang/kau/email/EmailBuilder.kt index e6ad97a..1f959ab 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/email/EmailBuilder.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/email/EmailBuilder.kt @@ -90,6 +90,7 @@ class EmailBuilder(val email: String, val subject: String) { val versionCode = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { appInfo.longVersionCode.toString() } else { + @Suppress("DEPRECATION") appInfo.versionCode.toString() } emailBuilder.append("\nApp: ").append(context.packageName) diff --git a/docs/Changelog.md b/docs/Changelog.md index 7291753..021a0f1 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -1,6 +1,7 @@ # Changelog ## v4.0.0-alpha02 +* Update translations * :core: Remove anko dependency. Methods that used it now use coroutines; see the migration doc for minor changes * :core: Add default CoroutineScope implementation to KauBaseActivity * :core: Remove zip class. Coroutines and join can be used as an alternative diff --git a/mediapicker/src/main/kotlin/ca/allanwang/kau/mediapicker/MediaPickerCore.kt b/mediapicker/src/main/kotlin/ca/allanwang/kau/mediapicker/MediaPickerCore.kt index 7004967..e6556ab 100644 --- a/mediapicker/src/main/kotlin/ca/allanwang/kau/mediapicker/MediaPickerCore.kt +++ b/mediapicker/src/main/kotlin/ca/allanwang/kau/mediapicker/MediaPickerCore.kt @@ -166,7 +166,7 @@ abstract class MediaPickerCore>( open fun loadItems() { kauRequestPermissions(Manifest.permission.READ_EXTERNAL_STORAGE) { granted, _ -> if (granted) { - supportLoaderManager.initLoader(LOADER_ID, null, this) + LoaderManager.getInstance(this).initLoader(LOADER_ID, null, this) onStatusChange(true) } else { toast(R.string.kau_permission_denied) diff --git a/sample/src/main/res/xml/kau_changelog.xml b/sample/src/main/res/xml/kau_changelog.xml index 570a0b9..be1c023 100644 --- a/sample/src/main/res/xml/kau_changelog.xml +++ b/sample/src/main/res/xml/kau_changelog.xml @@ -7,6 +7,7 @@ --> + @@ -14,7 +15,6 @@ - -- cgit v1.2.3 From 2d826d960469668b13e0de7642b4e9067d61ef4b Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Thu, 4 Apr 2019 15:54:54 -0400 Subject: Apply spotless and update changelog --- .idea/misc.xml | 2 +- sample/src/main/kotlin/ca/allanwang/kau/sample/MainActivity.kt | 2 +- sample/src/main/res/xml/kau_changelog.xml | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) (limited to 'sample/src/main/res/xml/kau_changelog.xml') diff --git a/.idea/misc.xml b/.idea/misc.xml index cc04cd3..51fa3e5 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -35,7 +35,7 @@ - + diff --git a/sample/src/main/kotlin/ca/allanwang/kau/sample/MainActivity.kt b/sample/src/main/kotlin/ca/allanwang/kau/sample/MainActivity.kt index fccba05..d4a30bb 100644 --- a/sample/src/main/kotlin/ca/allanwang/kau/sample/MainActivity.kt +++ b/sample/src/main/kotlin/ca/allanwang/kau/sample/MainActivity.kt @@ -318,4 +318,4 @@ class MainActivity : KPrefActivity() { REQUEST_MEDIA -> toast("${kauOnMediaPickerResult(resultCode, data).size} items selected") } } -} \ No newline at end of file +} diff --git a/sample/src/main/res/xml/kau_changelog.xml b/sample/src/main/res/xml/kau_changelog.xml index be1c023..d78fcea 100644 --- a/sample/src/main/res/xml/kau_changelog.xml +++ b/sample/src/main/res/xml/kau_changelog.xml @@ -6,6 +6,9 @@ --> + + + @@ -14,7 +17,6 @@ - -- cgit v1.2.3 From a0b16ab0d8886934f7d29403420ce30e791e7119 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Tue, 21 May 2019 12:08:23 -0700 Subject: Deprecate network utils --- core/src/main/kotlin/ca/allanwang/kau/utils/NetworkUtils.kt | 3 +++ sample/src/main/res/xml/kau_changelog.xml | 4 ++++ 2 files changed, 7 insertions(+) (limited to 'sample/src/main/res/xml/kau_changelog.xml') diff --git a/core/src/main/kotlin/ca/allanwang/kau/utils/NetworkUtils.kt b/core/src/main/kotlin/ca/allanwang/kau/utils/NetworkUtils.kt index 260e90f..32cf084 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/utils/NetworkUtils.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/utils/NetworkUtils.kt @@ -22,6 +22,7 @@ import android.net.ConnectivityManager /** * Created by Allan Wang on 2017-07-07. */ +@Deprecated("Applications should make use of network callbacks instead of individual queries") inline val Context.isNetworkAvailable: Boolean @SuppressLint("MissingPermission") get() { @@ -30,6 +31,7 @@ inline val Context.isNetworkAvailable: Boolean return activeNetworkInfo?.isConnectedOrConnecting ?: false } +@Deprecated("Applications should make use of network callbacks instead of individual queries") inline val Context.isWifiConnected: Boolean @SuppressLint("MissingPermission") get() { @@ -38,6 +40,7 @@ inline val Context.isWifiConnected: Boolean return (activeNetworkInfo?.type ?: -1) == ConnectivityManager.TYPE_WIFI } +@Deprecated("Applications should make use of network callbacks instead of individual queries") inline val Context.isMobileDataConnected: Boolean @SuppressLint("MissingPermission") get() { diff --git a/sample/src/main/res/xml/kau_changelog.xml b/sample/src/main/res/xml/kau_changelog.xml index d78fcea..1edb48e 100644 --- a/sample/src/main/res/xml/kau_changelog.xml +++ b/sample/src/main/res/xml/kau_changelog.xml @@ -6,6 +6,10 @@ --> + + + + -- cgit v1.2.3 From ff20b96232a2e19481ca33df8ce76d4d44a847e3 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Tue, 21 May 2019 20:00:51 -0700 Subject: Update changelog --- docs/Changelog.md | 1 + sample/src/main/res/xml/kau_changelog.xml | 2 ++ 2 files changed, 3 insertions(+) (limited to 'sample/src/main/res/xml/kau_changelog.xml') diff --git a/docs/Changelog.md b/docs/Changelog.md index 9d96135..0100e8e 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -2,6 +2,7 @@ ## v4.1.0 * :core: Deprecate NetworkUtils, as the underlying functions are deprecated +* :kpref-activity: Getter and setter now have action context, with the option to reload self ## v4.0.0 * Update translations diff --git a/sample/src/main/res/xml/kau_changelog.xml b/sample/src/main/res/xml/kau_changelog.xml index 1edb48e..dbaa1ef 100644 --- a/sample/src/main/res/xml/kau_changelog.xml +++ b/sample/src/main/res/xml/kau_changelog.xml @@ -8,6 +8,8 @@ + + -- cgit v1.2.3 From efae24a43ba6f861442a485ddff49afd65559eb4 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Wed, 22 May 2019 18:04:26 -0700 Subject: Update changelog --- docs/Changelog.md | 1 + sample/src/main/res/xml/kau_changelog.xml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'sample/src/main/res/xml/kau_changelog.xml') diff --git a/docs/Changelog.md b/docs/Changelog.md index 0100e8e..a2c38e1 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -2,6 +2,7 @@ ## v4.1.0 * :core: Deprecate NetworkUtils, as the underlying functions are deprecated +* :core: Permission manager no longer synchronized, as all actions should occur in the main thread * :kpref-activity: Getter and setter now have action context, with the option to reload self ## v4.0.0 diff --git a/sample/src/main/res/xml/kau_changelog.xml b/sample/src/main/res/xml/kau_changelog.xml index dbaa1ef..0b13dd4 100644 --- a/sample/src/main/res/xml/kau_changelog.xml +++ b/sample/src/main/res/xml/kau_changelog.xml @@ -8,9 +8,9 @@ + - -- cgit v1.2.3 From 879ac366074697dd0a7fbb2c3d99a48d7aeeb22d Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Fri, 7 Jun 2019 12:30:22 -0400 Subject: Update changelog and migration --- docs/Migration.md | 6 ++++++ sample/src/main/res/xml/kau_changelog.xml | 8 ++++++++ 2 files changed, 14 insertions(+) (limited to 'sample/src/main/res/xml/kau_changelog.xml') diff --git a/docs/Migration.md b/docs/Migration.md index cdaca4c..c0816d4 100644 --- a/docs/Migration.md +++ b/docs/Migration.md @@ -2,6 +2,12 @@ Below are some highlights on major refactoring/breaking changes +# v5.0.0 + +* Material Dialog is now 3.x. This leads to a whole new API, but fortunately it is based around kotlin. Please refer to [MD's documents](https://github.com/afollestad/material-dialogs/tree/3.0.0-rc2/documentation) for the new methods. + * Alongside such changes, `:colorpicker` is no longer as necessary. It exists mainly to provide an internal interface for other submodules. + + # v4.0.1-alpha02 * `kauParseFaq` is now synchronous. diff --git a/sample/src/main/res/xml/kau_changelog.xml b/sample/src/main/res/xml/kau_changelog.xml index 0b13dd4..53265d0 100644 --- a/sample/src/main/res/xml/kau_changelog.xml +++ b/sample/src/main/res/xml/kau_changelog.xml @@ -6,6 +6,14 @@ --> + + + + + + + + -- cgit v1.2.3