From 4affedcf5a0e31cc2aaab2ae03d52f5bc9bfcfd6 Mon Sep 17 00:00:00 2001 From: AntsyLich <59261191+AntsyLich@users.noreply.github.com> Date: Fri, 9 Jan 2026 13:06:29 +0700 Subject: [PATCH] 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) --- .../settings/screen/SettingsReaderScreen.kt | 2 +- .../reader/settings/GeneralSettingsPage.kt | 6 +- .../tachiyomi/ui/reader/ReaderActivity.kt | 69 +++++++++---------- .../ui/reader/setting/ReaderPreferences.kt | 2 +- .../util/system/DisplayExtensions.kt | 15 +++- 5 files changed, 49 insertions(+), 45 deletions(-) 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 30f09d905..8deb1c9af 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 @@ -146,7 +146,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 65919768c..6a0b09c9f 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,11 +1,11 @@ package eu.kanade.presentation.reader.settings +import androidx.activity.compose.LocalActivity 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 @@ -87,10 +87,10 @@ internal fun 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 5d56888bd..3f3e943bb 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 @@ -22,7 +22,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.compose.rememberLauncherForActivityResult import androidx.activity.enableEdgeToEdge import androidx.activity.result.contract.ActivityResultContracts @@ -54,7 +53,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 @@ -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.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 @@ -127,7 +124,6 @@ import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapLatest -import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.sample 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) } - enableEdgeToEdge(navigationBarStyle = SystemBarStyle.auto(Color.TRANSPARENT, Color.TRANSPARENT)) + enableEdgeToEdge() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { window.isNavigationBarContrastEnforced = false } @@ -330,7 +326,6 @@ class ReaderActivity : BaseActivity() { // KMK <-- 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, @@ -348,7 +343,7 @@ class ReaderActivity : BaseActivity() { totalPages = state.totalPages, modifier = Modifier .align(Alignment.BottomCenter) - .then(if (isFullscreen) Modifier else Modifier.navigationBarsPadding()), + .navigationBarsPadding(), ) } @@ -968,7 +963,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 --> @@ -1294,22 +1289,29 @@ class ReaderActivity : BaseActivity() { /** * Updates viewer inset depending on fullscreen reader preferences. */ - private fun updateViewerInset(fullscreen: Boolean) { - val view = viewModel.state.value.viewer?.getView() ?: return + private fun updateViewerInset(fullscreen: Boolean, drawUnderCutout: Boolean) { + 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 -> - 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) } @@ -1365,10 +1367,6 @@ class ReaderActivity : BaseActivity() { .onEach { setDisplayProfile(it) } .launchIn(lifecycleScope) - readerPreferences.cutoutShort().changes() - .onEach(::setCutoutShort) - .launchIn(lifecycleScope) - readerPreferences.keepScreenOn().changes() .onEach(::setKeepScreenOn) .launchIn(lifecycleScope) @@ -1377,14 +1375,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) @@ -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]. */ 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 7871107b6..2d6fd4025 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 @@ -39,7 +39,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 } /**