owenclave / app/src/main/java/io/nekohasekai/sagernet/ktx/Utils.kt

github - dev - back to tree - upstream
/****************************************************************************** * * * Copyright (C) 2021 by nekohasekai * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * ******************************************************************************/ package io.nekohasekai.sagernet.ktx import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.content.* import android.os.Build import android.system.Os import android.system.OsConstants import android.util.TypedValue import android.view.View import androidx.activity.result.ActivityResultLauncher import androidx.annotation.AttrRes import androidx.annotation.ColorRes import androidx.core.content.ContextCompat import androidx.fragment.app.DialogFragment import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager import androidx.preference.Preference import androidx.recyclerview.widget.LinearSmoothScroller import androidx.recyclerview.widget.RecyclerView import io.nekohasekai.sagernet.R import io.nekohasekai.sagernet.SagerNet import io.nekohasekai.sagernet.ui.MainActivity import io.nekohasekai.sagernet.ui.ThemedActivity import java.net.InetAddress import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicLong import java.util.concurrent.atomic.AtomicReference import kotlin.coroutines.Continuation import kotlin.reflect.KMutableProperty0 import kotlin.reflect.KProperty import kotlin.reflect.KProperty0 import androidx.core.view.isVisible import androidx.core.view.isGone inline fun Iterable.forEachTry(action: (T) -> Unit) { var result: Exception? = null for (element in this) try { action(element) } catch (e: Exception) { if (result == null) result = e else result.addSuppressed(e) } if (result != null) { throw result } } val Throwable.readableMessage: String get() = localizedMessage.takeIf { !it.isNullOrBlank() } ?: javaClass.simpleName fun parsePort(str: String?, default: Int, min: Int = 0): Int { val value = str?.toIntOrNull() ?: default return if (value !in min..65535) default else value } fun broadcastReceiver(callback: (Context, Intent) -> Unit): BroadcastReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) = callback(context, intent) } fun Context.listenForPackageChanges(onetime: Boolean = true, callback: () -> Unit) = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { callback() if (onetime) context.unregisterReceiver(this) } }.apply { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { registerReceiver(this, IntentFilter().apply { addAction(Intent.ACTION_PACKAGE_ADDED) addAction(Intent.ACTION_PACKAGE_REMOVED) addDataScheme("package") }, Context.RECEIVER_EXPORTED) } else { registerReceiver(this, IntentFilter().apply { addAction(Intent.ACTION_PACKAGE_ADDED) addAction(Intent.ACTION_PACKAGE_REMOVED) addDataScheme("package") }) } } fun Preference.remove() = parent!!.removePreference(this) /** * A slightly more performant variant of parseNumericAddress. * * Bug in Android 9.0 and lower: https://issuetracker.google.com/issues/123456213 */ private val parseNumericAddress by lazy { InetAddress::class.java.getDeclaredMethod("parseNumericAddress", String::class.java).apply { isAccessible = true } } fun String?.parseNumericAddress(): InetAddress? = Os.inet_pton(OsConstants.AF_INET, this) ?: Os.inet_pton(OsConstants.AF_INET6, this)?.let { if (Build.VERSION.SDK_INT >= 29) it else parseNumericAddress.invoke( null, this ) as InetAddress } @JvmOverloads fun DialogFragment.showAllowingStateLoss(fragmentManager: FragmentManager, tag: String? = null) { if (!fragmentManager.isStateSaved) show(fragmentManager, tag) } fun RecyclerView.scrollTo(index: Int, force: Boolean = false) { if (force) post { scrollToPosition(index) } postDelayed({ try { layoutManager?.startSmoothScroll(object : LinearSmoothScroller(context) { init { targetPosition = index } override fun getVerticalSnapPreference(): Int { return SNAP_TO_START } }) } catch (_: IllegalArgumentException) { } }, 300L) } val app get() = SagerNet.application val shortAnimTime by lazy { app.resources.getInteger(android.R.integer.config_shortAnimTime).toLong() } fun View.crossFadeFrom(other: View) { clearAnimation() other.clearAnimation() if (isVisible && other.isGone) return alpha = 0F visibility = View.VISIBLE animate().alpha(1F).duration = shortAnimTime other.animate().alpha(0F).setListener(object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator) { other.visibility = View.GONE } }).duration = shortAnimTime } fun Fragment.snackbar(textId: Int) = (requireActivity() as MainActivity).snackbar(textId) fun Fragment.snackbar(text: CharSequence) = (requireActivity() as MainActivity).snackbar(text) fun ThemedActivity.startFilesForResult( launcher: ActivityResultLauncher, input: String ) { try { return launcher.launch(input) } catch (_: ActivityNotFoundException) { } catch (_: SecurityException) { } snackbar(getString(R.string.file_manager_missing)).show() } fun Fragment.startFilesForResult( launcher: ActivityResultLauncher, input: String ) { try { return launcher.launch(input) } catch (_: ActivityNotFoundException) { } catch (_: SecurityException) { } (requireActivity() as ThemedActivity).snackbar(getString(R.string.file_manager_missing)).show() } fun Fragment.needReload() { if (SagerNet.started) { snackbar(getString(R.string.restart)).setAction(R.string.apply) { SagerNet.reloadService() }.show() } } fun Context.getColour(@ColorRes colorRes: Int): Int { return ContextCompat.getColor(this, colorRes) } fun Context.getColorAttr(@AttrRes resId: Int): Int { return ContextCompat.getColor(this, TypedValue().also { theme.resolveAttribute(resId, it, true) }.resourceId) } fun Continuation.tryResume(value: T) { try { resumeWith(Result.success(value)) } catch (_: IllegalStateException) { } } fun Continuation.tryResumeWithException(exception: Throwable) { try { resumeWith(Result.failure(exception)) } catch (_: IllegalStateException) { } } operator fun KProperty0.getValue(thisRef: Any?, property: KProperty<*>): F = get() operator fun KMutableProperty0.setValue( thisRef: Any?, property: KProperty<*>, value: F ) = set(value) operator fun AtomicBoolean.getValue(thisRef: Any?, property: KProperty<*>): Boolean = get() operator fun AtomicBoolean.setValue(thisRef: Any?, property: KProperty<*>, value: Boolean) = set(value) operator fun AtomicInteger.getValue(thisRef: Any?, property: KProperty<*>): Int = get() operator fun AtomicInteger.setValue(thisRef: Any?, property: KProperty<*>, value: Int) = set(value) operator fun AtomicLong.getValue(thisRef: Any?, property: KProperty<*>): Long = get() operator fun AtomicLong.setValue(thisRef: Any?, property: KProperty<*>, value: Long) = set(value) operator fun AtomicReference.getValue(thisRef: Any?, property: KProperty<*>): T = get() operator fun AtomicReference.setValue(thisRef: Any?, property: KProperty<*>, value: T) = set(value) operator fun Map.getValue(thisRef: K, property: KProperty<*>) = get(thisRef) operator fun MutableMap.setValue(thisRef: K, property: KProperty<*>, value: V?) { if (value != null) { put(thisRef, value) } else { remove(thisRef) } }