Reorganize other util files

This commit is contained in:
arkon
2020-02-02 22:22:54 -05:00
parent faf8f1fbbc
commit 47f5ea881f
123 changed files with 218 additions and 192 deletions
@@ -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)
}
}