Skip to content

Commit

Permalink
Refactor: Centralize Window Configuration and Enhance Fullscreen Hand…
Browse files Browse the repository at this point in the history
…ling

- Removed `WindowConfiguration` data class and its associated logic.
- Introduced `PlatformWindow` to manage window-related states, including `isUndecoratedFullscreen` and `isLandscape`.
- Centralized fullscreen state management in `PlatformWindow` across Android and desktop.
- Improved fullscreen detection and handling by leveraging `PlatformWindow`.
- Modified `WindowsWindowUtils` to use `isWindowsUndecoratedFullscreen`.
- Adjusted `WindowsWindowFrame` to utilize `isUndecoratedFullscreen`.
- Updated `EpisodePage` and `AniDesktop` to use `PlatformWindow` for fullscreen checks.
- Added landscape mode support to `PlatformWindow`.
- Updated UI components to use `PlatformWindow` properties.
- Enhanced Android's `PlatformWindow` to handle insets and configuration changes.
- Added `Platform` property in desktop's `PlatformWindow`.
- Updated preview composition to use `PlatformWindow`.
- Update `isSystemInFullscreen` and `isInLandscapeMode` to get state from `PlatformWindow`.
  • Loading branch information
Sanlorng committed Feb 19, 2025
1 parent 4cb4f16 commit 986e7b3
Show file tree
Hide file tree
Showing 17 changed files with 160 additions and 221 deletions.
14 changes: 10 additions & 4 deletions app/android/src/main/kotlin/activity/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import androidx.activity.SystemBarStyle
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.core.view.WindowCompat
Expand Down Expand Up @@ -114,15 +115,20 @@ class MainActivity : AniComponentActivity() {
}

val settingsRepository = KoinPlatform.getKoin().get<SettingsRepository>()

setContent {
AniApp {
SystemBarColorEffect()

val platformWindow = remember(this) {
PlatformWindow(this)
}
DisposableEffect(platformWindow) {
onDispose { platformWindow.close() }
}

CompositionLocalProvider(
LocalToaster provides toaster,
LocalPlatformWindow provides remember {
PlatformWindow()
},
LocalPlatformWindow provides platformWindow,
) {
val uiSettings by settingsRepository.uiSettings.flow.collectAsStateWithLifecycle(null)
uiSettings?.let {
Expand Down
6 changes: 5 additions & 1 deletion app/desktop/src/main/kotlin/AniDesktop.kt
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ import me.him188.ani.app.platform.window.setTitleBar
import me.him188.ani.app.tools.update.DesktopUpdateInstaller
import me.him188.ani.app.tools.update.UpdateInstaller
import me.him188.ani.app.torrent.anitorrent.AnitorrentLibraryLoader
import me.him188.ani.app.ui.foundation.LocalPlatform
import me.him188.ani.app.ui.foundation.LocalWindowState
import me.him188.ani.app.ui.foundation.effects.OverrideCaptionButtonAppearance
import me.him188.ani.app.ui.foundation.ifThen
Expand Down Expand Up @@ -396,13 +397,16 @@ object AniDesktop {
}

val systemTheme by systemThemeDetector.current.collectAsStateWithLifecycle()
val platform = LocalPlatform.current
CompositionLocalProvider(
LocalContext provides context,
LocalWindowState provides windowState,
LocalPlatformWindow provides remember(window.windowHandle) {
LocalPlatformWindow provides remember(window.windowHandle, this, platform, windowState) {
PlatformWindow(
windowHandle = window.windowHandle,
windowScope = this,
platform = platform,
windowState = windowState
)
},
LocalOnBackPressedDispatcherOwner provides backPressedDispatcherOwner,
Expand Down
20 changes: 8 additions & 12 deletions app/desktop/src/main/kotlin/window/WindowsWindowFrame.kt
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.AbsoluteAlignment
Expand Down Expand Up @@ -85,7 +84,6 @@ import me.him188.ani.app.ui.foundation.layout.LocalPlatformWindow
import me.him188.ani.app.ui.foundation.layout.LocalTitleBarInsets
import me.him188.ani.app.ui.foundation.layout.ZeroInsets
import me.him188.ani.app.ui.foundation.layout.isSystemInFullscreen
import me.him188.ani.app.ui.foundation.window.currentWindowConfiguration
import me.him188.ani.desktop.generated.resources.Res
import me.him188.ani.desktop.generated.resources.ic_fluent_arrow_minimize_28_regular
import me.him188.ani.desktop.generated.resources.ic_fluent_dismiss_48_regular
Expand All @@ -111,19 +109,17 @@ internal fun FrameWindowScope.WindowsWindowFrame(
val windowUtils = WindowsWindowUtils.instance
val platformWindow = LocalPlatformWindow.current
val scope = rememberCoroutineScope()
val windowConfiguration = currentWindowConfiguration()
val windowConfigurationState = rememberUpdatedState(windowConfiguration)

//Keep 1px for showing float window top area border.
val topBorderFixedInsets by remember(windowConfigurationState) {
val topBorderFixedInsets by remember(platformWindow) {
derivedStateOf {
if (!windowConfigurationState.value.isFullScreen) WindowInsets(top = 1) else ZeroInsets
if (!platformWindow.isUndecoratedFullscreen) WindowInsets(top = 1) else ZeroInsets
}
}
Box(modifier = Modifier.fillMaxSize().windowInsetsPadding(topBorderFixedInsets)) {
//Control the visibility of the title bar. initial value is !isFullScreen.
LaunchedEffect(windowConfiguration.isFullScreen) {
frameState.isTitleBarVisible = !windowConfiguration.isFullScreen
LaunchedEffect(platformWindow.isUndecoratedFullscreen) {
frameState.isTitleBarVisible = !platformWindow.isUndecoratedFullscreen
}

// Window content
Expand All @@ -135,10 +131,10 @@ internal fun FrameWindowScope.WindowsWindowFrame(
)

// Hide title bar if window is full screen mode and title bar is not hovered.
val titleBarInteractionSource = remember(windowConfiguration.isFullScreen) { MutableInteractionSource() }
val titleBarInteractionSource = remember(platformWindow.isUndecoratedFullscreen) { MutableInteractionSource() }
val titleBarHovered by titleBarInteractionSource.collectIsHoveredAsState()
LaunchedEffect(titleBarInteractionSource, titleBarHovered, windowConfiguration.isFullScreen) {
if (!titleBarHovered && windowConfiguration.isFullScreen) {
LaunchedEffect(titleBarInteractionSource, titleBarHovered, platformWindow.isUndecoratedFullscreen) {
if (!titleBarHovered && platformWindow.isUndecoratedFullscreen) {
delay(3.seconds)
frameState.isTitleBarVisible = false
}
Expand All @@ -149,7 +145,7 @@ internal fun FrameWindowScope.WindowsWindowFrame(
AnimatedVisibility(
visible = frameState.isTitleBarVisible,
modifier = Modifier
.ifThen(frameState.isTitleBarVisible && windowConfiguration.isFullScreen) { hoverable(titleBarInteractionSource) }
.ifThen(frameState.isTitleBarVisible && platformWindow.isUndecoratedFullscreen) { hoverable(titleBarInteractionSource) }
.fillMaxWidth()
.onSizeChanged(frameState::updateTitleBarInsets)
.wrapContentWidth(AbsoluteAlignment.Right),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,6 @@ import me.him188.ani.app.ui.foundation.pagerTabIndicatorOffset
import me.him188.ani.app.ui.foundation.rememberImageViewerHandler
import me.him188.ani.app.ui.foundation.theme.weaken
import me.him188.ani.app.ui.foundation.widgets.LocalToaster
import me.him188.ani.app.ui.foundation.window.currentWindowConfiguration
import me.him188.ani.app.ui.richtext.RichTextDefaults
import me.him188.ani.app.ui.subject.episode.comments.EpisodeCommentColumn
import me.him188.ani.app.ui.subject.episode.comments.EpisodeEditCommentSheet
Expand Down Expand Up @@ -228,7 +227,7 @@ private fun EpisodeScreenContent(

//Enable window fullscreen mode detection
if (LocalPlatform.current.isDesktop()) {
vm.isFullscreen = currentWindowConfiguration().isFullScreen
vm.isFullscreen = LocalPlatformWindow.current.isUndecoratedFullscreen
}

SideEffect {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,89 @@
package me.him188.ani.app.platform

import android.app.Activity
import android.content.ComponentCallbacks
import android.content.res.Configuration
import android.os.Build
import android.view.View
import android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat

/**
* PC 上的 Window. Android 上没有
*/
actual class PlatformWindow
actual class PlatformWindow(private val context: Context) : AutoCloseable {
private val activity = context.findActivity()
private val decorView = activity?.window?.decorView

private var _isLandscape: Boolean by mutableStateOf(isLandscape(context.resources.configuration))
actual val isLandscape: Boolean get() = _isLandscape

private var _isUndecoratedFullscreen: Boolean by mutableStateOf(isInFullscreenMode(context))
actual val isUndecoratedFullscreen: Boolean get() = _isUndecoratedFullscreen

private val insetListener = View.OnApplyWindowInsetsListener { _, insets ->
@Suppress("DEPRECATION")
val isFullscreenNow = when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> !insets.isVisible(android.view.WindowInsets.Type.systemBars())
else -> insets.systemWindowInsetTop == 0
}
if (isFullscreenNow != _isUndecoratedFullscreen) {
_isUndecoratedFullscreen = isFullscreenNow
}

insets
}

private val configurationListener = object : ComponentCallbacks {
override fun onLowMemory() {
}

override fun onConfigurationChanged(newConfig: Configuration) {
_isLandscape = isLandscape(newConfig)
}
}

init {

//register window inset listener
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
decorView?.setOnApplyWindowInsetsListener(insetListener)
} else if (decorView != null) {
ViewCompat.setOnApplyWindowInsetsListener(decorView) { v, insets ->
val toWindowInsets = insets.toWindowInsets()!!
insetListener.onApplyWindowInsets(v, toWindowInsets)
WindowInsetsCompat.toWindowInsetsCompat(toWindowInsets)
}
}

//register resource change listener
context.registerComponentCallbacks(configurationListener)
}

override fun close() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
decorView?.setOnApplyWindowInsetsListener(null)
} else if (decorView != null) {
ViewCompat.setOnApplyWindowInsetsListener(decorView, null)
}
context.unregisterComponentCallbacks(configurationListener)
}
}

@Suppress("DEPRECATION")
private fun isInFullscreenMode(context: Context): Boolean {
val window = (context as? Activity)?.window ?: return false
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val insetsController = window.insetsController
insetsController != null && insetsController.systemBarsBehavior == BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
} else {
val decorView = window.decorView
(decorView.systemUiVisibility and View.SYSTEM_UI_FLAG_FULLSCREEN) != 0
}
}

private fun isLandscape(configuration: Configuration) = configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,23 @@
package me.him188.ani.app.ui.foundation

import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.remember
import me.him188.ani.app.platform.LocalContext
import me.him188.ani.app.platform.PlatformWindow
import me.him188.ani.app.ui.foundation.layout.LocalPlatformWindow
import me.him188.ani.utils.platform.annotations.TestOnly

@Composable
@TestOnly
@PublishedApi
internal actual inline fun ProvidePlatformCompositionLocalsForPreview(crossinline content: @Composable () -> Unit) {
content()
val context = LocalContext.current
val platformWindow = remember(context) {
PlatformWindow(context)
}
CompositionLocalProvider(
LocalPlatformWindow provides platformWindow,
content = { content() }
)
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package me.him188.ani.app.platform

/**
* PC 上的 Window. Android 上没有
* 公共的 Window 抽象
* @property isUndecoratedFullscreen 当前窗口是否处于全屏模式
* @property isLandscape 当前窗口是否处于横屏模式,PC 上一定处于横屏模式,Android 从 configuration 里读
*/
expect class PlatformWindow
expect class PlatformWindow {
val isUndecoratedFullscreen: Boolean
val isLandscape: Boolean
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ package me.him188.ani.app.ui.foundation.layout

import androidx.compose.runtime.Composable
import me.him188.ani.app.platform.Context
import me.him188.ani.app.ui.foundation.window.currentWindowConfiguration


/**
Expand All @@ -29,4 +28,4 @@ expect fun Context.setSystemBarVisible(visible: Boolean)

@Suppress("NOTHING_TO_INLINE", "KotlinRedundantDiagnosticSuppress")
@Composable
inline fun isSystemInFullscreen(): Boolean = currentWindowConfiguration().isFullScreen
inline fun isSystemInFullscreen(): Boolean = LocalPlatformWindow.current.isUndecoratedFullscreen
Loading

0 comments on commit 986e7b3

Please sign in to comment.