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

(cherry picked from commit 0e0b6d92833f8e4f3aebdcc1f7c8c175084175d6)

Fix extra padding appearing in reader after user interactions (mihonapp/mihon#2669)

(cherry picked from commit aa300cb53ea3a02b63c3b3f3fca60d5e7533a8f1)
This commit is contained in:
AntsyLich 2026-01-09 13:06:29 +07:00 committed by Cuong-Tran
parent 26b1331750
commit 4affedcf5a
5 changed files with 49 additions and 45 deletions

View file

@ -146,7 +146,7 @@ object SettingsReaderScreen : SearchableSettings {
title = stringResource(MR.strings.pref_fullscreen), title = stringResource(MR.strings.pref_fullscreen),
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.cutoutShort(), preference = readerPreferences.drawUnderCutout(),
title = stringResource(MR.strings.pref_cutout_short), title = stringResource(MR.strings.pref_cutout_short),
enabled = LocalView.current.hasDisplayCutout() && fullscreen, enabled = LocalView.current.hasDisplayCutout() && fullscreen,
), ),

View file

@ -1,11 +1,11 @@
package eu.kanade.presentation.reader.settings package eu.kanade.presentation.reader.settings
import androidx.activity.compose.LocalActivity
import androidx.compose.material3.FilterChip import androidx.compose.material3.FilterChip
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue 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.ReaderPreferences
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
import eu.kanade.tachiyomi.util.system.hasDisplayCutout import eu.kanade.tachiyomi.util.system.hasDisplayCutout
@ -87,10 +87,10 @@ internal fun GeneralPage(screenModel: ReaderSettingsScreenModel) {
) )
val isFullscreen by screenModel.preferences.fullscreen().collectAsState() val isFullscreen by screenModel.preferences.fullscreen().collectAsState()
if (LocalView.current.hasDisplayCutout() && isFullscreen) { if (LocalActivity.current?.hasDisplayCutout() == true && isFullscreen) {
CheckboxItem( CheckboxItem(
label = stringResource(MR.strings.pref_cutout_short), label = stringResource(MR.strings.pref_cutout_short),
pref = screenModel.preferences.cutoutShort(), pref = screenModel.preferences.drawUnderCutout(),
) )
} }

View file

@ -22,7 +22,6 @@ import android.view.View
import android.view.View.LAYER_TYPE_HARDWARE import android.view.View.LAYER_TYPE_HARDWARE
import android.view.WindowManager import android.view.WindowManager
import android.widget.Toast import android.widget.Toast
import androidx.activity.SystemBarStyle
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
@ -54,7 +53,6 @@ import androidx.core.graphics.Insets
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.core.transition.doOnEnd import androidx.core.transition.doOnEnd
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat import androidx.core.view.WindowInsetsControllerCompat
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
@ -107,7 +105,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.pager.VerticalPagerViewer
import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonViewer import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonViewer
import eu.kanade.tachiyomi.ui.webview.WebViewActivity 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.isNightMode
import eu.kanade.tachiyomi.util.system.openInBrowser import eu.kanade.tachiyomi.util.system.openInBrowser
import eu.kanade.tachiyomi.util.system.toShareIntent import eu.kanade.tachiyomi.util.system.toShareIntent
@ -127,7 +124,6 @@ import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.sample import kotlinx.coroutines.flow.sample
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -221,7 +217,7 @@ class ReaderActivity : BaseActivity() {
overridePendingTransition(R.anim.shared_axis_x_push_enter, R.anim.shared_axis_x_push_exit) 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) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
window.isNavigationBarContrastEnforced = false window.isNavigationBarContrastEnforced = false
} }
@ -330,7 +326,6 @@ class ReaderActivity : BaseActivity() {
// KMK <-- // KMK <--
val state by viewModel.state.collectAsState() val state by viewModel.state.collectAsState()
val showPageNumber by readerPreferences.showPageNumber().collectAsState() val showPageNumber by readerPreferences.showPageNumber().collectAsState()
val isFullscreen by readerPreferences.fullscreen().collectAsState()
val settingsScreenModel = remember { val settingsScreenModel = remember {
ReaderSettingsScreenModel( ReaderSettingsScreenModel(
readerState = viewModel.state, readerState = viewModel.state,
@ -348,7 +343,7 @@ class ReaderActivity : BaseActivity() {
totalPages = state.totalPages, totalPages = state.totalPages,
modifier = Modifier modifier = Modifier
.align(Alignment.BottomCenter) .align(Alignment.BottomCenter)
.then(if (isFullscreen) Modifier else Modifier.navigationBarsPadding()), .navigationBarsPadding(),
) )
} }
@ -968,7 +963,7 @@ class ReaderActivity : BaseActivity() {
binding.viewerContainer.removeAllViews() binding.viewerContainer.removeAllViews()
} }
viewModel.onViewerLoaded(newViewer) viewModel.onViewerLoaded(newViewer)
updateViewerInset(readerPreferences.fullscreen().get()) updateViewerInset(readerPreferences.fullscreen().get(), readerPreferences.drawUnderCutout().get())
binding.viewerContainer.addView(newViewer.getView()) binding.viewerContainer.addView(newViewer.getView())
// SY --> // SY -->
@ -1294,22 +1289,29 @@ class ReaderActivity : BaseActivity() {
/** /**
* Updates viewer inset depending on fullscreen reader preferences. * 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 if (!::binding.isInitialized) return
val view = binding.viewerContainer
view.applyInsetsPadding(ViewCompat.getRootWindowInsets(view), fullscreen) view.applyInsetsPadding(ViewCompat.getRootWindowInsets(view), fullscreen, drawUnderCutout)
ViewCompat.setOnApplyWindowInsetsListener(view) { view, windowInsets -> ViewCompat.setOnApplyWindowInsetsListener(view) { view, windowInsets ->
view.applyInsetsPadding(windowInsets, fullscreen) view.applyInsetsPadding(windowInsets, fullscreen, drawUnderCutout)
windowInsets windowInsets
} }
} }
private fun View.applyInsetsPadding(windowInsets: WindowInsetsCompat?, fullscreen: Boolean) { private fun View.applyInsetsPadding(
val insets = if (!fullscreen) { windowInsets: WindowInsetsCompat?,
windowInsets?.getInsets(WindowInsetsCompat.Type.systemBars()) ?: Insets.NONE fullscreen: Boolean,
} else { drawUnderCutout: Boolean,
Insets.NONE ) {
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) setPadding(insets.left, insets.top, insets.right, insets.bottom)
} }
@ -1365,10 +1367,6 @@ class ReaderActivity : BaseActivity() {
.onEach { setDisplayProfile(it) } .onEach { setDisplayProfile(it) }
.launchIn(lifecycleScope) .launchIn(lifecycleScope)
readerPreferences.cutoutShort().changes()
.onEach(::setCutoutShort)
.launchIn(lifecycleScope)
readerPreferences.keepScreenOn().changes() readerPreferences.keepScreenOn().changes()
.onEach(::setKeepScreenOn) .onEach(::setKeepScreenOn)
.launchIn(lifecycleScope) .launchIn(lifecycleScope)
@ -1377,14 +1375,21 @@ class ReaderActivity : BaseActivity() {
.onEach(::setCustomBrightness) .onEach(::setCustomBrightness)
.launchIn(lifecycleScope) .launchIn(lifecycleScope)
merge(readerPreferences.grayscale().changes(), readerPreferences.invertedColors().changes()) combine(
.onEach { setLayerPaint(readerPreferences.grayscale().get(), readerPreferences.invertedColors().get()) } readerPreferences.grayscale().changes(),
readerPreferences.invertedColors().changes(),
) { grayscale, invertedColors -> grayscale to invertedColors }
.onEach { (grayscale, invertedColors) ->
setLayerPaint(grayscale, invertedColors)
}
.launchIn(lifecycleScope) .launchIn(lifecycleScope)
readerPreferences.fullscreen().changes() combine(
.onEach { readerPreferences.fullscreen().changes(),
WindowCompat.setDecorFitsSystemWindows(window, !it) readerPreferences.drawUnderCutout().changes(),
updateViewerInset(it) ) { fullscreen, drawUnderCutout -> fullscreen to drawUnderCutout }
.onEach { (fullscreen, drawUnderCutout) ->
updateViewerInset(fullscreen, drawUnderCutout)
} }
.launchIn(lifecycleScope) .launchIn(lifecycleScope)
@ -1451,16 +1456,6 @@ class ReaderActivity : BaseActivity() {
} }
} }
private fun setCutoutShort(enabled: Boolean) {
if (!window.decorView.hasDisplayCutout()) return
@SuppressLint("NewApi")
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]. * Sets the keep screen on mode according to [enabled].
*/ */

View file

@ -39,7 +39,7 @@ class ReaderPreferences(
fun fullscreen() = preferenceStore.getBoolean("fullscreen", true) 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) fun keepScreenOn() = preferenceStore.getBoolean("pref_keep_screen_on_key", false)

View file

@ -1,5 +1,6 @@
package eu.kanade.tachiyomi.util.system package eu.kanade.tachiyomi.util.system
import android.app.Activity
import android.content.Context import android.content.Context
import android.content.res.Configuration import android.content.res.Configuration
import android.os.Build 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.). * 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 { fun View.hasDisplayCutout(): Boolean {
return Build.VERSION.SDK_INT in Build.VERSION_CODES.P..Build.VERSION_CODES.UPSIDE_DOWN_CAKE && return Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && rootWindowInsets?.displayCutout != null
rootWindowInsets?.displayCutout != null
} }
/** /**