Reorganize other util files
This commit is contained in:
@@ -0,0 +1,183 @@
|
||||
package eu.kanade.tachiyomi.util.system
|
||||
|
||||
import android.app.ActivityManager
|
||||
import android.app.Notification
|
||||
import android.app.NotificationManager
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.Resources
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.Uri
|
||||
import android.os.PowerManager
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.AttrRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.browser.customtabs.CustomTabsIntent
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||
import com.nononsenseapps.filepicker.FilePickerActivity
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.widget.CustomLayoutPickerActivity
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
Toast.makeText(this, resource, duration).show()
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
Toast.makeText(this, text.orEmpty(), duration).show()
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to create a notification.
|
||||
*
|
||||
* @param id the channel id.
|
||||
* @param func the function that will execute inside the builder.
|
||||
* @return a notification to be displayed or updated.
|
||||
*/
|
||||
inline fun Context.notification(channelId: String, func: NotificationCompat.Builder.() -> Unit): Notification {
|
||||
val builder = NotificationCompat.Builder(this, channelId)
|
||||
builder.func()
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to construct an Intent to use a custom file picker.
|
||||
* @param currentDir the path the file picker will open with.
|
||||
* @return an Intent to start the file picker activity.
|
||||
*/
|
||||
fun Context.getFilePicker(currentDir: String): Intent {
|
||||
return Intent(this, CustomLayoutPickerActivity::class.java)
|
||||
.putExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE, false)
|
||||
.putExtra(FilePickerActivity.EXTRA_ALLOW_CREATE_DIR, true)
|
||||
.putExtra(FilePickerActivity.EXTRA_MODE, FilePickerActivity.MODE_DIR)
|
||||
.putExtra(FilePickerActivity.EXTRA_START_PATH, currentDir)
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the give permission is granted.
|
||||
*
|
||||
* @param permission the permission to check.
|
||||
* @return true if it has permissions.
|
||||
*/
|
||||
fun Context.hasPermission(permission: String)
|
||||
= ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED
|
||||
|
||||
/**
|
||||
* Returns the color for the given attribute.
|
||||
*
|
||||
* @param resource the attribute.
|
||||
*/
|
||||
fun Context.getResourceColor(@AttrRes resource: Int): Int {
|
||||
val typedArray = obtainStyledAttributes(intArrayOf(resource))
|
||||
val attrValue = typedArray.getColor(0, 0)
|
||||
typedArray.recycle()
|
||||
return attrValue
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts to dp.
|
||||
*/
|
||||
val Int.pxToDp: Int
|
||||
get() = (this / Resources.getSystem().displayMetrics.density).toInt()
|
||||
|
||||
/**
|
||||
* Converts to px.
|
||||
*/
|
||||
val Int.dpToPx: Int
|
||||
get() = (this * Resources.getSystem().displayMetrics.density).toInt()
|
||||
|
||||
/**
|
||||
* Property to get the notification manager from the context.
|
||||
*/
|
||||
val Context.notificationManager: NotificationManager
|
||||
get() = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
|
||||
/**
|
||||
* Property to get the connectivity manager from the context.
|
||||
*/
|
||||
val Context.connectivityManager: ConnectivityManager
|
||||
get() = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
|
||||
/**
|
||||
* Property to get the power manager from the context.
|
||||
*/
|
||||
val Context.powerManager: PowerManager
|
||||
get() = getSystemService(Context.POWER_SERVICE) as PowerManager
|
||||
|
||||
/**
|
||||
* Function used to send a local broadcast asynchronous
|
||||
*
|
||||
* @param intent intent that contains broadcast information
|
||||
*/
|
||||
fun Context.sendLocalBroadcast(intent: Intent) {
|
||||
LocalBroadcastManager.getInstance(this).sendBroadcast(intent)
|
||||
}
|
||||
|
||||
/**
|
||||
* Function used to send a local broadcast synchronous
|
||||
*
|
||||
* @param intent intent that contains broadcast information
|
||||
*/
|
||||
fun Context.sendLocalBroadcastSync(intent: Intent) {
|
||||
LocalBroadcastManager.getInstance(this).sendBroadcastSync(intent)
|
||||
}
|
||||
|
||||
/**
|
||||
* Function used to register local broadcast
|
||||
*
|
||||
* @param receiver receiver that gets registered.
|
||||
*/
|
||||
fun Context.registerLocalReceiver(receiver: BroadcastReceiver, filter: IntentFilter) {
|
||||
LocalBroadcastManager.getInstance(this).registerReceiver(receiver, filter)
|
||||
}
|
||||
|
||||
/**
|
||||
* Function used to unregister local broadcast
|
||||
*
|
||||
* @param receiver receiver that gets unregistered.
|
||||
*/
|
||||
fun Context.unregisterLocalReceiver(receiver: BroadcastReceiver) {
|
||||
LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given service class is running.
|
||||
*/
|
||||
fun Context.isServiceRunning(serviceClass: Class<*>): Boolean {
|
||||
val className = serviceClass.name
|
||||
val manager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
|
||||
@Suppress("DEPRECATION")
|
||||
return manager.getRunningServices(Integer.MAX_VALUE)
|
||||
.any { className == it.service.className }
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a URL in a custom tab.
|
||||
*/
|
||||
fun Context.openInBrowser(url: String) {
|
||||
try {
|
||||
val parsedUrl = Uri.parse(url)
|
||||
val intent = CustomTabsIntent.Builder()
|
||||
.setToolbarColor(getResourceColor(R.attr.colorPrimary))
|
||||
.build()
|
||||
intent.launchUrl(this, parsedUrl)
|
||||
} catch (e: Exception) {
|
||||
toast(e.message)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package eu.kanade.tachiyomi.util.system
|
||||
|
||||
import javax.microedition.khronos.egl.EGL10
|
||||
import javax.microedition.khronos.egl.EGLConfig
|
||||
import javax.microedition.khronos.egl.EGLContext
|
||||
import kotlin.math.max
|
||||
|
||||
class GLUtil private constructor() {
|
||||
companion object {
|
||||
// Safe minimum default size
|
||||
private const val IMAGE_MAX_BITMAP_DIMENSION = 2048
|
||||
|
||||
val maxTextureSize: Int
|
||||
get() {
|
||||
// Get EGL Display
|
||||
val egl = EGLContext.getEGL() as EGL10
|
||||
val display = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY)
|
||||
|
||||
// Initialise
|
||||
val version = IntArray(2)
|
||||
egl.eglInitialize(display, version)
|
||||
|
||||
// Query total number of configurations
|
||||
val totalConfigurations = IntArray(1)
|
||||
egl.eglGetConfigs(display, null, 0, totalConfigurations)
|
||||
|
||||
// Query actual list configurations
|
||||
val configurationsList = arrayOfNulls<EGLConfig>(totalConfigurations[0])
|
||||
egl.eglGetConfigs(display, configurationsList, totalConfigurations[0], totalConfigurations)
|
||||
|
||||
val textureSize = IntArray(1)
|
||||
var maximumTextureSize = 0
|
||||
|
||||
// Iterate through all the configurations to located the maximum texture size
|
||||
for (i in 0 until totalConfigurations[0]) {
|
||||
// Only need to check for width since opengl textures are always squared
|
||||
egl.eglGetConfigAttrib(display, configurationsList[i], EGL10.EGL_MAX_PBUFFER_WIDTH, textureSize)
|
||||
|
||||
// Keep track of the maximum texture size
|
||||
if (maximumTextureSize < textureSize[0]) maximumTextureSize = textureSize[0]
|
||||
}
|
||||
|
||||
// Release
|
||||
egl.eglTerminate(display)
|
||||
|
||||
// Return largest texture size found, or default
|
||||
return max(maximumTextureSize, IMAGE_MAX_BITMAP_DIMENSION)
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
throw InstantiationException("This class is not for instantiation")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package eu.kanade.tachiyomi.util.system
|
||||
|
||||
import java.io.InputStream
|
||||
import java.net.URLConnection
|
||||
|
||||
object ImageUtil {
|
||||
|
||||
fun isImage(name: String, openStream: (() -> InputStream)? = null): Boolean {
|
||||
val contentType = try {
|
||||
URLConnection.guessContentTypeFromName(name)
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
} ?: openStream?.let { findImageType(it)?.mime }
|
||||
return contentType?.startsWith("image/") ?: false
|
||||
}
|
||||
|
||||
fun findImageType(openStream: () -> InputStream): ImageType? {
|
||||
return openStream().use { findImageType(it) }
|
||||
}
|
||||
|
||||
fun findImageType(stream: InputStream): ImageType? {
|
||||
try {
|
||||
val bytes = ByteArray(8)
|
||||
|
||||
val length = if (stream.markSupported()) {
|
||||
stream.mark(bytes.size)
|
||||
stream.read(bytes, 0, bytes.size).also { stream.reset() }
|
||||
} else {
|
||||
stream.read(bytes, 0, bytes.size)
|
||||
}
|
||||
|
||||
if (length == -1)
|
||||
return null
|
||||
|
||||
if (bytes.compareWith(charByteArrayOf(0xFF, 0xD8, 0xFF))) {
|
||||
return ImageType.JPG
|
||||
}
|
||||
if (bytes.compareWith(charByteArrayOf(0x89, 0x50, 0x4E, 0x47))) {
|
||||
return ImageType.PNG
|
||||
}
|
||||
if (bytes.compareWith("GIF8".toByteArray())) {
|
||||
return ImageType.GIF
|
||||
}
|
||||
if (bytes.compareWith("RIFF".toByteArray())) {
|
||||
return ImageType.WEBP
|
||||
}
|
||||
} catch(e: Exception) {
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun ByteArray.compareWith(magic: ByteArray): Boolean {
|
||||
for (i in magic.indices) {
|
||||
if (this[i] != magic[i]) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun charByteArrayOf(vararg bytes: Int): ByteArray {
|
||||
return ByteArray(bytes.size).apply {
|
||||
for (i in bytes.indices) {
|
||||
set(i, bytes[i].toByte())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class ImageType(val mime: String, val extension: String) {
|
||||
JPG("image/jpeg", "jpg"),
|
||||
PNG("image/png", "png"),
|
||||
GIF("image/gif", "gif"),
|
||||
WEBP("image/webp", "webp")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package eu.kanade.tachiyomi.util.system
|
||||
|
||||
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())
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
package eu.kanade.tachiyomi.util.system
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import android.os.Build
|
||||
import android.view.ContextThemeWrapper
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
* Utility class to change the application's language in runtime.
|
||||
*/
|
||||
@Suppress("DEPRECATION")
|
||||
object LocaleHelper {
|
||||
|
||||
/**
|
||||
* Preferences helper.
|
||||
*/
|
||||
private val preferences: PreferencesHelper by injectLazy()
|
||||
|
||||
/**
|
||||
* The system's locale.
|
||||
*/
|
||||
private var systemLocale: Locale? = null
|
||||
|
||||
/**
|
||||
* The application's locale. When it's null, the system locale is used.
|
||||
*/
|
||||
private var appLocale = getLocaleFromString(preferences.lang())
|
||||
|
||||
/**
|
||||
* The currently applied locale. Used to avoid losing the selected language after a non locale
|
||||
* configuration change to the application.
|
||||
*/
|
||||
private var currentLocale: Locale? = null
|
||||
|
||||
/**
|
||||
* Returns the locale for the value stored in preferences, or null if it's system language.
|
||||
*
|
||||
* @param pref the string value stored in preferences.
|
||||
*/
|
||||
fun getLocaleFromString(pref: String?): Locale? {
|
||||
if (pref.isNullOrEmpty()) {
|
||||
return null
|
||||
}
|
||||
return getLocale(pref)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Display name of a string language code
|
||||
*/
|
||||
fun getDisplayName(lang: String?, context: Context): String {
|
||||
return when (lang) {
|
||||
null -> ""
|
||||
"" -> context.getString(R.string.other_source)
|
||||
"all" -> context.getString(R.string.all_lang)
|
||||
else -> {
|
||||
val locale = getLocale(lang)
|
||||
locale.getDisplayName(locale).capitalize()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*Return Locale from string language code
|
||||
|
||||
*/
|
||||
private fun getLocale(lang: String): Locale {
|
||||
val sp = lang.split("_", "-")
|
||||
return when (sp.size) {
|
||||
2 -> Locale(sp[0], sp[1])
|
||||
3 -> Locale(sp[0], sp[1], sp[2])
|
||||
else -> Locale(lang)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the application's locale with a new preference.
|
||||
*
|
||||
* @param pref the new value stored in preferences.
|
||||
*/
|
||||
fun changeLocale(pref: String) {
|
||||
appLocale = getLocaleFromString(pref)
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the app's language to an activity.
|
||||
*/
|
||||
fun updateConfiguration(wrapper: ContextThemeWrapper) {
|
||||
if (appLocale != null) {
|
||||
val config = Configuration(preferences.context.resources.configuration)
|
||||
config.setLocale(appLocale)
|
||||
wrapper.applyOverrideConfiguration(config)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the app's language to the application.
|
||||
*/
|
||||
fun updateConfiguration(app: Application, config: Configuration, configChange: Boolean = false) {
|
||||
if (systemLocale == null) {
|
||||
systemLocale = getConfigLocale(config)
|
||||
}
|
||||
if (configChange) {
|
||||
val configLocale = getConfigLocale(config)
|
||||
if (currentLocale == configLocale) {
|
||||
return
|
||||
}
|
||||
systemLocale = configLocale
|
||||
}
|
||||
currentLocale = appLocale ?: systemLocale ?: Locale.getDefault()
|
||||
val newConfig = updateConfigLocale(config, currentLocale!!)
|
||||
val resources = app.resources
|
||||
resources.updateConfiguration(newConfig, resources.displayMetrics)
|
||||
|
||||
Locale.setDefault(currentLocale)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the locale applied in the given configuration.
|
||||
*/
|
||||
private fun getConfigLocale(config: Configuration): Locale {
|
||||
return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
||||
config.locale
|
||||
} else {
|
||||
config.locales[0]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new configuration with the given locale applied.
|
||||
*/
|
||||
private fun updateConfigLocale(config: Configuration, locale: Locale): Configuration {
|
||||
val newConfig = Configuration(config)
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
||||
newConfig.locale = locale
|
||||
} else {
|
||||
newConfig.setLocale(locale)
|
||||
}
|
||||
return newConfig
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package eu.kanade.tachiyomi.util.system
|
||||
|
||||
import android.annotation.TargetApi
|
||||
import android.os.Build
|
||||
import android.webkit.*
|
||||
|
||||
@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)
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
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)
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
final override fun onReceivedHttpError(
|
||||
view: WebView,
|
||||
request: WebResourceRequest,
|
||||
error: WebResourceResponse
|
||||
) {
|
||||
onReceivedErrorCompat(view, error.statusCode, error.reasonPhrase, request.url
|
||||
.toString(), request.isForMainFrame)
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user