From 82f79a7d368caad4068d97051ad8deddd05b4f7e Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Thu, 3 Jan 2019 23:10:53 -0500 Subject: Add asset preloader (#1311) --- .../kotlin/com/pitchedapps/frost/StartActivity.kt | 2 + .../pitchedapps/frost/activities/IntroActivity.kt | 10 ++++- .../frost/activities/SettingsActivity.kt | 8 +++- .../com/pitchedapps/frost/injectors/CssAssets.kt | 33 ++++++++++++++--- .../com/pitchedapps/frost/injectors/CssHider.kt | 5 +-- .../com/pitchedapps/frost/injectors/JsActions.kt | 4 +- .../com/pitchedapps/frost/injectors/JsAssets.kt | 19 ++++++++-- .../com/pitchedapps/frost/injectors/JsInjector.kt | 43 ++++------------------ .../com/pitchedapps/frost/settings/Appearance.kt | 2 +- .../kotlin/com/pitchedapps/frost/utils/Prefs.kt | 2 +- .../kotlin/com/pitchedapps/frost/utils/Utils.kt | 8 ++++ 11 files changed, 80 insertions(+), 56 deletions(-) (limited to 'app/src') diff --git a/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt index 3fafa2a6..37383182 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt @@ -37,6 +37,7 @@ import com.pitchedapps.frost.utils.EXTRA_COOKIES import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.Prefs import com.pitchedapps.frost.utils.launchNewTask +import com.pitchedapps.frost.utils.loadAssets import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -63,6 +64,7 @@ class StartActivity : KauBaseActivity() { }) L.i { "Cookies loaded at time ${System.currentTimeMillis()}" } L._d { "Cookies: ${cookies.joinToString("\t")}" } + loadAssets() if (cookies.isNotEmpty()) { if (Prefs.userId != -1L) startActivity(intentBuilder = { diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/IntroActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/IntroActivity.kt index 4aa0966c..04f67276 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/IntroActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/IntroActivity.kt @@ -53,6 +53,9 @@ import com.pitchedapps.frost.intro.IntroTabTouchFragment import com.pitchedapps.frost.utils.Prefs import com.pitchedapps.frost.utils.cookies import com.pitchedapps.frost.utils.launchNewTask +import com.pitchedapps.frost.utils.loadAssets +import kotlinx.coroutines.NonCancellable +import kotlinx.coroutines.launch /** * Created by Allan Wang on 2017-07-25. @@ -166,8 +169,11 @@ class IntroActivity : KauBaseActivity(), ViewPager.PageTransformer, ViewPager.On } override fun finish() { - launchNewTask(cookies(), false) - super.finish() + launch(NonCancellable) { + loadAssets() + launchNewTask(cookies(), false) + super.finish() + } } override fun onBackPressed() { diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/SettingsActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/SettingsActivity.kt index 37047448..c3089c7a 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/SettingsActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/SettingsActivity.kt @@ -53,8 +53,11 @@ import com.pitchedapps.frost.utils.cookies import com.pitchedapps.frost.utils.frostChangelog import com.pitchedapps.frost.utils.frostNavigationBar import com.pitchedapps.frost.utils.launchNewTask +import com.pitchedapps.frost.utils.loadAssets import com.pitchedapps.frost.utils.materialDialogThemed import com.pitchedapps.frost.utils.setFrostTheme +import kotlinx.coroutines.NonCancellable +import kotlinx.coroutines.launch /** * Created by Allan Wang on 2017-06-06. @@ -206,7 +209,10 @@ class SettingsActivity : KPrefActivity() { override fun onBackPressed() { if (!super.backPress()) { setResult(resultFlag) - finishSlideOut() + launch(NonCancellable) { + loadAssets() + finishSlideOut() + } } } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/injectors/CssAssets.kt b/app/src/main/kotlin/com/pitchedapps/frost/injectors/CssAssets.kt index de19f99c..77cb4de1 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/injectors/CssAssets.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/injectors/CssAssets.kt @@ -16,6 +16,7 @@ */ package com.pitchedapps.frost.injectors +import android.content.Context import android.graphics.Color import android.webkit.WebView import ca.allanwang.kau.kotlin.lazyContext @@ -27,6 +28,8 @@ import ca.allanwang.kau.utils.use import ca.allanwang.kau.utils.withAlpha import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.Prefs +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import java.io.BufferedReader import java.io.FileNotFoundException import java.util.Locale @@ -36,12 +39,16 @@ import java.util.Locale * Mapping of the available assets * The enum name must match the css file name */ -enum class CssAssets(val folder: String = "themes") : InjectorContract { +enum class CssAssets(val folder: String = THEME_FOLDER) : InjectorContract { MATERIAL_LIGHT, MATERIAL_DARK, MATERIAL_AMOLED, MATERIAL_GLASS, CUSTOM, ROUND_ICONS("components") ; - var file = "${name.toLowerCase(Locale.CANADA)}.css" - var injector = lazyContext { + private val file = "${name.toLowerCase(Locale.CANADA)}.css" + + /** + * Note that while this can be loaded from any thread, it is typically done through [load] + */ + private val injector = lazyContext { try { var content = it.assets.open("css/$folder/$file").bufferedReader().use(BufferedReader::readText) if (this == CUSTOM) { @@ -72,11 +79,25 @@ enum class CssAssets(val folder: String = "themes") : InjectorContract { } } - override fun inject(webView: WebView, callback: (() -> Unit)?) { - injector(webView.context).inject(webView, callback) - } + override fun inject(webView: WebView) = + injector(webView.context).inject(webView) fun reset() { injector.invalidate() } + + companion object { + // Ensures that all non themes and the selected theme are loaded + suspend fun load(context: Context) { + withContext(Dispatchers.IO) { + val currentTheme = Prefs.t.injector as? CssAssets + val (themes, others) = CssAssets.values().partition { it.folder == THEME_FOLDER } + themes.filter { it != currentTheme }.forEach { it.reset() } + currentTheme?.injector?.invoke(context) + others.forEach { it.injector.invoke(context) } + } + } + } } + +private const val THEME_FOLDER = "themes" diff --git a/app/src/main/kotlin/com/pitchedapps/frost/injectors/CssHider.kt b/app/src/main/kotlin/com/pitchedapps/frost/injectors/CssHider.kt index 7da6295f..4fd46ae4 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/injectors/CssHider.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/injectors/CssHider.kt @@ -45,7 +45,6 @@ enum class CssHider(vararg val items: String) : InjectorContract { .single(name).build() } - override fun inject(webView: WebView, callback: (() -> Unit)?) { - injector.inject(webView, callback) - } + override fun inject(webView: WebView) = + injector.inject(webView) } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsActions.kt b/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsActions.kt index e64d4faa..44fd01da 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsActions.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsActions.kt @@ -42,8 +42,8 @@ enum class JsActions(body: String) : InjectorContract { val function = "(function(){$body})();" - override fun inject(webView: WebView, callback: (() -> Unit)?) = - JsInjector(function).inject(webView, callback) + override fun inject(webView: WebView) = + JsInjector(function).inject(webView) } @Suppress("NOTHING_TO_INLINE") diff --git a/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsAssets.kt b/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsAssets.kt index 0dccc751..4b1bde43 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsAssets.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsAssets.kt @@ -16,9 +16,12 @@ */ package com.pitchedapps.frost.injectors +import android.content.Context import android.webkit.WebView import ca.allanwang.kau.kotlin.lazyContext import com.pitchedapps.frost.utils.L +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import java.io.BufferedReader import java.io.FileNotFoundException import java.util.Locale @@ -33,8 +36,8 @@ enum class JsAssets : InjectorContract { DOCUMENT_WATCHER ; - var file = "${name.toLowerCase(Locale.CANADA)}.js" - var injector = lazyContext { + private val file = "${name.toLowerCase(Locale.CANADA)}.js" + private val injector = lazyContext { try { val content = it.assets.open("js/$file").bufferedReader().use(BufferedReader::readText) JsBuilder().js(content).single(name).build() @@ -44,7 +47,15 @@ enum class JsAssets : InjectorContract { } } - override fun inject(webView: WebView, callback: (() -> Unit)?) { - injector(webView.context).inject(webView, callback) + override fun inject(webView: WebView) = + injector(webView.context).inject(webView) + + companion object { + // Ensures that all non themes and the selected theme are loaded + suspend fun load(context: Context) { + withContext(Dispatchers.IO) { + JsAssets.values().forEach { it.injector.invoke(context) } + } + } } } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsInjector.kt b/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsInjector.kt index 8ae3a2f4..00c7bcfc 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsInjector.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsInjector.kt @@ -17,12 +17,7 @@ package com.pitchedapps.frost.injectors import android.webkit.WebView -import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.web.FrostWebViewClient -import io.reactivex.Single -import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.disposables.Disposable -import io.reactivex.subjects.SingleSubject import org.apache.commons.text.StringEscapeUtils import java.util.Locale @@ -81,8 +76,7 @@ class JsBuilder { * Contract for all injectors to allow it to interact properly with a webview */ interface InjectorContract { - fun inject(webView: WebView) = inject(webView, null) - fun inject(webView: WebView, callback: (() -> Unit)?) + fun inject(webView: WebView) /** * Toggle the injector (usually through Prefs * If false, will fallback to an empty action @@ -93,41 +87,18 @@ interface InjectorContract { /** * Helper method to inject multiple functions simultaneously with a single callback */ -fun WebView.jsInject(vararg injectors: InjectorContract, callback: ((Int) -> Unit)? = null): Disposable? { - val validInjectors = injectors.filter { it != JsActions.EMPTY } - if (validInjectors.isEmpty()) { - callback?.invoke(0) - return null +fun WebView.jsInject(vararg injectors: InjectorContract) { + injectors.filter { it != JsActions.EMPTY }.forEach { + it.inject(this) } - L.d { "Injecting ${validInjectors.size} items" } - if (callback == null) { - validInjectors.forEach { it.inject(this) } - return null - } - val observables = Array(validInjectors.size) { SingleSubject.create() } - val disposable = Single.zip(observables.asList()) { it.size } - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { res, _ -> - callback(res) - } - (0 until validInjectors.size).forEach { i -> - validInjectors[i].inject(this) { - observables[i].onSuccess(Unit) - } - } - return disposable } -fun FrostWebViewClient.jsInject( - vararg injectors: InjectorContract, - callback: ((Int) -> Unit)? = null -) = web.jsInject(*injectors, callback = callback) +fun FrostWebViewClient.jsInject(vararg injectors: InjectorContract) = web.jsInject(*injectors) /** * Wrapper class to convert a function into an injector */ class JsInjector(val function: String) : InjectorContract { - override fun inject(webView: WebView, callback: (() -> Unit)?) { - webView.evaluateJavascript(function) { callback?.invoke() } - } + override fun inject(webView: WebView) = + webView.evaluateJavascript(function, null) } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/settings/Appearance.kt b/app/src/main/kotlin/com/pitchedapps/frost/settings/Appearance.kt index 538e20f1..1b1ca796 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/settings/Appearance.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/settings/Appearance.kt @@ -76,7 +76,7 @@ fun SettingsActivity.getAppearancePrefs(): KPrefAdapterBuilder.() -> Unit = { } fun invalidateCustomTheme() { - CssAssets.CUSTOM.injector.invalidate() + CssAssets.CUSTOM.reset() } colorPicker(R.string.text_color, Prefs::customTextColor, { diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt index 303142af..add9c6cd 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt @@ -68,7 +68,7 @@ object Prefs : KPref() { private val loader = lazyResettable { Theme.values[Prefs.theme] } - private val t: Theme by loader + val t: Theme by loader val textColor: Int get() = t.textColor diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt index 3f92c41d..4410b26e 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt @@ -70,8 +70,11 @@ import com.pitchedapps.frost.facebook.FbItem import com.pitchedapps.frost.facebook.FbUrlFormatter.Companion.VIDEO_REDIRECT import com.pitchedapps.frost.facebook.USER_AGENT_BASIC import com.pitchedapps.frost.facebook.formattedFbUrl +import com.pitchedapps.frost.injectors.CssAssets +import com.pitchedapps.frost.injectors.JsAssets import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch import org.apache.commons.text.StringEscapeUtils import org.jsoup.Jsoup @@ -421,3 +424,8 @@ fun String.unescapeHtml(): String = StringEscapeUtils.unescapeXml(this) .replace("\\u003C", "<") .replace("\\\"", "\"") + +suspend fun Context.loadAssets(): Unit = coroutineScope { + CssAssets.load(this@loadAssets) + JsAssets.load(this@loadAssets) +} -- cgit v1.2.3