diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/ActivityExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/ActivityExtensions.kt deleted file mode 100644 index bbe0cb134..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/util/system/ActivityExtensions.kt +++ /dev/null @@ -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 -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/AnimationExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/AnimationExtensions.kt index 3136e38c5..01c6b581b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/system/AnimationExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/AnimationExtensions.kt @@ -1,10 +1,18 @@ package eu.kanade.tachiyomi.util.system import android.content.Context +import android.provider.Settings import android.view.ViewPropertyAnimator import android.view.animation.Animation 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] */ fun Animation.applySystemAnimatorScale(context: Context) { this.duration = (this.duration * context.animatorDurationScale).toLong() diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt index e90c9cf3e..79da5aea3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt @@ -1,27 +1,18 @@ package eu.kanade.tachiyomi.util.system import android.app.ActivityManager -import android.app.NotificationManager import android.content.ClipData import android.content.ClipboardManager import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.content.res.Configuration -import android.content.res.Resources import android.graphics.Color import android.graphics.drawable.Drawable -import android.net.ConnectivityManager -import android.net.NetworkCapabilities import android.net.Uri -import android.net.wifi.WifiManager import android.os.Build import android.os.PowerManager -import android.provider.Settings import android.util.TypedValue -import android.view.Display -import android.view.View -import android.view.WindowManager import androidx.annotation.AttrRes import androidx.annotation.ColorInt import androidx.appcompat.view.ContextThemeWrapper @@ -34,7 +25,6 @@ import androidx.core.graphics.red import androidx.core.net.toUri import com.hippo.unifile.UniFile import eu.kanade.domain.ui.UiPreferences -import eu.kanade.domain.ui.model.TabletUiMode import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.R 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 get() = getSystemService()!! -val Context.displayCompat: Display? - get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - display - } else { - @Suppress("DEPRECATION") - getSystemService()?.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. */ @@ -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 resolveInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { packageManager.resolveActivity(browserIntent, PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY.toLong())) @@ -210,59 +165,6 @@ fun Context.createFileInCacheDir(name: String): 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().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 * @@ -292,35 +194,6 @@ fun Context.createReaderThemeContext(): Context { 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] * @@ -370,11 +243,3 @@ fun Context.getApplicationIcon(pkgName: String): Drawable? { 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) -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/DisplayExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/DisplayExtensions.kt new file mode 100644 index 000000000..e93905c74 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/DisplayExtensions.kt @@ -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().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()?.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) +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/NetworkExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/NetworkExtensions.kt new file mode 100644 index 000000000..8d72a9c86 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/NetworkExtensions.kt @@ -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 + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/NotificationExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/NotificationExtensions.kt index c4e207c83..5ad11e189 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/system/NotificationExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/NotificationExtensions.kt @@ -1,14 +1,19 @@ package eu.kanade.tachiyomi.util.system import android.Manifest +import android.app.NotificationManager import android.content.Context import androidx.core.app.NotificationChannelCompat import androidx.core.app.NotificationChannelGroupCompat import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import androidx.core.content.PermissionChecker +import androidx.core.content.getSystemService import eu.kanade.tachiyomi.R +val Context.notificationManager: NotificationManager + get() = getSystemService()!! + fun Context.notify(id: Int, channelId: String, block: (NotificationCompat.Builder.() -> Unit)? = null) { if (PermissionChecker.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PermissionChecker.PERMISSION_GRANTED) { return