mirror of
https://github.com/null2264/yokai.git
synced 2025-06-21 10:44:42 +00:00
feat: Cutout support on pre-Android P
This commit is contained in:
parent
afe3fd64af
commit
9644b71e57
8 changed files with 119 additions and 71 deletions
|
@ -7,3 +7,8 @@
|
|||
|
||||
## Other
|
||||
-->
|
||||
## Additions
|
||||
- Added cutout support for some pre-Android P devices
|
||||
|
||||
## Fixes
|
||||
- Fixed cutout behaviour for Android P
|
|
@ -76,6 +76,7 @@ class ReaderGeneralView @JvmOverloads constructor(context: Context, attrs: Attri
|
|||
}
|
||||
|
||||
private fun updatePrefs() {
|
||||
binding.cutoutShort.isVisible = DeviceUtil.hasCutout(context) && preferences.fullscreen().get()
|
||||
binding.cutoutShort.isVisible =
|
||||
DeviceUtil.hasCutout(context as ReaderActivity).ordinal >= DeviceUtil.CutoutSupport.MODERN.ordinal && preferences.fullscreen().get()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,14 +3,9 @@ package eu.kanade.tachiyomi.ui.reader.settings
|
|||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import android.hardware.display.DisplayManager
|
||||
import android.os.Build
|
||||
import android.util.AttributeSet
|
||||
import android.view.Display
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.core.view.isVisible
|
||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||
import dev.yokai.domain.ui.settings.ReaderPreferences
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.databinding.ReaderPagedLayoutBinding
|
||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||
|
@ -18,7 +13,6 @@ import eu.kanade.tachiyomi.util.bindToPreference
|
|||
import eu.kanade.tachiyomi.util.lang.addBetaTag
|
||||
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
||||
import eu.kanade.tachiyomi.widget.BaseReaderSettingsView
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
class ReaderPagedView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
|
||||
BaseReaderSettingsView<ReaderPagedLayoutBinding>(context, attrs) {
|
||||
|
@ -110,15 +104,15 @@ class ReaderPagedView @JvmOverloads constructor(context: Context, attrs: Attribu
|
|||
else -> false
|
||||
}
|
||||
val ogView = (context as? Activity)?.window?.decorView
|
||||
val hasCutout = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
ogView?.rootWindowInsets?.displayCutout?.safeInsetTop != null || ogView?.rootWindowInsets?.displayCutout?.safeInsetBottom != null
|
||||
} else {
|
||||
false
|
||||
}
|
||||
binding.landscapeZoom.isVisible = show && preferences.imageScaleType().get() == SubsamplingScaleImageView.SCALE_TYPE_CENTER_INSIDE
|
||||
binding.extendPastCutout.isVisible = show && isFullFit && hasCutout && preferences.fullscreen().get()
|
||||
binding.extendPastCutoutLandscape.isVisible = DeviceUtil.hasCutout(context) && preferences.fullscreen().get() &&
|
||||
ogView?.resources?.configuration?.orientation == Configuration.ORIENTATION_LANDSCAPE
|
||||
binding.extendPastCutout.isVisible =
|
||||
show && isFullFit
|
||||
&& DeviceUtil.hasCutout(context as? Activity).ordinal >= DeviceUtil.CutoutSupport.LEGACY.ordinal
|
||||
&& preferences.fullscreen().get()
|
||||
binding.extendPastCutoutLandscape.isVisible =
|
||||
DeviceUtil.hasCutout(context as? Activity).ordinal >= DeviceUtil.CutoutSupport.MODERN.ordinal
|
||||
&& preferences.fullscreen().get()
|
||||
&& ogView?.resources?.configuration?.orientation == Configuration.ORIENTATION_LANDSCAPE
|
||||
if (binding.extendPastCutoutLandscape.isVisible) {
|
||||
binding.filterLinearLayout.removeView(binding.extendPastCutoutLandscape)
|
||||
binding.filterLinearLayout.addView(
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package eu.kanade.tachiyomi.ui.reader.viewer
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.graphics.PointF
|
||||
import android.graphics.drawable.Animatable
|
||||
|
@ -16,7 +17,6 @@ import androidx.annotation.AttrRes
|
|||
import androidx.annotation.CallSuper
|
||||
import androidx.annotation.StyleRes
|
||||
import androidx.appcompat.widget.AppCompatImageView
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.isVisible
|
||||
import coil.dispose
|
||||
import coil.imageLoader
|
||||
|
@ -29,6 +29,7 @@ import com.github.chrisbanes.photoview.PhotoView
|
|||
import dev.yokai.domain.ui.settings.ReaderPreferences.CutoutBehaviour
|
||||
import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerConfig
|
||||
import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonSubsamplingImageView
|
||||
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
||||
import eu.kanade.tachiyomi.util.system.GLUtil
|
||||
import eu.kanade.tachiyomi.util.system.animatorDurationScale
|
||||
import java.io.InputStream
|
||||
|
@ -190,15 +191,16 @@ open class ReaderPageImageView @JvmOverloads constructor(
|
|||
config.insetInfo.scaleTypeIsFullFit && topInsets + bottomInsets > 0,
|
||||
)
|
||||
if ((config.insetInfo.cutoutBehavior != CutoutBehaviour.IGNORE || !config.insetInfo.scaleTypeIsFullFit) &&
|
||||
android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q &&
|
||||
config.insetInfo.isFullscreen
|
||||
) {
|
||||
val insets: WindowInsets? = config.insetInfo.insets
|
||||
setExtraSpace(
|
||||
0f,
|
||||
insets?.displayCutout?.boundingRectTop?.height()?.toFloat() ?: 0f,
|
||||
DeviceUtil.getCutoutHeight(context as? Activity, config.insetInfo.cutoutSupport).toFloat(),
|
||||
0f,
|
||||
insets?.displayCutout?.boundingRectBottom?.height()?.toFloat() ?: 0f,
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q)
|
||||
insets?.displayCutout?.boundingRectBottom?.height()?.toFloat() ?: 0f
|
||||
else 0f,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -318,6 +320,7 @@ open class ReaderPageImageView @JvmOverloads constructor(
|
|||
)
|
||||
|
||||
data class InsetInfo(
|
||||
val cutoutSupport: DeviceUtil.CutoutSupport,
|
||||
val cutoutBehavior: CutoutBehaviour,
|
||||
val topCutoutInset: Float,
|
||||
val bottomCutoutInset: Float,
|
||||
|
|
|
@ -12,6 +12,7 @@ import eu.kanade.tachiyomi.ui.reader.viewer.navigation.EdgeNavigation
|
|||
import eu.kanade.tachiyomi.ui.reader.viewer.navigation.KindlishNavigation
|
||||
import eu.kanade.tachiyomi.ui.reader.viewer.navigation.LNavigation
|
||||
import eu.kanade.tachiyomi.ui.reader.viewer.navigation.RightAndLeftNavigation
|
||||
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.drop
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
|
|
|
@ -26,6 +26,7 @@ import eu.kanade.tachiyomi.ui.reader.viewer.ReaderErrorView
|
|||
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderPageImageView
|
||||
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderProgressBar
|
||||
import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerConfig.ZoomType
|
||||
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
||||
import eu.kanade.tachiyomi.util.system.ImageUtil
|
||||
import eu.kanade.tachiyomi.util.system.ImageUtil.isPagePadded
|
||||
import eu.kanade.tachiyomi.util.system.ThemeUtil
|
||||
|
@ -543,12 +544,13 @@ class PagerPageHolder(
|
|||
zoomStartPosition = viewer.config.imageZoomType,
|
||||
landscapeZoom = viewer.config.landscapeZoom,
|
||||
insetInfo = InsetInfo(
|
||||
cutoutSupport = DeviceUtil.hasCutout(viewer.activity),
|
||||
cutoutBehavior = viewer.config.cutoutBehavior,
|
||||
topCutoutInset = viewer.activity.window.decorView.rootWindowInsets?.topCutoutInset()?.toFloat() ?: 0f,
|
||||
bottomCutoutInset = viewer.activity.window.decorView.rootWindowInsets?.bottomCutoutInset()?.toFloat() ?: 0f,
|
||||
scaleTypeIsFullFit = viewer.config.scaleTypeIsFullFit(),
|
||||
isFullscreen = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q &&
|
||||
viewer.config.isFullscreen && !viewer.activity.isInMultiWindowMode,
|
||||
isFullscreen = viewer.config.isFullscreen
|
||||
&& if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) !viewer.activity.isInMultiWindowMode else true,
|
||||
isSplitScreen = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && viewer.activity.isInMultiWindowMode,
|
||||
insets = viewer.activity.window.decorView.rootWindowInsets,
|
||||
),
|
||||
|
|
|
@ -121,7 +121,9 @@ class SettingsReaderController : SettingsController() {
|
|||
// FIXME: Transition from reader to homepage is broken when cutout short is disabled
|
||||
title = context.getString(R.string.pref_cutout_short).addBetaTag(context)
|
||||
|
||||
preferences.fullscreen().changesIn(viewScope) { isVisible = DeviceUtil.hasCutout(activity) && it}
|
||||
preferences.fullscreen().changesIn(viewScope) {
|
||||
isVisible = DeviceUtil.hasCutout(activity).ordinal >= DeviceUtil.CutoutSupport.MODERN.ordinal && it
|
||||
}
|
||||
}
|
||||
listPreference(activity) {
|
||||
bindTo(readerPreferences.landscapeCutoutBehavior())
|
||||
|
@ -130,7 +132,9 @@ class SettingsReaderController : SettingsController() {
|
|||
entriesRes = values.map { it.titleResId }.toTypedArray()
|
||||
entryValues = values.map { it.name }
|
||||
|
||||
preferences.fullscreen().changesIn(viewScope) { isVisible = DeviceUtil.hasCutout(activity) && it}
|
||||
preferences.fullscreen().changesIn(viewScope) {
|
||||
isVisible = DeviceUtil.hasCutout(activity).ordinal >= DeviceUtil.CutoutSupport.MODERN.ordinal && it
|
||||
}
|
||||
}
|
||||
switchPreference {
|
||||
key = Keys.keepScreenOn
|
||||
|
@ -221,22 +225,13 @@ class SettingsReaderController : SettingsController() {
|
|||
entryValues = values.map { it.name }
|
||||
|
||||
// Calling this once to show only on cutout
|
||||
isVisible = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
activityBinding?.root?.rootWindowInsets?.displayCutout?.safeInsetTop != null ||
|
||||
activityBinding?.root?.rootWindowInsets?.displayCutout?.safeInsetBottom != null
|
||||
} else {
|
||||
false
|
||||
}
|
||||
isVisible = DeviceUtil.hasCutout(activity).ordinal >= DeviceUtil.CutoutSupport.LEGACY.ordinal
|
||||
|
||||
// Calling this a second time in case activity is recreated while on this page
|
||||
// Keep the first so it shouldn't animate hiding the preference for phones without
|
||||
// cutouts
|
||||
activityBinding?.root?.post {
|
||||
isVisible = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
activityBinding?.root?.rootWindowInsets?.displayCutout?.safeInsetTop != null ||
|
||||
activityBinding?.root?.rootWindowInsets?.displayCutout?.safeInsetBottom != null
|
||||
} else {
|
||||
false
|
||||
}
|
||||
isVisible = DeviceUtil.hasCutout(activity).ordinal >= DeviceUtil.CutoutSupport.LEGACY.ordinal
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
package eu.kanade.tachiyomi.util.system
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.app.Activity
|
||||
import android.hardware.display.DisplayManager
|
||||
import android.os.Build
|
||||
import android.view.Display
|
||||
import android.view.View
|
||||
import android.view.Window
|
||||
import android.view.WindowManager
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import timber.log.Timber
|
||||
import java.lang.reflect.InvocationTargetException
|
||||
|
||||
|
||||
|
||||
object DeviceUtil {
|
||||
|
||||
|
@ -92,59 +94,104 @@ object DeviceUtil {
|
|||
fun setLegacyCutoutMode(window: Window, mode: LegacyCutoutMode) {
|
||||
when (mode) {
|
||||
LegacyCutoutMode.SHORT_EDGES -> {
|
||||
/* Deprecated method
|
||||
// Vivo doesn't support this, user had to set it from Settings
|
||||
/*
|
||||
if (isVivo) {
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
|
||||
var systemUiVisibility = window.decorView.systemUiVisibility
|
||||
systemUiVisibility = systemUiVisibility or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
systemUiVisibility = systemUiVisibility or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||
window.decorView.systemUiVisibility = systemUiVisibility
|
||||
}
|
||||
*/
|
||||
}
|
||||
LegacyCutoutMode.NEVER -> {
|
||||
/* Deprecated method
|
||||
// Vivo doesn't support this, user had to set it from Settings
|
||||
/*
|
||||
if (isVivo) {
|
||||
window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
|
||||
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
|
||||
var systemUiVisibility = window.decorView.systemUiVisibility
|
||||
systemUiVisibility = systemUiVisibility and View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN.inv()
|
||||
systemUiVisibility = systemUiVisibility and View.SYSTEM_UI_FLAG_LAYOUT_STABLE.inv()
|
||||
window.decorView.systemUiVisibility = systemUiVisibility
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun hasCutout(context: Context? = null): Boolean {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
fun hasCutout(context: Activity?): CutoutSupport {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
return context?.getSystemService<DisplayManager>()
|
||||
?.getDisplay(Display.DEFAULT_DISPLAY)?.cutout != null
|
||||
}
|
||||
// TODO: Actually check for cutout
|
||||
return true
|
||||
}
|
||||
/*
|
||||
else if (isVivo && context != null) {
|
||||
if (context?.getSystemService<DisplayManager>()
|
||||
?.getDisplay(Display.DEFAULT_DISPLAY)?.cutout != null)
|
||||
return CutoutSupport.EXTENDED
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
val displayCutout = context?.window?.decorView?.rootWindowInsets?.displayCutout
|
||||
if (displayCutout?.safeInsetTop != null || displayCutout?.safeInsetBottom != null)
|
||||
return CutoutSupport.MODERN
|
||||
} else if (isVivo) {
|
||||
// https://swsdl.vivo.com.cn/appstore/developer/uploadfile/20180328/20180328152252602.pdf
|
||||
try {
|
||||
@SuppressLint("PrivateApi")
|
||||
val ftFeature = context.classLoader
|
||||
.loadClass("android.util.FtFeature")
|
||||
val isFeatureSupportMethod = ftFeature.getMethod(
|
||||
val ftFeature = context?.classLoader
|
||||
?.loadClass("android.util.FtFeature")
|
||||
val isFeatureSupportMethod = ftFeature?.getMethod(
|
||||
"isFeatureSupport",
|
||||
Int::class.javaPrimitiveType
|
||||
Int::class.javaPrimitiveType,
|
||||
)
|
||||
val isNotchOnScreen = 0x00000020
|
||||
return isFeatureSupportMethod.invoke(ftFeature, isNotchOnScreen) as Boolean
|
||||
val isSupported = isFeatureSupportMethod?.invoke(ftFeature, isNotchOnScreen) as Boolean
|
||||
if (isSupported) return CutoutSupport.LEGACY
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
} else if (isMiui) {
|
||||
try {
|
||||
@SuppressLint("PrivateApi")
|
||||
val sysProp = context?.classLoader?.loadClass("android.os.SystemProperties")
|
||||
val method = sysProp?.getMethod("getInt", String::class.java, Int::class.javaPrimitiveType)
|
||||
val rt = method?.invoke(sysProp, "ro.miui.notch", 0) as Int
|
||||
if (rt == 1) return CutoutSupport.LEGACY
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
}
|
||||
*/
|
||||
return false
|
||||
return CutoutSupport.NONE
|
||||
}
|
||||
|
||||
fun getCutoutHeight(context: Activity?, cutoutSupport: CutoutSupport): Number {
|
||||
return when (cutoutSupport) {
|
||||
CutoutSupport.MODERN -> {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P)
|
||||
throw IllegalStateException("Modern cutout only available on Android P or higher")
|
||||
context?.window?.decorView?.rootWindowInsets?.displayCutout?.safeInsetTop ?: 0
|
||||
}
|
||||
CutoutSupport.EXTENDED -> {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q)
|
||||
throw IllegalStateException("Extended cutout only available on Android Q or higher")
|
||||
context?.window?.decorView?.rootWindowInsets?.displayCutout?.boundingRectTop?.height()?.toFloat() ?: 0f
|
||||
}
|
||||
CutoutSupport.LEGACY -> {
|
||||
if (isVivo) {
|
||||
val insetCompat = context?.window?.decorView?.rootWindowInsets?.let {
|
||||
WindowInsetsCompat.toWindowInsetsCompat(it)
|
||||
}
|
||||
val statusBarHeight = insetCompat?.getInsets(WindowInsetsCompat.Type.statusBars())?.top
|
||||
?: 24.dpToPx // 24dp is "standard" height for Android since Marshmallow
|
||||
var notchHeight = 32.dpToPx
|
||||
if (notchHeight < statusBarHeight) {
|
||||
notchHeight = statusBarHeight
|
||||
}
|
||||
notchHeight
|
||||
} else if (isMiui) {
|
||||
val resourceId = context?.resources?.getIdentifier("notch_height",
|
||||
"dimen", "android") ?: 0
|
||||
if (resourceId > 0) {
|
||||
context?.resources?.getDimensionPixelSize(resourceId) ?: 0
|
||||
} else {
|
||||
0
|
||||
}
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
else -> 0
|
||||
}
|
||||
}
|
||||
|
||||
enum class CutoutSupport {
|
||||
NONE,
|
||||
LEGACY, // Pre-Android P, the start of this hell
|
||||
MODERN, // Android P
|
||||
EXTENDED, // Android Q
|
||||
}
|
||||
|
||||
enum class LegacyCutoutMode {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue