Split up ContextExtensions into smaller files

This commit is contained in:
arkon 2023-03-19 18:27:30 -04:00
parent 859601a46e
commit d703fb7946
6 changed files with 163 additions and 150 deletions

View File

@ -1,14 +0,0 @@
package eu.kanade.tachiyomi.util.system
import android.app.Activity
import android.os.Build
/**
* Checks whether if the device has a display cutout (i.e. notch, camera cutout, etc.).
*
* Only works in Android 9+.
*/
fun Activity.hasDisplayCutout(): Boolean {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.P &&
window.decorView.rootWindowInsets?.displayCutout != null
}

View File

@ -1,10 +1,18 @@
package eu.kanade.tachiyomi.util.system package eu.kanade.tachiyomi.util.system
import android.content.Context import android.content.Context
import android.provider.Settings
import android.view.ViewPropertyAnimator import android.view.ViewPropertyAnimator
import android.view.animation.Animation import android.view.animation.Animation
import androidx.constraintlayout.motion.widget.MotionScene.Transition import androidx.constraintlayout.motion.widget.MotionScene.Transition
/**
* Gets the duration multiplier for general animations on the device
* @see Settings.Global.ANIMATOR_DURATION_SCALE
*/
val Context.animatorDurationScale: Float
get() = Settings.Global.getFloat(this.contentResolver, Settings.Global.ANIMATOR_DURATION_SCALE, 1f)
/** Scale the duration of this [Animation] by [Context.animatorDurationScale] */ /** Scale the duration of this [Animation] by [Context.animatorDurationScale] */
fun Animation.applySystemAnimatorScale(context: Context) { fun Animation.applySystemAnimatorScale(context: Context) {
this.duration = (this.duration * context.animatorDurationScale).toLong() this.duration = (this.duration * context.animatorDurationScale).toLong()

View File

@ -1,27 +1,18 @@
package eu.kanade.tachiyomi.util.system package eu.kanade.tachiyomi.util.system
import android.app.ActivityManager import android.app.ActivityManager
import android.app.NotificationManager
import android.content.ClipData import android.content.ClipData
import android.content.ClipboardManager import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.res.Configuration import android.content.res.Configuration
import android.content.res.Resources
import android.graphics.Color import android.graphics.Color
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.net.Uri import android.net.Uri
import android.net.wifi.WifiManager
import android.os.Build import android.os.Build
import android.os.PowerManager import android.os.PowerManager
import android.provider.Settings
import android.util.TypedValue import android.util.TypedValue
import android.view.Display
import android.view.View
import android.view.WindowManager
import androidx.annotation.AttrRes import androidx.annotation.AttrRes
import androidx.annotation.ColorInt import androidx.annotation.ColorInt
import androidx.appcompat.view.ContextThemeWrapper import androidx.appcompat.view.ContextThemeWrapper
@ -34,7 +25,6 @@ import androidx.core.graphics.red
import androidx.core.net.toUri import androidx.core.net.toUri
import com.hippo.unifile.UniFile import com.hippo.unifile.UniFile
import eu.kanade.domain.ui.UiPreferences import eu.kanade.domain.ui.UiPreferences
import eu.kanade.domain.ui.model.TabletUiMode
import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.base.delegate.ThemingDelegate import eu.kanade.tachiyomi.ui.base.delegate.ThemingDelegate
@ -112,44 +102,9 @@ fun Context.hasPermission(permission: String) = PermissionChecker.checkSelfPermi
} }
} }
/**
* Converts to px and takes into account LTR/RTL layout.
*/
val Float.dpToPxEnd: Float
get() = (
this * Resources.getSystem().displayMetrics.density *
if (Resources.getSystem().isLTR) 1 else -1
)
val Resources.isLTR
get() = configuration.layoutDirection == View.LAYOUT_DIRECTION_LTR
val Context.notificationManager: NotificationManager
get() = getSystemService()!!
val Context.connectivityManager: ConnectivityManager
get() = getSystemService()!!
val Context.wifiManager: WifiManager
get() = getSystemService()!!
val Context.powerManager: PowerManager val Context.powerManager: PowerManager
get() = getSystemService()!! get() = getSystemService()!!
val Context.displayCompat: Display?
get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
display
} else {
@Suppress("DEPRECATION")
getSystemService<WindowManager>()?.defaultDisplay
}
/** Gets the duration multiplier for general animations on the device
* @see Settings.Global.ANIMATOR_DURATION_SCALE
*/
val Context.animatorDurationScale: Float
get() = Settings.Global.getFloat(this.contentResolver, Settings.Global.ANIMATOR_DURATION_SCALE, 1f)
/** /**
* Convenience method to acquire a partial wake lock. * Convenience method to acquire a partial wake lock.
*/ */
@ -188,7 +143,7 @@ fun Context.openInBrowser(uri: Uri, forceDefaultBrowser: Boolean = false) {
} }
} }
fun Context.defaultBrowserPackageName(): String? { private fun Context.defaultBrowserPackageName(): String? {
val browserIntent = Intent(Intent.ACTION_VIEW, "http://".toUri()) val browserIntent = Intent(Intent.ACTION_VIEW, "http://".toUri())
val resolveInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { val resolveInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
packageManager.resolveActivity(browserIntent, PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY.toLong())) packageManager.resolveActivity(browserIntent, PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY.toLong()))
@ -210,59 +165,6 @@ fun Context.createFileInCacheDir(name: String): File {
return file return file
} }
private const val TABLET_UI_REQUIRED_SCREEN_WIDTH_DP = 720
// some tablets have screen width like 711dp = 1600px / 2.25
private const val TABLET_UI_MIN_SCREEN_WIDTH_PORTRAIT_DP = 700
// make sure icons on the nav rail fit
private const val TABLET_UI_MIN_SCREEN_WIDTH_LANDSCAPE_DP = 600
fun Context.isTabletUi(): Boolean {
return resources.configuration.isTabletUi()
}
fun Configuration.isTabletUi(): Boolean {
return smallestScreenWidthDp >= TABLET_UI_REQUIRED_SCREEN_WIDTH_DP
}
fun Configuration.isAutoTabletUiAvailable(): Boolean {
return smallestScreenWidthDp >= TABLET_UI_MIN_SCREEN_WIDTH_LANDSCAPE_DP
}
// TODO: move the logic to `isTabletUi()` when main activity is rewritten in Compose
fun Context.prepareTabletUiContext(): Context {
val configuration = resources.configuration
val expected = when (Injekt.get<UiPreferences>().tabletUiMode().get()) {
TabletUiMode.AUTOMATIC ->
configuration.smallestScreenWidthDp >= when (configuration.orientation) {
Configuration.ORIENTATION_PORTRAIT -> TABLET_UI_MIN_SCREEN_WIDTH_PORTRAIT_DP
else -> TABLET_UI_MIN_SCREEN_WIDTH_LANDSCAPE_DP
}
TabletUiMode.ALWAYS -> true
TabletUiMode.LANDSCAPE -> configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
TabletUiMode.NEVER -> false
}
if (configuration.isTabletUi() != expected) {
val overrideConf = Configuration()
overrideConf.setTo(configuration)
overrideConf.smallestScreenWidthDp = if (expected) {
overrideConf.smallestScreenWidthDp.coerceAtLeast(TABLET_UI_REQUIRED_SCREEN_WIDTH_DP)
} else {
overrideConf.smallestScreenWidthDp.coerceAtMost(TABLET_UI_REQUIRED_SCREEN_WIDTH_DP - 1)
}
return createConfigurationContext(overrideConf)
}
return this
}
/**
* Returns true if current context is in night mode
*/
fun Context.isNightMode(): Boolean {
return resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
}
/** /**
* Creates night mode Context depending on reader theme/background * Creates night mode Context depending on reader theme/background
* *
@ -292,35 +194,6 @@ fun Context.createReaderThemeContext(): Context {
return this return this
} }
fun Context.isOnline(): Boolean {
val activeNetwork = connectivityManager.activeNetwork ?: return false
val networkCapabilities = connectivityManager.getNetworkCapabilities(activeNetwork) ?: return false
val maxTransport = when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1 -> NetworkCapabilities.TRANSPORT_LOWPAN
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O -> NetworkCapabilities.TRANSPORT_WIFI_AWARE
else -> NetworkCapabilities.TRANSPORT_VPN
}
return (NetworkCapabilities.TRANSPORT_CELLULAR..maxTransport).any(networkCapabilities::hasTransport)
}
/**
* Returns true if device is connected to Wifi.
*/
fun Context.isConnectedToWifi(): Boolean {
if (!wifiManager.isWifiEnabled) return false
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val activeNetwork = connectivityManager.activeNetwork ?: return false
val networkCapabilities = connectivityManager.getNetworkCapabilities(activeNetwork) ?: return false
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) &&
networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
} else {
@Suppress("DEPRECATION")
wifiManager.connectionInfo.bssid != null
}
}
/** /**
* Gets document size of provided [Uri] * Gets document size of provided [Uri]
* *
@ -370,11 +243,3 @@ fun Context.getApplicationIcon(pkgName: String): Drawable? {
null null
} }
} }
/**
* Gets system's config_navBarNeedsScrim boolean flag added in Android 10, defaults to true.
*/
fun Context.isNavigationBarNeedsScrim(): Boolean {
return Build.VERSION.SDK_INT < Build.VERSION_CODES.Q ||
InternalResourceHelper.getBoolean(this, "config_navBarNeedsScrim", true)
}

View File

@ -0,0 +1,106 @@
package eu.kanade.tachiyomi.util.system
import android.app.Activity
import android.content.Context
import android.content.res.Configuration
import android.content.res.Resources
import android.os.Build
import android.view.Display
import android.view.View
import android.view.WindowManager
import androidx.core.content.getSystemService
import eu.kanade.domain.ui.UiPreferences
import eu.kanade.domain.ui.model.TabletUiMode
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
private const val TABLET_UI_REQUIRED_SCREEN_WIDTH_DP = 720
// some tablets have screen width like 711dp = 1600px / 2.25
private const val TABLET_UI_MIN_SCREEN_WIDTH_PORTRAIT_DP = 700
// make sure icons on the nav rail fit
private const val TABLET_UI_MIN_SCREEN_WIDTH_LANDSCAPE_DP = 600
fun Context.isTabletUi(): Boolean {
return resources.configuration.isTabletUi()
}
fun Configuration.isTabletUi(): Boolean {
return smallestScreenWidthDp >= TABLET_UI_REQUIRED_SCREEN_WIDTH_DP
}
fun Configuration.isAutoTabletUiAvailable(): Boolean {
return smallestScreenWidthDp >= TABLET_UI_MIN_SCREEN_WIDTH_LANDSCAPE_DP
}
// TODO: move the logic to `isTabletUi()` when main activity is rewritten in Compose
fun Context.prepareTabletUiContext(): Context {
val configuration = resources.configuration
val expected = when (Injekt.get<UiPreferences>().tabletUiMode().get()) {
TabletUiMode.AUTOMATIC ->
configuration.smallestScreenWidthDp >= when (configuration.orientation) {
Configuration.ORIENTATION_PORTRAIT -> TABLET_UI_MIN_SCREEN_WIDTH_PORTRAIT_DP
else -> TABLET_UI_MIN_SCREEN_WIDTH_LANDSCAPE_DP
}
TabletUiMode.ALWAYS -> true
TabletUiMode.LANDSCAPE -> configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
TabletUiMode.NEVER -> false
}
if (configuration.isTabletUi() != expected) {
val overrideConf = Configuration()
overrideConf.setTo(configuration)
overrideConf.smallestScreenWidthDp = if (expected) {
overrideConf.smallestScreenWidthDp.coerceAtLeast(TABLET_UI_REQUIRED_SCREEN_WIDTH_DP)
} else {
overrideConf.smallestScreenWidthDp.coerceAtMost(TABLET_UI_REQUIRED_SCREEN_WIDTH_DP - 1)
}
return createConfigurationContext(overrideConf)
}
return this
}
/**
* Returns true if current context is in night mode
*/
fun Context.isNightMode(): Boolean {
return resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
}
val Context.displayCompat: Display?
get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
display
} else {
@Suppress("DEPRECATION")
getSystemService<WindowManager>()?.defaultDisplay
}
val Resources.isLTR
get() = configuration.layoutDirection == View.LAYOUT_DIRECTION_LTR
/**
* Converts to px and takes into account LTR/RTL layout.
*/
val Float.dpToPxEnd: Float
get() = (
this * Resources.getSystem().displayMetrics.density *
if (Resources.getSystem().isLTR) 1 else -1
)
/**
* Checks whether if the device has a display cutout (i.e. notch, camera cutout, etc.).
*
* Only works in Android 9+.
*/
fun Activity.hasDisplayCutout(): Boolean {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.P &&
window.decorView.rootWindowInsets?.displayCutout != null
}
/**
* Gets system's config_navBarNeedsScrim boolean flag added in Android 10, defaults to true.
*/
fun Context.isNavigationBarNeedsScrim(): Boolean {
return Build.VERSION.SDK_INT < Build.VERSION_CODES.Q ||
InternalResourceHelper.getBoolean(this, "config_navBarNeedsScrim", true)
}

View File

@ -0,0 +1,43 @@
package eu.kanade.tachiyomi.util.system
import android.content.Context
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.net.wifi.WifiManager
import android.os.Build
import androidx.core.content.getSystemService
val Context.connectivityManager: ConnectivityManager
get() = getSystemService()!!
val Context.wifiManager: WifiManager
get() = getSystemService()!!
fun Context.isOnline(): Boolean {
val activeNetwork = connectivityManager.activeNetwork ?: return false
val networkCapabilities = connectivityManager.getNetworkCapabilities(activeNetwork) ?: return false
val maxTransport = when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1 -> NetworkCapabilities.TRANSPORT_LOWPAN
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O -> NetworkCapabilities.TRANSPORT_WIFI_AWARE
else -> NetworkCapabilities.TRANSPORT_VPN
}
return (NetworkCapabilities.TRANSPORT_CELLULAR..maxTransport).any(networkCapabilities::hasTransport)
}
/**
* Returns true if device is connected to Wifi.
*/
fun Context.isConnectedToWifi(): Boolean {
if (!wifiManager.isWifiEnabled) return false
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val activeNetwork = connectivityManager.activeNetwork ?: return false
val networkCapabilities = connectivityManager.getNetworkCapabilities(activeNetwork) ?: return false
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) &&
networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
} else {
@Suppress("DEPRECATION")
wifiManager.connectionInfo.bssid != null
}
}

View File

@ -1,14 +1,19 @@
package eu.kanade.tachiyomi.util.system package eu.kanade.tachiyomi.util.system
import android.Manifest import android.Manifest
import android.app.NotificationManager
import android.content.Context import android.content.Context
import androidx.core.app.NotificationChannelCompat import androidx.core.app.NotificationChannelCompat
import androidx.core.app.NotificationChannelGroupCompat import androidx.core.app.NotificationChannelGroupCompat
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import androidx.core.content.PermissionChecker import androidx.core.content.PermissionChecker
import androidx.core.content.getSystemService
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
val Context.notificationManager: NotificationManager
get() = getSystemService()!!
fun Context.notify(id: Int, channelId: String, block: (NotificationCompat.Builder.() -> Unit)? = null) { fun Context.notify(id: Int, channelId: String, block: (NotificationCompat.Builder.() -> Unit)? = null) {
if (PermissionChecker.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PermissionChecker.PERMISSION_GRANTED) { if (PermissionChecker.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PermissionChecker.PERMISSION_GRANTED) {
return return