From 0295c40b024c9d7ed6d1c9a088cac2aa6fa253e3 Mon Sep 17 00:00:00 2001 From: FooIbar <118464521+FooIbar@users.noreply.github.com> Date: Mon, 6 May 2024 15:03:05 +0800 Subject: [PATCH] Implement fullscreen mode in new reader --- .../systemuicontroller/SystemUiController.kt | 152 ++++++++++++++++++ .../hippo/ehviewer/ui/reader/GalleryPager.kt | 11 +- .../hippo/ehviewer/ui/reader/ReaderScreen.kt | 34 ++++ 3 files changed, 191 insertions(+), 6 deletions(-) create mode 100644 app/src/main/kotlin/com/google/accompanist/systemuicontroller/SystemUiController.kt diff --git a/app/src/main/kotlin/com/google/accompanist/systemuicontroller/SystemUiController.kt b/app/src/main/kotlin/com/google/accompanist/systemuicontroller/SystemUiController.kt new file mode 100644 index 000000000..a737d4a60 --- /dev/null +++ b/app/src/main/kotlin/com/google/accompanist/systemuicontroller/SystemUiController.kt @@ -0,0 +1,152 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.accompanist.systemuicontroller + +import android.app.Activity +import android.content.Context +import android.content.ContextWrapper +import android.view.View +import android.view.Window +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.window.DialogWindowProvider +import androidx.core.view.ViewCompat +import androidx.core.view.WindowCompat +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.WindowInsetsControllerCompat + +/** + * A class which provides easy-to-use utilities for updating the System UI bar + * colors within Jetpack Compose. + */ +@Stable +interface SystemUiController { + + /** + * Control for the behavior of the system bars. This value should be one of the + * [WindowInsetsControllerCompat] behavior constants: + * [WindowInsetsControllerCompat.BEHAVIOR_SHOW_BARS_BY_TOUCH] (Deprecated), + * [WindowInsetsControllerCompat.BEHAVIOR_DEFAULT] and + * [WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE]. + */ + var systemBarsBehavior: Int + + /** + * Property which holds the status bar visibility. If set to true, show the status bar, + * otherwise hide the status bar. + */ + var isStatusBarVisible: Boolean + + /** + * Property which holds the navigation bar visibility. If set to true, show the navigation bar, + * otherwise hide the navigation bar. + */ + var isNavigationBarVisible: Boolean + + /** + * Property which holds the status & navigation bar visibility. If set to true, show both bars, + * otherwise hide both bars. + */ + var isSystemBarsVisible: Boolean + get() = isNavigationBarVisible && isStatusBarVisible + set(value) { + isStatusBarVisible = value + isNavigationBarVisible = value + } +} + +/** + * Remembers a [SystemUiController] for the given [window]. + * + * If no [window] is provided, an attempt to find the correct [Window] is made. + * + * First, if the [LocalView]'s parent is a [DialogWindowProvider], then that dialog's [Window] will + * be used. + * + * Second, we attempt to find [Window] for the [Activity] containing the [LocalView]. + * + * If none of these are found (such as may happen in a preview), then the functionality of the + * returned [SystemUiController] will be degraded, but won't throw an exception. + */ +@Composable +fun rememberSystemUiController( + window: Window? = findWindow(), +): SystemUiController { + val view = LocalView.current + return remember(view, window) { AndroidSystemUiController(view, window) } +} + +@Composable +private fun findWindow(): Window? = + (LocalView.current.parent as? DialogWindowProvider)?.window + ?: LocalView.current.context.findWindow() + +private tailrec fun Context.findWindow(): Window? = + when (this) { + is Activity -> window + is ContextWrapper -> baseContext.findWindow() + else -> null + } + +/** + * A helper class for setting the navigation and status bar colors for a [View], gracefully + * degrading behavior based upon API level. + * + * Typically you would use [rememberSystemUiController] to remember an instance of this. + */ +internal class AndroidSystemUiController( + private val view: View, + window: Window?, +) : SystemUiController { + private val windowInsetsController = window?.let { + WindowCompat.getInsetsController(it, view) + } + + override var systemBarsBehavior: Int + get() = windowInsetsController?.systemBarsBehavior ?: 0 + set(value) { + windowInsetsController?.systemBarsBehavior = value + } + + override var isStatusBarVisible: Boolean + get() { + return ViewCompat.getRootWindowInsets(view) + ?.isVisible(WindowInsetsCompat.Type.statusBars()) == true + } + set(value) { + if (value) { + windowInsetsController?.show(WindowInsetsCompat.Type.statusBars()) + } else { + windowInsetsController?.hide(WindowInsetsCompat.Type.statusBars()) + } + } + + override var isNavigationBarVisible: Boolean + get() { + return ViewCompat.getRootWindowInsets(view) + ?.isVisible(WindowInsetsCompat.Type.navigationBars()) == true + } + set(value) { + if (value) { + windowInsetsController?.show(WindowInsetsCompat.Type.navigationBars()) + } else { + windowInsetsController?.hide(WindowInsetsCompat.Type.navigationBars()) + } + } +} diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/reader/GalleryPager.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/reader/GalleryPager.kt index 1fdca7a4b..1184a9c1c 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/reader/GalleryPager.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/reader/GalleryPager.kt @@ -3,10 +3,8 @@ package com.hippo.ehviewer.ui.reader import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.asPaddingValues +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.items @@ -42,6 +40,7 @@ fun GalleryPager( onSelectPage: (ReaderPage) -> Unit, onMenuRegionClick: () -> Unit, modifier: Modifier = Modifier, + contentPadding: PaddingValues = PaddingValues(0.dp), ) { val items = pageLoader.pages when (type) { @@ -49,7 +48,7 @@ fun GalleryPager( HorizontalPager( state = pagerState, modifier = modifier, - contentPadding = WindowInsets.systemBars.asPaddingValues(), + contentPadding = contentPadding, reverseLayout = type == RIGHT_TO_LEFT, key = { it }, ) { index -> @@ -86,7 +85,7 @@ fun GalleryPager( VerticalPager( state = pagerState, modifier = modifier, - contentPadding = WindowInsets.systemBars.asPaddingValues(), + contentPadding = contentPadding, key = { it }, ) { index -> val page = items[index] @@ -137,7 +136,7 @@ fun GalleryPager( }, ), state = lazyListState, - contentPadding = WindowInsets.systemBars.asPaddingValues(), + contentPadding = contentPadding, verticalArrangement = Arrangement.spacedBy(if (type != WEBTOON) 15.dp else 0.dp), ) { items(items, key = { it.index }) { page -> diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/reader/ReaderScreen.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/reader/ReaderScreen.kt index 6a5e6c5cb..aeeecee16 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/reader/ReaderScreen.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/reader/ReaderScreen.kt @@ -4,12 +4,16 @@ import androidx.compose.animation.animateColorAsState import androidx.compose.foundation.background import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides +import androidx.compose.foundation.layout.asPaddingValues +import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.safeDrawing +import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.pager.rememberPagerState @@ -27,11 +31,15 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.colorResource +import androidx.compose.ui.unit.dp +import androidx.core.view.WindowInsetsControllerCompat +import com.google.accompanist.systemuicontroller.rememberSystemUiController import com.hippo.ehviewer.R import com.hippo.ehviewer.Settings import com.hippo.ehviewer.client.data.BaseGalleryInfo @@ -79,6 +87,16 @@ fun ReaderScreen(info: BaseGalleryInfo, page: Int = -1, navigator: DestinationsN val showSeekbar by Settings.showReaderSeekbar.collectAsState() val readingMode by Settings.readingMode.collectAsState { ReadingModeType.fromPreference(it) } val volumeKeysEnabled by Settings.readWithVolumeKeys.collectAsState() + val fullscreen by Settings.fullscreen.collectAsState() + val cutoutShort by Settings.cutoutShort.collectAsState() + val uiController = rememberSystemUiController() + DisposableEffect(uiController) { + uiController.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE + onDispose { + uiController.isSystemBarsVisible = true + uiController.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_DEFAULT + } + } Deferred({ pageLoader.awaitReady() }) { val lazyListState = rememberLazyListState() val pagerState = rememberPagerState { pageLoader.size } @@ -87,6 +105,13 @@ fun ReaderScreen(info: BaseGalleryInfo, page: Int = -1, navigator: DestinationsN Box { var appbarVisible by remember { mutableStateOf(false) } val bgColor by collectBackgroundColorAsState() + if (fullscreen) { + LaunchedEffect(Unit) { + snapshotFlow { appbarVisible }.collect { + uiController.isSystemBarsVisible = it + } + } + } VolumeKeysHandler( enabled = { volumeKeysEnabled && !appbarVisible }, movePrevious = { syncState.sliderScrollTo(syncState.sliderValue - 1) }, @@ -125,6 +150,15 @@ fun ReaderScreen(info: BaseGalleryInfo, page: Int = -1, navigator: DestinationsN }, onMenuRegionClick = { appbarVisible = !appbarVisible }, modifier = Modifier.background(bgColor), + contentPadding = if (fullscreen) { + if (cutoutShort) { + PaddingValues(0.dp) + } else { + WindowInsets.displayCutout.asPaddingValues() + } + } else { + WindowInsets.systemBars.asPaddingValues() + }, ) val brightness by Settings.customBrightness.collectAsState() val brightnessValue by Settings.customBrightnessValue.collectAsState()