mirror of
https://github.com/mihonapp/mihon.git
synced 2025-11-14 21:18:56 +01:00
Extract source api from app module (#8014)
* Extract source api from app module * Extract source online api from app module
This commit is contained in:
@@ -1,26 +0,0 @@
|
||||
package eu.kanade.tachiyomi.util
|
||||
|
||||
import okhttp3.Response
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
|
||||
fun Element.selectText(css: String, defaultValue: String? = null): String? {
|
||||
return select(css).first()?.text() ?: defaultValue
|
||||
}
|
||||
|
||||
fun Element.selectInt(css: String, defaultValue: Int = 0): Int {
|
||||
return select(css).first()?.text()?.toInt() ?: defaultValue
|
||||
}
|
||||
|
||||
fun Element.attrOrText(css: String): String {
|
||||
return if (css != "text") attr(css) else text()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Jsoup document for this response.
|
||||
* @param html the body of the response. Use only if the body was read before calling this method.
|
||||
*/
|
||||
fun Response.asJsoup(html: String? = null): Document {
|
||||
return Jsoup.parse(html ?: body.string(), request.url.toString())
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package eu.kanade.tachiyomi.util.lang
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.CoroutineStart
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.NonCancellable
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
/**
|
||||
* Think twice before using this. This is a delicate API. It is easy to accidentally create resource or memory leaks when GlobalScope is used.
|
||||
*
|
||||
* **Possible replacements**
|
||||
* - suspend function
|
||||
* - custom scope like view or presenter scope
|
||||
*/
|
||||
@DelicateCoroutinesApi
|
||||
fun launchUI(block: suspend CoroutineScope.() -> Unit): Job =
|
||||
GlobalScope.launch(Dispatchers.Main, CoroutineStart.DEFAULT, block)
|
||||
|
||||
/**
|
||||
* Think twice before using this. This is a delicate API. It is easy to accidentally create resource or memory leaks when GlobalScope is used.
|
||||
*
|
||||
* **Possible replacements**
|
||||
* - suspend function
|
||||
* - custom scope like view or presenter scope
|
||||
*/
|
||||
@DelicateCoroutinesApi
|
||||
fun launchIO(block: suspend CoroutineScope.() -> Unit): Job =
|
||||
GlobalScope.launch(Dispatchers.IO, CoroutineStart.DEFAULT, block)
|
||||
|
||||
/**
|
||||
* Think twice before using this. This is a delicate API. It is easy to accidentally create resource or memory leaks when GlobalScope is used.
|
||||
*
|
||||
* **Possible replacements**
|
||||
* - suspend function
|
||||
* - custom scope like view or presenter scope
|
||||
*/
|
||||
@DelicateCoroutinesApi
|
||||
fun launchNow(block: suspend CoroutineScope.() -> Unit): Job =
|
||||
GlobalScope.launch(Dispatchers.Main, CoroutineStart.UNDISPATCHED, block)
|
||||
|
||||
fun CoroutineScope.launchUI(block: suspend CoroutineScope.() -> Unit): Job =
|
||||
launch(Dispatchers.Main, block = block)
|
||||
|
||||
fun CoroutineScope.launchIO(block: suspend CoroutineScope.() -> Unit): Job =
|
||||
launch(Dispatchers.IO, block = block)
|
||||
|
||||
fun CoroutineScope.launchNonCancellableIO(block: suspend CoroutineScope.() -> Unit): Job =
|
||||
launchIO { withContext(NonCancellable, block) }
|
||||
|
||||
suspend fun <T> withUIContext(block: suspend CoroutineScope.() -> T) = withContext(Dispatchers.Main, block)
|
||||
|
||||
suspend fun <T> withIOContext(block: suspend CoroutineScope.() -> T) = withContext(Dispatchers.IO, block)
|
||||
@@ -1,87 +0,0 @@
|
||||
package eu.kanade.tachiyomi.util.lang
|
||||
|
||||
import kotlinx.coroutines.CancellableContinuation
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineStart
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import rx.Emitter
|
||||
import rx.Observable
|
||||
import rx.Subscriber
|
||||
import rx.Subscription
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.resumeWithException
|
||||
|
||||
/*
|
||||
* Util functions for bridging RxJava and coroutines. Taken from TachiyomiEH/SY.
|
||||
*/
|
||||
|
||||
suspend fun <T> Observable<T>.awaitSingle(): T = single().awaitOne()
|
||||
|
||||
private suspend fun <T> Observable<T>.awaitOne(): T = suspendCancellableCoroutine { cont ->
|
||||
cont.unsubscribeOnCancellation(
|
||||
subscribe(
|
||||
object : Subscriber<T>() {
|
||||
override fun onStart() {
|
||||
request(1)
|
||||
}
|
||||
|
||||
override fun onNext(t: T) {
|
||||
cont.resume(t)
|
||||
}
|
||||
|
||||
override fun onCompleted() {
|
||||
if (cont.isActive) {
|
||||
cont.resumeWithException(
|
||||
IllegalStateException(
|
||||
"Should have invoked onNext",
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onError(e: Throwable) {
|
||||
/*
|
||||
* Rx1 observable throws NoSuchElementException if cancellation happened before
|
||||
* element emission. To mitigate this we try to atomically resume continuation with exception:
|
||||
* if resume failed, then we know that continuation successfully cancelled itself
|
||||
*/
|
||||
val token = cont.tryResumeWithException(e)
|
||||
if (token != null) {
|
||||
cont.completeResume(token)
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
internal fun <T> CancellableContinuation<T>.unsubscribeOnCancellation(sub: Subscription) =
|
||||
invokeOnCancellation { sub.unsubscribe() }
|
||||
|
||||
fun <T> runAsObservable(
|
||||
backpressureMode: Emitter.BackpressureMode = Emitter.BackpressureMode.NONE,
|
||||
block: suspend () -> T,
|
||||
): Observable<T> {
|
||||
return Observable.create(
|
||||
{ emitter ->
|
||||
val job = GlobalScope.launch(Dispatchers.Unconfined, start = CoroutineStart.ATOMIC) {
|
||||
try {
|
||||
emitter.onNext(block())
|
||||
emitter.onCompleted()
|
||||
} catch (e: Throwable) {
|
||||
// Ignore `CancellationException` as error, since it indicates "normal cancellation"
|
||||
if (e !is CancellationException) {
|
||||
emitter.onError(e)
|
||||
} else {
|
||||
emitter.onCompleted()
|
||||
}
|
||||
}
|
||||
}
|
||||
emitter.setCancellation { job.cancel() }
|
||||
},
|
||||
backpressureMode,
|
||||
)
|
||||
}
|
||||
@@ -24,10 +24,8 @@ import android.util.TypedValue
|
||||
import android.view.Display
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.AttrRes
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.appcompat.view.ContextThemeWrapper
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
@@ -52,29 +50,6 @@ import kotlin.math.roundToInt
|
||||
|
||||
private const val TABLET_UI_MIN_SCREEN_WIDTH_DP = 720
|
||||
|
||||
/**
|
||||
* Display a toast in this context.
|
||||
*
|
||||
* @param resource the text resource.
|
||||
* @param duration the duration of the toast. Defaults to short.
|
||||
*/
|
||||
fun Context.toast(@StringRes resource: Int, duration: Int = Toast.LENGTH_SHORT, block: (Toast) -> Unit = {}): Toast {
|
||||
return toast(getString(resource), duration, block)
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a toast in this context.
|
||||
*
|
||||
* @param text the text to display.
|
||||
* @param duration the duration of the toast. Defaults to short.
|
||||
*/
|
||||
fun Context.toast(text: String?, duration: Int = Toast.LENGTH_SHORT, block: (Toast) -> Unit = {}): Toast {
|
||||
return Toast.makeText(applicationContext, text.orEmpty(), duration).also {
|
||||
block(it)
|
||||
it.show()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies a string to clipboard
|
||||
*
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
package eu.kanade.tachiyomi.util.system
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Build
|
||||
import com.google.android.material.color.DynamicColors
|
||||
import logcat.LogPriority
|
||||
|
||||
object DeviceUtil {
|
||||
|
||||
val isMiui by lazy {
|
||||
getSystemProperty("ro.miui.ui.version.name")?.isNotEmpty() ?: false
|
||||
}
|
||||
|
||||
@SuppressLint("PrivateApi")
|
||||
fun isMiuiOptimizationDisabled(): Boolean {
|
||||
val sysProp = getSystemProperty("persist.sys.miui_optimization")
|
||||
if (sysProp == "0" || sysProp == "false") {
|
||||
return true
|
||||
}
|
||||
|
||||
return try {
|
||||
Class.forName("android.miui.AppOpsUtils")
|
||||
.getDeclaredMethod("isXOptMode")
|
||||
.invoke(null) as Boolean
|
||||
} catch (e: Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
val isSamsung by lazy {
|
||||
Build.MANUFACTURER.equals("samsung", ignoreCase = true)
|
||||
}
|
||||
|
||||
val isDynamicColorAvailable by lazy {
|
||||
DynamicColors.isDynamicColorAvailable() || (isSamsung && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
|
||||
}
|
||||
|
||||
val invalidDefaultBrowsers = listOf("android", "com.huawei.android.internal.app")
|
||||
|
||||
@SuppressLint("PrivateApi")
|
||||
private fun getSystemProperty(key: String?): String? {
|
||||
return try {
|
||||
Class.forName("android.os.SystemProperties")
|
||||
.getDeclaredMethod("get", String::class.java)
|
||||
.invoke(null, key) as String
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.WARN, e) { "Unable to use SystemProperties.get()" }
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package eu.kanade.tachiyomi.util.system
|
||||
|
||||
import android.os.Build
|
||||
import com.google.android.material.color.DynamicColors
|
||||
|
||||
val DeviceUtil.isDynamicColorAvailable by lazy {
|
||||
DynamicColors.isDynamicColorAvailable() || (DeviceUtil.isSamsung && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package eu.kanade.tachiyomi.util.system
|
||||
|
||||
import logcat.LogPriority
|
||||
import logcat.asLog
|
||||
import logcat.logcat
|
||||
|
||||
inline fun Any.logcat(
|
||||
priority: LogPriority = LogPriority.DEBUG,
|
||||
throwable: Throwable? = null,
|
||||
message: () -> String = { "" },
|
||||
) = logcat(priority = priority) {
|
||||
var msg = message()
|
||||
if (throwable != null) {
|
||||
if (msg.isNotBlank()) msg += "\n"
|
||||
msg += throwable.asLog()
|
||||
}
|
||||
msg
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
package eu.kanade.tachiyomi.util.system
|
||||
|
||||
import android.annotation.TargetApi
|
||||
import android.os.Build
|
||||
import android.webkit.WebResourceError
|
||||
import android.webkit.WebResourceRequest
|
||||
import android.webkit.WebResourceResponse
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
|
||||
@Suppress("OverridingDeprecatedMember")
|
||||
abstract class WebViewClientCompat : WebViewClient() {
|
||||
|
||||
open fun shouldOverrideUrlCompat(view: WebView, url: String): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
open fun shouldInterceptRequestCompat(view: WebView, url: String): WebResourceResponse? {
|
||||
return null
|
||||
}
|
||||
|
||||
open fun onReceivedErrorCompat(
|
||||
view: WebView,
|
||||
errorCode: Int,
|
||||
description: String?,
|
||||
failingUrl: String,
|
||||
isMainFrame: Boolean,
|
||||
) {
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.N)
|
||||
final override fun shouldOverrideUrlLoading(
|
||||
view: WebView,
|
||||
request: WebResourceRequest,
|
||||
): Boolean {
|
||||
return shouldOverrideUrlCompat(view, request.url.toString())
|
||||
}
|
||||
|
||||
final override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {
|
||||
return shouldOverrideUrlCompat(view, url)
|
||||
}
|
||||
|
||||
final override fun shouldInterceptRequest(
|
||||
view: WebView,
|
||||
request: WebResourceRequest,
|
||||
): WebResourceResponse? {
|
||||
return shouldInterceptRequestCompat(view, request.url.toString())
|
||||
}
|
||||
|
||||
final override fun shouldInterceptRequest(
|
||||
view: WebView,
|
||||
url: String,
|
||||
): WebResourceResponse? {
|
||||
return shouldInterceptRequestCompat(view, url)
|
||||
}
|
||||
|
||||
final override fun onReceivedError(
|
||||
view: WebView,
|
||||
request: WebResourceRequest,
|
||||
error: WebResourceError,
|
||||
) {
|
||||
onReceivedErrorCompat(
|
||||
view,
|
||||
error.errorCode,
|
||||
error.description?.toString(),
|
||||
request.url.toString(),
|
||||
request.isForMainFrame,
|
||||
)
|
||||
}
|
||||
|
||||
final override fun onReceivedError(
|
||||
view: WebView,
|
||||
errorCode: Int,
|
||||
description: String?,
|
||||
failingUrl: String,
|
||||
) {
|
||||
onReceivedErrorCompat(view, errorCode, description, failingUrl, failingUrl == view.url)
|
||||
}
|
||||
|
||||
final override fun onReceivedHttpError(
|
||||
view: WebView,
|
||||
request: WebResourceRequest,
|
||||
error: WebResourceResponse,
|
||||
) {
|
||||
onReceivedErrorCompat(
|
||||
view,
|
||||
error.statusCode,
|
||||
error.reasonPhrase,
|
||||
request.url
|
||||
.toString(),
|
||||
request.isForMainFrame,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
package eu.kanade.tachiyomi.util.system
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.webkit.CookieManager
|
||||
import android.webkit.WebSettings
|
||||
import android.webkit.WebView
|
||||
import logcat.LogPriority
|
||||
|
||||
object WebViewUtil {
|
||||
const val SPOOF_PACKAGE_NAME = "org.chromium.chrome"
|
||||
|
||||
const val MINIMUM_WEBVIEW_VERSION = 100
|
||||
|
||||
fun supportsWebView(context: Context): Boolean {
|
||||
try {
|
||||
// May throw android.webkit.WebViewFactory$MissingWebViewPackageException if WebView
|
||||
// is not installed
|
||||
CookieManager.getInstance()
|
||||
} catch (e: Throwable) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
return false
|
||||
}
|
||||
|
||||
return context.packageManager.hasSystemFeature(PackageManager.FEATURE_WEBVIEW)
|
||||
}
|
||||
}
|
||||
|
||||
fun WebView.isOutdated(): Boolean {
|
||||
return getWebViewMajorVersion() < WebViewUtil.MINIMUM_WEBVIEW_VERSION
|
||||
}
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
fun WebView.setDefaultSettings() {
|
||||
with(settings) {
|
||||
javaScriptEnabled = true
|
||||
domStorageEnabled = true
|
||||
databaseEnabled = true
|
||||
useWideViewPort = true
|
||||
loadWithOverviewMode = true
|
||||
cacheMode = WebSettings.LOAD_DEFAULT
|
||||
}
|
||||
}
|
||||
|
||||
private fun WebView.getWebViewMajorVersion(): Int {
|
||||
val uaRegexMatch = """.*Chrome/(\d+)\..*""".toRegex().matchEntire(getDefaultUserAgentString())
|
||||
return if (uaRegexMatch != null && uaRegexMatch.groupValues.size > 1) {
|
||||
uaRegexMatch.groupValues[1].toInt()
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
// Based on https://stackoverflow.com/a/29218966
|
||||
private fun WebView.getDefaultUserAgentString(): String {
|
||||
val originalUA: String = settings.userAgentString
|
||||
|
||||
// Next call to getUserAgentString() will get us the default
|
||||
settings.userAgentString = null
|
||||
val defaultUserAgentString = settings.userAgentString
|
||||
|
||||
// Revert to original UA string
|
||||
settings.userAgentString = originalUA
|
||||
|
||||
return defaultUserAgentString
|
||||
}
|
||||
Reference in New Issue
Block a user