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:
parent
26b1331750
commit
4affedcf5a
5 changed files with 49 additions and 45 deletions
|
|
@ -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,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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].
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue