mirror of
https://github.com/mihonapp/mihon.git
synced 2025-12-10 07:22:25 +01:00
Handle reader cutout setting with Insets to support Android 15+ (#2640)
This commit is contained in:
@@ -19,7 +19,6 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co
|
|||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Increased default concurrent page downloads to 5 ([@AntsyLich](https://github.com/AntsyLich)) ([#2637](https://github.com/mihonapp/mihon/pull/2637))
|
- 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
|
### 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))
|
- Spoofing of `X-Requested-With` header to support newer WebView versions ([@Guzmazow](https://github.com/Guzmazow)) ([#2491](https://github.com/mihonapp/mihon/pull/2491))
|
||||||
|
|||||||
@@ -101,7 +101,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,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
package eu.kanade.presentation.reader.settings
|
package eu.kanade.presentation.reader.settings
|
||||||
|
|
||||||
|
import androidx.activity.compose.LocalActivity
|
||||||
import androidx.compose.foundation.layout.ColumnScope
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
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
|
||||||
@@ -67,10 +67,10 @@ internal fun ColumnScope.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(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,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.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
@@ -42,7 +41,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.lifecycleScope
|
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.setting.ReadingMode
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderProgressIndicator
|
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderProgressIndicator
|
||||||
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
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
import eu.kanade.tachiyomi.util.view.setComposeContent
|
import eu.kanade.tachiyomi.util.view.setComposeContent
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.drop
|
import kotlinx.coroutines.flow.drop
|
||||||
import kotlinx.coroutines.flow.filterNotNull
|
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.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
|
||||||
@@ -159,7 +156,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
|
||||||
}
|
}
|
||||||
@@ -252,7 +249,6 @@ class ReaderActivity : BaseActivity() {
|
|||||||
private fun ReaderActivityBinding.setComposeOverlay(): Unit = composeOverlay.setComposeContent {
|
private fun ReaderActivityBinding.setComposeOverlay(): Unit = composeOverlay.setComposeContent {
|
||||||
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,
|
||||||
@@ -268,7 +264,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(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -537,7 +533,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())
|
||||||
|
|
||||||
if (readerPreferences.showReadingMode().get()) {
|
if (readerPreferences.showReadingMode().get()) {
|
||||||
@@ -780,22 +776,28 @@ 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
|
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 ->
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -851,10 +853,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)
|
||||||
@@ -863,14 +861,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)
|
||||||
}
|
}
|
||||||
@@ -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].
|
* Sets the keep screen on mode according to [enabled].
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -31,7 +31,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)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user