From 0e0b6d92833f8e4f3aebdcc1f7c8c175084175d6 Mon Sep 17 00:00:00 2001 From: AntsyLich <59261191+AntsyLich@users.noreply.github.com> Date: Sun, 2 Nov 2025 22:28:25 +0600 Subject: [PATCH] Handle reader cutout setting with Insets to support Android 15+ (#2640) --- CHANGELOG.md | 1 - .../settings/screen/SettingsReaderScreen.kt | 2 +- .../reader/settings/GeneralSettingsPage.kt | 6 +- .../tachiyomi/ui/reader/ReaderActivity.kt | 66 +++++++++---------- .../ui/reader/setting/ReaderPreferences.kt | 2 +- .../util/system/DisplayExtensions.kt | 15 ++++- 6 files changed, 48 insertions(+), 44 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e72ec66fc..e4a06b7c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,6 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co ### Changed - Increased default concurrent page downloads to 5 ([@AntsyLich](https://github.com/AntsyLich)) ([#2637](https://github.com/mihonapp/mihon/pull/2637)) -- Hide "Show content in cutout area" reader setting on Android 15+ as it's not supported ([@AntsyLich](https://github.com/AntsyLich)) ([#1908](https://github.com/mihonapp/mihon/pull/1908)) ### Improved - Spoofing of `X-Requested-With` header to support newer WebView versions ([@Guzmazow](https://github.com/Guzmazow)) ([#2491](https://github.com/mihonapp/mihon/pull/2491)) diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsReaderScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsReaderScreen.kt index 3ec853d5f..322bf107f 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsReaderScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsReaderScreen.kt @@ -101,7 +101,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, ), diff --git a/app/src/main/java/eu/kanade/presentation/reader/settings/GeneralSettingsPage.kt b/app/src/main/java/eu/kanade/presentation/reader/settings/GeneralSettingsPage.kt index c6da9fbc7..5baba4587 100644 --- a/app/src/main/java/eu/kanade/presentation/reader/settings/GeneralSettingsPage.kt +++ b/app/src/main/java/eu/kanade/presentation/reader/settings/GeneralSettingsPage.kt @@ -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 @@ -67,10 +67,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(), ) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt index c2039127a..cd246983a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt @@ -19,7 +19,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 @@ -42,7 +41,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.lifecycleScope @@ -79,18 +77,17 @@ import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode import eu.kanade.tachiyomi.ui.reader.viewer.ReaderProgressIndicator 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 import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.view.setComposeContent +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.drop import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.sample import kotlinx.coroutines.launch @@ -159,7 +156,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 } @@ -252,7 +249,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, @@ -268,7 +264,7 @@ class ReaderActivity : BaseActivity() { totalPages = state.totalPages, modifier = Modifier .align(Alignment.BottomCenter) - .then(if (isFullscreen) Modifier else Modifier.navigationBarsPadding()), + .navigationBarsPadding(), ) } @@ -537,7 +533,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()) if (readerPreferences.showReadingMode().get()) { @@ -780,22 +776,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) } @@ -851,10 +853,6 @@ class ReaderActivity : BaseActivity() { .onEach { setDisplayProfile(it) } .launchIn(lifecycleScope) - readerPreferences.cutoutShort().changes() - .onEach(::setCutoutShort) - .launchIn(lifecycleScope) - readerPreferences.keepScreenOn().changes() .onEach(::setKeepScreenOn) .launchIn(lifecycleScope) @@ -863,14 +861,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) } @@ -905,15 +910,6 @@ class ReaderActivity : BaseActivity() { } } - private fun setCutoutShort(enabled: Boolean) { - 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]. */ diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderPreferences.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderPreferences.kt index 13b42d0b0..c9072fc6b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderPreferences.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderPreferences.kt @@ -31,7 +31,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) 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 index fa5d6c2d8..141b65c0b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/system/DisplayExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/DisplayExtensions.kt @@ -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 } /**