mirror of
https://github.com/null2264/yokai.git
synced 2025-07-18 06:46:57 +00:00
feat: Start using moko for i18n
This commit is contained in:
parent
872cc92c93
commit
6d531fbb5c
19 changed files with 171 additions and 44 deletions
|
@ -9,16 +9,18 @@ kotlin {
|
|||
sourceSets {
|
||||
val commonMain by getting {
|
||||
dependencies {
|
||||
api(libs.okhttp)
|
||||
api(libs.okhttp.logging.interceptor)
|
||||
api(libs.okhttp.dnsoverhttps)
|
||||
api(libs.okhttp.brotli)
|
||||
api(libs.okio)
|
||||
implementation(projects.i18n)
|
||||
api(libs.bundles.logging)
|
||||
}
|
||||
}
|
||||
val androidMain by getting {
|
||||
dependencies {
|
||||
api(libs.okhttp)
|
||||
api(libs.okhttp.logging.interceptor)
|
||||
api(libs.okhttp.dnsoverhttps)
|
||||
api(libs.okhttp.brotli)
|
||||
api(libs.okio)
|
||||
|
||||
api(androidx.core)
|
||||
api(androidx.annotation)
|
||||
api(libs.rxjava)
|
||||
|
|
|
@ -5,7 +5,6 @@ import android.content.Context
|
|||
import android.webkit.WebView
|
||||
import android.widget.Toast
|
||||
import androidx.core.content.ContextCompat
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.network.AndroidCookieJar
|
||||
import eu.kanade.tachiyomi.util.system.WebViewClientCompat
|
||||
import eu.kanade.tachiyomi.util.system.isOutdated
|
||||
|
@ -15,6 +14,8 @@ import okhttp3.HttpUrl.Companion.toHttpUrl
|
|||
import okhttp3.Interceptor
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import yokai.i18n.MR
|
||||
import yokai.util.lang.getMString
|
||||
import java.io.IOException
|
||||
import java.util.concurrent.*
|
||||
|
||||
|
@ -48,7 +49,7 @@ class CloudflareInterceptor(
|
|||
// Because OkHttp's enqueue only handles IOExceptions, wrap the exception so that
|
||||
// we don't crash the entire app
|
||||
catch (e: CloudflareBypassException) {
|
||||
throw IOException(context.getString(R.string.failed_to_bypass_cloudflare))
|
||||
throw IOException(context.getMString(MR.strings.failed_to_bypass_cloudflare))
|
||||
} catch (e: Exception) {
|
||||
throw IOException(e)
|
||||
}
|
||||
|
@ -130,7 +131,7 @@ class CloudflareInterceptor(
|
|||
if (!cloudflareBypassed) {
|
||||
// Prompt user to update WebView if it seems too outdated
|
||||
if (isWebViewOutdated) {
|
||||
context.toast(R.string.please_update_webview, Toast.LENGTH_LONG)
|
||||
context.toast(MR.strings.please_update_webview, Toast.LENGTH_LONG)
|
||||
}
|
||||
|
||||
throw CloudflareBypassException()
|
||||
|
|
|
@ -5,7 +5,6 @@ import android.os.Build
|
|||
import android.webkit.WebSettings
|
||||
import android.webkit.WebView
|
||||
import android.widget.Toast
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
||||
import eu.kanade.tachiyomi.util.system.WebViewUtil
|
||||
import eu.kanade.tachiyomi.util.system.launchUI
|
||||
|
@ -15,6 +14,7 @@ import okhttp3.Headers
|
|||
import okhttp3.Interceptor
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import yokai.i18n.MR
|
||||
import java.util.*
|
||||
import java.util.concurrent.*
|
||||
|
||||
|
@ -56,7 +56,7 @@ abstract class WebViewInterceptor(
|
|||
|
||||
if (!WebViewUtil.supportsWebView(context)) {
|
||||
launchUI {
|
||||
context.toast(R.string.webview_is_required, Toast.LENGTH_LONG)
|
||||
context.toast(MR.strings.webview_is_required, Toast.LENGTH_LONG)
|
||||
}
|
||||
return response
|
||||
}
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
package eu.kanade.tachiyomi.util.system
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.util.TypedValue
|
||||
|
||||
/**
|
||||
* Converts to dp.
|
||||
*/
|
||||
val Int.pxToDp: Int
|
||||
get() = (this / Resources.getSystem().displayMetrics.density).toInt()
|
||||
|
||||
val Float.pxToDp: Float
|
||||
get() = (this / Resources.getSystem().displayMetrics.density)
|
||||
|
||||
/**
|
||||
* Converts to px.
|
||||
*/
|
||||
val Int.dpToPx: Int
|
||||
get() = this.toFloat().dpToPx.toInt()
|
||||
|
||||
val Int.spToPx: Int
|
||||
get() = this.toFloat().spToPx.toInt()
|
||||
|
||||
val Float.spToPx: Float
|
||||
get() = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, this, Resources.getSystem().displayMetrics)
|
||||
|
||||
val Float.dpToPx: Float
|
||||
get() = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, this, Resources.getSystem().displayMetrics)
|
|
@ -0,0 +1,199 @@
|
|||
package eu.kanade.tachiyomi.util.system
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.hardware.display.DisplayManager
|
||||
import android.os.Build
|
||||
import android.view.Display
|
||||
import android.view.Window
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import co.touchlab.kermit.Logger
|
||||
|
||||
|
||||
object DeviceUtil {
|
||||
|
||||
val isMiui by lazy {
|
||||
getSystemProperty("ro.miui.ui.version.name")?.isNotEmpty() ?: false
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the MIUI major version code from a string like "V12.5.3.0.QFGMIXM".
|
||||
*
|
||||
* @return MIUI major version code (e.g., 13) or null if can't be parsed.
|
||||
*/
|
||||
val miuiMajorVersion by lazy {
|
||||
if (!isMiui) return@lazy null
|
||||
|
||||
Build.VERSION.INCREMENTAL
|
||||
.substringBefore('.')
|
||||
.trimStart('V')
|
||||
.toIntOrNull()
|
||||
}
|
||||
|
||||
@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 oneUiVersion by lazy {
|
||||
try {
|
||||
val semPlatformIntField = Build.VERSION::class.java.getDeclaredField("SEM_PLATFORM_INT")
|
||||
val version = semPlatformIntField.getInt(null) - 90000
|
||||
if (version < 0) {
|
||||
1.0
|
||||
} else {
|
||||
((version / 10000).toString() + "." + version % 10000 / 100).toDouble()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
val invalidDefaultBrowsers = listOf(
|
||||
"android",
|
||||
"com.huawei.android.internal.app",
|
||||
"com.zui.resolver",
|
||||
)
|
||||
|
||||
@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) {
|
||||
Logger.w(e) { "Unable to use SystemProperties.get" }
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
val isVivo by lazy {
|
||||
val prop = getSystemProperty("ro.vivo.os.name")
|
||||
!prop.isNullOrBlank() &&
|
||||
prop.contains("funtouch", true)
|
||||
}
|
||||
|
||||
fun setLegacyCutoutMode(window: Window, mode: LegacyCutoutMode) {
|
||||
when (mode) {
|
||||
LegacyCutoutMode.SHORT_EDGES -> {
|
||||
// Vivo doesn't support this, user had to set it from Settings
|
||||
/*
|
||||
if (isVivo) {
|
||||
}
|
||||
*/
|
||||
}
|
||||
LegacyCutoutMode.NEVER -> {
|
||||
// Vivo doesn't support this, user had to set it from Settings
|
||||
/*
|
||||
if (isVivo) {
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun hasCutout(context: Activity?): CutoutSupport {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
if (context?.getSystemService<DisplayManager>()
|
||||
?.getDisplay(Display.DEFAULT_DISPLAY)?.cutout != null)
|
||||
return CutoutSupport.EXTENDED
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
val displayCutout = context?.window?.decorView?.rootWindowInsets?.displayCutout
|
||||
if (displayCutout?.safeInsetTop != null || displayCutout?.safeInsetBottom != null)
|
||||
return CutoutSupport.MODERN
|
||||
} else if (isVivo) {
|
||||
// https://swsdl.vivo.com.cn/appstore/developer/uploadfile/20180328/20180328152252602.pdf
|
||||
try {
|
||||
@SuppressLint("PrivateApi")
|
||||
val ftFeature = context?.classLoader
|
||||
?.loadClass("android.util.FtFeature")
|
||||
val isFeatureSupportMethod = ftFeature?.getMethod(
|
||||
"isFeatureSupport",
|
||||
Int::class.javaPrimitiveType,
|
||||
)
|
||||
val isNotchOnScreen = 0x00000020
|
||||
val isSupported = isFeatureSupportMethod?.invoke(ftFeature, isNotchOnScreen) as Boolean
|
||||
if (isSupported) return CutoutSupport.LEGACY
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
} else if (isMiui) {
|
||||
try {
|
||||
@SuppressLint("PrivateApi")
|
||||
val sysProp = context?.classLoader?.loadClass("android.os.SystemProperties")
|
||||
val method = sysProp?.getMethod("getInt", String::class.java, Int::class.javaPrimitiveType)
|
||||
val rt = method?.invoke(sysProp, "ro.miui.notch", 0) as Int
|
||||
if (rt == 1) return CutoutSupport.LEGACY
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
}
|
||||
return CutoutSupport.NONE
|
||||
}
|
||||
|
||||
fun getCutoutHeight(context: Activity?, cutoutSupport: CutoutSupport): Number {
|
||||
return when (cutoutSupport) {
|
||||
CutoutSupport.MODERN -> {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P)
|
||||
throw IllegalStateException("Modern cutout only available on Android P or higher")
|
||||
context?.window?.decorView?.rootWindowInsets?.displayCutout?.safeInsetTop ?: 0
|
||||
}
|
||||
CutoutSupport.EXTENDED -> {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q)
|
||||
throw IllegalStateException("Extended cutout only available on Android Q or higher")
|
||||
context?.window?.decorView?.rootWindowInsets?.displayCutout?.boundingRectTop?.height()?.toFloat() ?: 0f
|
||||
}
|
||||
CutoutSupport.LEGACY -> {
|
||||
if (isVivo) {
|
||||
val insetCompat = context?.window?.decorView?.rootWindowInsets?.let {
|
||||
WindowInsetsCompat.toWindowInsetsCompat(it)
|
||||
}
|
||||
val statusBarHeight = insetCompat?.getInsets(WindowInsetsCompat.Type.statusBars())?.top
|
||||
?: 24.dpToPx // 24dp is "standard" height for Android since Marshmallow
|
||||
var notchHeight = 32.dpToPx
|
||||
if (notchHeight < statusBarHeight) {
|
||||
notchHeight = statusBarHeight
|
||||
}
|
||||
notchHeight
|
||||
} else if (isMiui) {
|
||||
val resourceId = context?.resources?.getIdentifier("notch_height",
|
||||
"dimen", "android") ?: 0
|
||||
if (resourceId > 0) {
|
||||
context?.resources?.getDimensionPixelSize(resourceId) ?: 0
|
||||
} else {
|
||||
0
|
||||
}
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
else -> 0
|
||||
}
|
||||
}
|
||||
|
||||
enum class CutoutSupport {
|
||||
NONE,
|
||||
LEGACY, // Pre-Android P, the start of this hell
|
||||
MODERN, // Android P
|
||||
EXTENDED, // Android Q
|
||||
}
|
||||
|
||||
enum class LegacyCutoutMode {
|
||||
SHORT_EDGES,
|
||||
NEVER,
|
||||
}
|
||||
}
|
|
@ -3,6 +3,8 @@ package eu.kanade.tachiyomi.util.system
|
|||
import android.content.Context
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.StringRes
|
||||
import dev.icerock.moko.resources.StringResource
|
||||
import yokai.util.lang.getMString
|
||||
|
||||
/**
|
||||
* Display a toast in this context.
|
||||
|
@ -14,6 +16,16 @@ fun Context.toast(@StringRes resource: Int, duration: Int = Toast.LENGTH_SHORT)
|
|||
Toast.makeText(this, resource, duration).show()
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a toast in this context.
|
||||
*
|
||||
* @param resource the text resource.
|
||||
* @param duration the duration of the toast. Defaults to short.
|
||||
*/
|
||||
fun Context.toast(resource: StringResource, duration: Int = Toast.LENGTH_SHORT) {
|
||||
toast(getMString(resource), duration)
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a toast in this context.
|
||||
*
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
package yokai.util.lang
|
||||
|
||||
import android.content.Context
|
||||
import dev.icerock.moko.resources.StringResource
|
||||
import dev.icerock.moko.resources.desc.Resource
|
||||
import dev.icerock.moko.resources.desc.StringDesc
|
||||
|
||||
fun Context.getMString(stringRes: StringResource): String = StringDesc.Resource(stringRes).toString(this)
|
|
@ -1,13 +1,10 @@
|
|||
package eu.kanade.tachiyomi.network
|
||||
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import eu.kanade.tachiyomi.core.preference.PreferenceStore
|
||||
|
||||
class NetworkPreferences(private val preferenceStore: PreferenceStore) {
|
||||
|
||||
fun dohProvider() = preferenceStore.getInt("doh_provider", -1)
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.GINGERBREAD)
|
||||
fun defaultUserAgent() = preferenceStore.getString("default_user_agent", NetworkHelper.DEFAULT_USER_AGENT)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue