Handle reader cutout setting with Insets to support Android 15+ (#2640)

(cherry picked from commit 0e0b6d92833f8e4f3aebdcc1f7c8c175084175d6)
This commit is contained in:
AntsyLich
2025-11-02 22:13:25 +05:45
committed by NGB-Was-Taken
parent d068559dee
commit 19c23943ec
5 changed files with 48 additions and 43 deletions
@@ -135,7 +135,7 @@ object SettingsReaderScreen : SearchableSettings {
title = stringResource(MR.strings.pref_fullscreen),
),
Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.cutoutShort(),
preference = readerPreferences.drawUnderCutout(),
title = stringResource(MR.strings.pref_cutout_short),
enabled = LocalView.current.hasDisplayCutout() && fullscreen,
),
@@ -1,12 +1,12 @@
package eu.kanade.presentation.reader.settings
import androidx.activity.compose.LocalActivity
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.material3.FilterChip
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.platform.LocalView
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
import eu.kanade.tachiyomi.util.system.hasDisplayCutout
@@ -88,10 +88,10 @@ internal fun ColumnScope.GeneralPage(screenModel: ReaderSettingsScreenModel) {
)
val isFullscreen by screenModel.preferences.fullscreen().collectAsState()
if (LocalView.current.hasDisplayCutout() && isFullscreen) {
if (LocalActivity.current?.hasDisplayCutout() == true && isFullscreen) {
CheckboxItem(
label = stringResource(MR.strings.pref_cutout_short),
pref = screenModel.preferences.cutoutShort(),
pref = screenModel.preferences.drawUnderCutout(),
)
}
@@ -20,7 +20,6 @@ import android.view.View
import android.view.View.LAYER_TYPE_HARDWARE
import android.view.WindowManager
import android.widget.Toast
import androidx.activity.SystemBarStyle
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.compose.foundation.layout.Arrangement
@@ -47,7 +46,6 @@ import androidx.core.graphics.Insets
import androidx.core.net.toUri
import androidx.core.transition.doOnEnd
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.lifecycle.Lifecycle
@@ -94,7 +92,6 @@ import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerViewer
import eu.kanade.tachiyomi.ui.reader.viewer.pager.VerticalPagerViewer
import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonViewer
import eu.kanade.tachiyomi.ui.webview.WebViewActivity
import eu.kanade.tachiyomi.util.system.hasDisplayCutout
import eu.kanade.tachiyomi.util.system.isNightMode
import eu.kanade.tachiyomi.util.system.openInBrowser
import eu.kanade.tachiyomi.util.system.toShareIntent
@@ -109,6 +106,7 @@ import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toImmutableSet
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.filterNotNull
@@ -205,7 +203,7 @@ class ReaderActivity : BaseActivity() {
overridePendingTransition(R.anim.shared_axis_x_push_enter, R.anim.shared_axis_x_push_exit)
}
enableEdgeToEdge(navigationBarStyle = SystemBarStyle.auto(Color.TRANSPARENT, Color.TRANSPARENT))
enableEdgeToEdge()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
window.isNavigationBarContrastEnforced = false
}
@@ -308,7 +306,6 @@ class ReaderActivity : BaseActivity() {
private fun ReaderActivityBinding.setComposeOverlay(): Unit = composeOverlay.setComposeContent {
val state by viewModel.state.collectAsState()
val showPageNumber by readerPreferences.showPageNumber().collectAsState()
val isFullscreen by readerPreferences.fullscreen().collectAsState()
val settingsScreenModel = remember {
ReaderSettingsScreenModel(
readerState = viewModel.state,
@@ -324,7 +321,7 @@ class ReaderActivity : BaseActivity() {
totalPages = state.totalPages,
modifier = Modifier
.align(Alignment.BottomCenter)
.then(if (isFullscreen) Modifier else Modifier.navigationBarsPadding()),
.navigationBarsPadding(),
)
}
@@ -872,7 +869,7 @@ class ReaderActivity : BaseActivity() {
binding.viewerContainer.removeAllViews()
}
viewModel.onViewerLoaded(newViewer)
updateViewerInset(readerPreferences.fullscreen().get())
updateViewerInset(readerPreferences.fullscreen().get(), readerPreferences.drawUnderCutout().get())
binding.viewerContainer.addView(newViewer.getView())
// SY -->
@@ -1193,22 +1190,28 @@ class ReaderActivity : BaseActivity() {
/**
* Updates viewer inset depending on fullscreen reader preferences.
*/
private fun updateViewerInset(fullscreen: Boolean) {
private fun updateViewerInset(fullscreen: Boolean, drawUnderCutout: Boolean) {
val view = viewModel.state.value.viewer?.getView() ?: return
view.applyInsetsPadding(ViewCompat.getRootWindowInsets(view), fullscreen)
view.applyInsetsPadding(ViewCompat.getRootWindowInsets(view), fullscreen, drawUnderCutout)
ViewCompat.setOnApplyWindowInsetsListener(view) { view, windowInsets ->
view.applyInsetsPadding(windowInsets, fullscreen)
view.applyInsetsPadding(windowInsets, fullscreen, drawUnderCutout)
windowInsets
}
}
private fun View.applyInsetsPadding(windowInsets: WindowInsetsCompat?, fullscreen: Boolean) {
val insets = if (!fullscreen) {
windowInsets?.getInsets(WindowInsetsCompat.Type.systemBars()) ?: Insets.NONE
} else {
Insets.NONE
private fun View.applyInsetsPadding(
windowInsets: WindowInsetsCompat?,
fullscreen: Boolean,
drawUnderCutout: Boolean,
) {
val insets = when {
!fullscreen -> windowInsets?.getInsets(WindowInsetsCompat.Type.systemBars())
!drawUnderCutout -> windowInsets?.getInsets(WindowInsetsCompat.Type.displayCutout())
else -> null
}
?: Insets.NONE
setPadding(insets.left, insets.top, insets.right, insets.bottom)
}
@@ -1264,10 +1267,6 @@ class ReaderActivity : BaseActivity() {
.onEach { setDisplayProfile(it) }
.launchIn(lifecycleScope)
readerPreferences.cutoutShort().changes()
.onEach(::setCutoutShort)
.launchIn(lifecycleScope)
readerPreferences.keepScreenOn().changes()
.onEach(::setKeepScreenOn)
.launchIn(lifecycleScope)
@@ -1276,14 +1275,21 @@ class ReaderActivity : BaseActivity() {
.onEach(::setCustomBrightness)
.launchIn(lifecycleScope)
merge(readerPreferences.grayscale().changes(), readerPreferences.invertedColors().changes())
.onEach { setLayerPaint(readerPreferences.grayscale().get(), readerPreferences.invertedColors().get()) }
combine(
readerPreferences.grayscale().changes(),
readerPreferences.invertedColors().changes(),
) { grayscale, invertedColors -> grayscale to invertedColors }
.onEach { (grayscale, invertedColors) ->
setLayerPaint(grayscale, invertedColors)
}
.launchIn(lifecycleScope)
readerPreferences.fullscreen().changes()
.onEach {
WindowCompat.setDecorFitsSystemWindows(window, !it)
updateViewerInset(it)
combine(
readerPreferences.fullscreen().changes(),
readerPreferences.drawUnderCutout().changes(),
) { fullscreen, drawUnderCutout -> fullscreen to drawUnderCutout }
.onEach { (fullscreen, drawUnderCutout) ->
updateViewerInset(fullscreen, drawUnderCutout)
}
.launchIn(lifecycleScope)
@@ -1351,16 +1357,6 @@ class ReaderActivity : BaseActivity() {
}
}
private fun setCutoutShort(enabled: Boolean) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P ) return
if (!window.decorView.hasDisplayCutout()) return
window.attributes.layoutInDisplayCutoutMode = when (enabled) {
true -> WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
false -> WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
}
}
/**
* Sets the keep screen on mode according to [enabled].
*/
@@ -37,7 +37,7 @@ class ReaderPreferences(
fun fullscreen() = preferenceStore.getBoolean("fullscreen", true)
fun cutoutShort() = preferenceStore.getBoolean("cutout_short", true)
fun drawUnderCutout() = preferenceStore.getBoolean("cutout_short", true)
fun keepScreenOn() = preferenceStore.getBoolean("pref_keep_screen_on_key", false)
@@ -1,5 +1,6 @@
package eu.kanade.tachiyomi.util.system
import android.app.Activity
import android.content.Context
import android.content.res.Configuration
import android.os.Build
@@ -57,11 +58,19 @@ fun Context.isNightMode(): Boolean {
/**
* Checks whether if the device has a display cutout (i.e. notch, camera cutout, etc.).
*
* Only relevant from Android 9 to Android 14.
* Only works on Android 9+.
*/
fun Activity.hasDisplayCutout(): Boolean {
return window.decorView.hasDisplayCutout()
}
/**
* Checks whether if the device has a display cutout (i.e. notch, camera cutout, etc.).
*
* Only works on Android 9+.
*/
fun View.hasDisplayCutout(): Boolean {
return Build.VERSION.SDK_INT in Build.VERSION_CODES.P..Build.VERSION_CODES.UPSIDE_DOWN_CAKE &&
rootWindowInsets?.displayCutout != null
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && rootWindowInsets?.displayCutout != null
}
/**