diff --git a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/icon/OxygenIcons.kt b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/icon/OxygenIcons.kt
index 4e2bef7..7e22b91 100644
--- a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/icon/OxygenIcons.kt
+++ b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/icon/OxygenIcons.kt
@@ -10,6 +10,8 @@ import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Code
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Download
+import androidx.compose.material.icons.filled.Fullscreen
+import androidx.compose.material.icons.filled.FullscreenExit
import androidx.compose.material.icons.filled.Inbox
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.filled.Reorder
@@ -43,6 +45,8 @@ object OxygenIcons {
val Delete = Icons.Default.Delete
val Download = Icons.Default.Download
val Error = Icons.Default.Cancel
+ val FullScreen = Icons.Default.Fullscreen
+ val FullScreenExit = Icons.Default.FullscreenExit
val Home = Icons.Rounded.Home
val HomeBorder = Icons.Outlined.Home
val Info = Icons.Outlined.Info
diff --git a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/navigation/OxygenNavHost.kt b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/navigation/OxygenNavHost.kt
index f69e8d6..2afc63c 100644
--- a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/navigation/OxygenNavHost.kt
+++ b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/navigation/OxygenNavHost.kt
@@ -1,9 +1,14 @@
package top.fatweb.oxygen.toolbox.navigation
+import android.os.Bundle
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
+import androidx.navigation.NavController
+import androidx.navigation.NavDestination
import androidx.navigation.compose.NavHost
import top.fatweb.oxygen.toolbox.ui.OxygenAppState
+import top.fatweb.oxygen.toolbox.ui.util.LocalFullScreen
@Composable
fun OxygenNavHost(
@@ -16,6 +21,21 @@ fun OxygenNavHost(
onShowSnackbar: suspend (message: String, action: String?) -> Boolean
) {
val navController = appState.navController
+ val fullScreen = LocalFullScreen.current
+
+ LaunchedEffect(navController) {
+ navController.addOnDestinationChangedListener(object :
+ NavController.OnDestinationChangedListener {
+ override fun onDestinationChanged(
+ controller: NavController,
+ destination: NavDestination,
+ arguments: Bundle?
+ ) {
+ fullScreen.onStateChange.invoke(false)
+ }
+ })
+ }
+
NavHost(
modifier = modifier,
navController = navController,
diff --git a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/OxygenApp.kt b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/OxygenApp.kt
index 9cd1d25..d22e117 100644
--- a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/OxygenApp.kt
+++ b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/OxygenApp.kt
@@ -1,5 +1,6 @@
package top.fatweb.oxygen.toolbox.ui
+import androidx.activity.ComponentActivity
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@@ -7,7 +8,6 @@ import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing
@@ -24,6 +24,7 @@ import androidx.compose.material3.SnackbarResult
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
@@ -34,9 +35,11 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
-import androidx.compose.ui.unit.dp
+import androidx.core.view.WindowInsetsCompat
+import androidx.core.view.WindowInsetsControllerCompat
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavDestination
import androidx.navigation.NavDestination.Companion.hierarchy
@@ -59,6 +62,8 @@ import top.fatweb.oxygen.toolbox.ui.component.SearchButtonPosition
import top.fatweb.oxygen.toolbox.ui.settings.SettingsDialog
import top.fatweb.oxygen.toolbox.ui.theme.GradientColors
import top.fatweb.oxygen.toolbox.ui.theme.LocalGradientColors
+import top.fatweb.oxygen.toolbox.ui.util.FullScreen
+import top.fatweb.oxygen.toolbox.ui.util.LocalFullScreen
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@@ -69,159 +74,186 @@ fun OxygenApp(appState: OxygenAppState) {
mutableStateOf(false)
}
- OxygenBackground {
- OxygenGradientBackground(
- gradientColors = if (shouldShowGradientBackground) LocalGradientColors.current else GradientColors()
- ) {
- val destination = appState.currentTopLevelDestination
+ val context = LocalContext.current
+ val window = (context as ComponentActivity).window
+ val windowInsetsController = WindowInsetsControllerCompat(window, window.decorView)
+ var isFullScreen by remember { mutableStateOf(false) }
- val snackbarHostState = remember { SnackbarHostState() }
+ val fullScreen = FullScreen(
+ enable = isFullScreen,
+ onStateChange = {
+ isFullScreen = it
+ }
+ )
- val isOffline by appState.isOffline.collectAsStateWithLifecycle()
+ LaunchedEffect(Unit) {
+ windowInsetsController.systemBarsBehavior =
+ WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
+ }
- val noConnectMessage = stringResource(R.string.core_no_connect)
+ LaunchedEffect(isFullScreen) {
+ if (isFullScreen) {
+ windowInsetsController.hide(WindowInsetsCompat.Type.systemBars())
+ } else {
+ windowInsetsController.show(WindowInsetsCompat.Type.systemBars())
+ }
+ }
- var canScroll by remember { mutableStateOf(true) }
- val topAppBarScrollBehavior =
- if (canScroll) TopAppBarDefaults.enterAlwaysScrollBehavior() else TopAppBarDefaults.pinnedScrollBehavior()
+ CompositionLocalProvider(LocalFullScreen provides fullScreen) {
+ OxygenBackground {
+ OxygenGradientBackground(
+ gradientColors = if (shouldShowGradientBackground) LocalGradientColors.current else GradientColors()
+ ) {
+ val destination = appState.currentTopLevelDestination
- var activeSearch by remember { mutableStateOf(false) }
- var searchValue by remember { mutableStateOf("") }
- var searchCount by remember { mutableIntStateOf(0) }
+ val snackbarHostState = remember { SnackbarHostState() }
- LaunchedEffect(activeSearch) {
- canScroll = !activeSearch
- }
+ val isOffline by appState.isOffline.collectAsStateWithLifecycle()
- LaunchedEffect(destination) {
- activeSearch = false
- searchValue = ""
- if (searchCount == 0) {
- searchCount++
- } else {
- searchCount = 0
+ val noConnectMessage = stringResource(R.string.core_no_connect)
+
+ var canScroll by remember { mutableStateOf(true) }
+ val topAppBarScrollBehavior =
+ if (canScroll) TopAppBarDefaults.enterAlwaysScrollBehavior() else TopAppBarDefaults.pinnedScrollBehavior()
+
+ var activeSearch by remember { mutableStateOf(false) }
+ var searchValue by remember { mutableStateOf("") }
+ var searchCount by remember { mutableIntStateOf(0) }
+
+ LaunchedEffect(activeSearch) {
+ canScroll = !activeSearch
}
- }
- LaunchedEffect(isOffline) {
- if (isOffline) {
- snackbarHostState.showSnackbar(
- message = noConnectMessage,
- duration = SnackbarDuration.Indefinite
+ LaunchedEffect(destination) {
+ activeSearch = false
+ searchValue = ""
+ if (searchCount == 0) {
+ searchCount++
+ } else {
+ searchCount = 0
+ }
+ }
+
+ LaunchedEffect(isOffline) {
+ if (isOffline) {
+ snackbarHostState.showSnackbar(
+ message = noConnectMessage,
+ duration = SnackbarDuration.Indefinite
+ )
+ }
+ }
+
+ if (showSettingsDialog) {
+ SettingsDialog(
+ onDismiss = { showSettingsDialog = false },
+ onNavigateToLibraries = appState::navigateToLibraries,
+ onNavigateToAbout = appState::navigateToAbout
)
}
- }
- if (showSettingsDialog) {
- SettingsDialog(
- onDismiss = { showSettingsDialog = false },
- onNavigateToLibraries = appState::navigateToLibraries,
- onNavigateToAbout = appState::navigateToAbout
- )
- }
-
- Scaffold(
- modifier = Modifier
- .nestedScroll(connection = topAppBarScrollBehavior.nestedScrollConnection),
- containerColor = Color.Transparent,
- contentColor = MaterialTheme.colorScheme.onBackground,
- contentWindowInsets = WindowInsets(left = 0, top = 0, right = 0, bottom = 0),
- snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
- bottomBar = {
- AnimatedVisibility(
- visible = appState.shouldShowBottomBar && destination != null
- ) {
- OxygenBottomBar(
- destinations = appState.topLevelDestinations,
- currentDestination = appState.currentDestination,
- onNavigateToDestination = appState::navigateToTopLevelDestination
- )
- }
- }
- ) { padding ->
- Row(
- Modifier
- .fillMaxSize()
- .padding(padding)
- .consumeWindowInsets(padding)
- .windowInsetsPadding(
- WindowInsets.safeDrawing.only(
- WindowInsetsSides.Horizontal
+ Scaffold(
+ modifier = Modifier
+ .nestedScroll(connection = topAppBarScrollBehavior.nestedScrollConnection),
+ containerColor = Color.Transparent,
+ contentColor = MaterialTheme.colorScheme.onBackground,
+ contentWindowInsets = WindowInsets(left = 0, top = 0, right = 0, bottom = 0),
+ snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
+ bottomBar = {
+ AnimatedVisibility(
+ visible = appState.shouldShowBottomBar && destination != null
+ ) {
+ OxygenBottomBar(
+ destinations = appState.topLevelDestinations,
+ currentDestination = appState.currentDestination,
+ onNavigateToDestination = appState::navigateToTopLevelDestination
)
- )
- ) {
- AnimatedVisibility(
- visible = appState.shouldShowNavRail && destination != null
- ) {
- OxygenNavRail(
- modifier = Modifier.safeDrawingPadding(),
- destinations = appState.topLevelDestinations,
- currentDestination = appState.currentDestination,
- onNavigateToDestination = appState::navigateToTopLevelDestination
- )
+ }
}
-
- Column(
- Modifier.fillMaxSize()
+ ) { padding ->
+ Row(
+ Modifier
+ .fillMaxSize()
+ .padding(padding)
+ .consumeWindowInsets(padding)
+ .windowInsetsPadding(
+ WindowInsets.safeDrawing.only(
+ WindowInsetsSides.Horizontal
+ )
+ )
) {
AnimatedVisibility(
- visible = destination != null
+ visible = appState.shouldShowNavRail && destination != null
) {
- OxygenTopAppBar(
- scrollBehavior = topAppBarScrollBehavior,
- title = {
- destination?.let {
- Text(
- text = stringResource(destination.titleTextId),
- maxLines = 1,
- overflow = TextOverflow.Ellipsis
- )
- }
- },
- navigationIcon = OxygenIcons.Search,
- navigationIconContentDescription = stringResource(R.string.feature_settings_top_app_bar_navigation_icon_description),
- actionIcon = OxygenIcons.MoreVert,
- actionIconContentDescription = stringResource(R.string.feature_settings_top_app_bar_action_icon_description),
- activeSearch = activeSearch,
- searchButtonPosition = SearchButtonPosition.Navigation,
- query = searchValue,
- colors = TopAppBarDefaults.centerAlignedTopAppBarColors(
- containerColor = Color.Transparent,
- scrolledContainerColor = Color.Transparent
- ),
- onNavigationClick = { activeSearch = true },
- onActionClick = { showSettingsDialog = true },
- onQueryChange = {
- searchValue = it
- },
- onSearch = {
- searchCount++
- },
- onCancelSearch = {
- searchValue = ""
- activeSearch = false
- searchCount = 0
- }
+ OxygenNavRail(
+ modifier = Modifier.safeDrawingPadding(),
+ destinations = appState.topLevelDestinations,
+ currentDestination = appState.currentDestination,
+ onNavigateToDestination = appState::navigateToTopLevelDestination
)
}
- OxygenNavHost(
- appState = appState,
- startDestination = when (appState.launchPageConfig) {
- LaunchPageConfig.Tools -> TOOLS_ROUTE
- LaunchPageConfig.Star -> STAR_ROUTE
- },
- isVertical = appState.shouldShowBottomBar,
- searchValue = searchValue,
- searchCount = searchCount,
- onShowSnackbar = { message, action ->
- snackbarHostState.showSnackbar(
- message = message,
- actionLabel = action,
- duration = SnackbarDuration.Short
- ) == SnackbarResult.ActionPerformed
+ Column(
+ Modifier.fillMaxSize()
+ ) {
+ AnimatedVisibility(
+ visible = destination != null
+ ) {
+ OxygenTopAppBar(
+ scrollBehavior = topAppBarScrollBehavior,
+ title = {
+ destination?.let {
+ Text(
+ text = stringResource(destination.titleTextId),
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis
+ )
+ }
+ },
+ navigationIcon = OxygenIcons.Search,
+ navigationIconContentDescription = stringResource(R.string.feature_settings_top_app_bar_navigation_icon_description),
+ actionIcon = OxygenIcons.MoreVert,
+ actionIconContentDescription = stringResource(R.string.feature_settings_top_app_bar_action_icon_description),
+ activeSearch = activeSearch,
+ searchButtonPosition = SearchButtonPosition.Navigation,
+ query = searchValue,
+ colors = TopAppBarDefaults.centerAlignedTopAppBarColors(
+ containerColor = Color.Transparent,
+ scrolledContainerColor = Color.Transparent
+ ),
+ onNavigationClick = { activeSearch = true },
+ onActionClick = { showSettingsDialog = true },
+ onQueryChange = {
+ searchValue = it
+ },
+ onSearch = {
+ searchCount++
+ },
+ onCancelSearch = {
+ searchValue = ""
+ activeSearch = false
+ searchCount = 0
+ }
+ )
}
- )
+
+ OxygenNavHost(
+ appState = appState,
+ startDestination = when (appState.launchPageConfig) {
+ LaunchPageConfig.Tools -> TOOLS_ROUTE
+ LaunchPageConfig.Star -> STAR_ROUTE
+ },
+ isVertical = appState.shouldShowBottomBar,
+ searchValue = searchValue,
+ searchCount = searchCount,
+ onShowSnackbar = { message, action ->
+ snackbarHostState.showSnackbar(
+ message = message,
+ actionLabel = action,
+ duration = SnackbarDuration.Short
+ ) == SnackbarResult.ActionPerformed
+ }
+ )
+ }
}
}
}
diff --git a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/util/LocalFullScreen.kt b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/util/LocalFullScreen.kt
new file mode 100644
index 0000000..26680c3
--- /dev/null
+++ b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/util/LocalFullScreen.kt
@@ -0,0 +1,10 @@
+package top.fatweb.oxygen.toolbox.ui.util
+
+import androidx.compose.runtime.compositionLocalOf
+
+data class FullScreen(
+ val enable: Boolean = false,
+ val onStateChange: (Boolean) -> Unit = {}
+)
+
+val LocalFullScreen = compositionLocalOf { FullScreen() }
diff --git a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/view/ToolViewScreen.kt b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/view/ToolViewScreen.kt
index 230cb2b..11f5328 100644
--- a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/view/ToolViewScreen.kt
+++ b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/view/ToolViewScreen.kt
@@ -64,6 +64,7 @@ import top.fatweb.oxygen.toolbox.R
import top.fatweb.oxygen.toolbox.icon.OxygenIcons
import top.fatweb.oxygen.toolbox.ui.component.Indicator
import top.fatweb.oxygen.toolbox.ui.component.OxygenTopAppBar
+import top.fatweb.oxygen.toolbox.ui.util.LocalFullScreen
import top.fatweb.oxygen.toolbox.ui.util.ResourcesUtils
import top.fatweb.oxygen.toolbox.util.NativeWebApi
import top.fatweb.oxygen.toolbox.util.Permissions
@@ -98,6 +99,7 @@ internal fun ToolViewScreen(
isPreview: Boolean,
onBackClick: () -> Unit
) {
+ val (isFullScreen, onFullScreenStateChange) = LocalFullScreen.current
Column(
modifier
@@ -111,7 +113,9 @@ internal fun ToolViewScreen(
TopBar(
toolViewUiState = toolViewUiState,
isPreview = isPreview,
- onBackClick = onBackClick
+ isFullScreen = isFullScreen,
+ onBackClick = onBackClick,
+ onFullScreenChange = onFullScreenStateChange
)
Content(
toolViewUiState = toolViewUiState,
@@ -125,7 +129,9 @@ internal fun ToolViewScreen(
private fun TopBar(
toolViewUiState: ToolViewUiState,
isPreview: Boolean,
- onBackClick: () -> Unit
+ isFullScreen: Boolean,
+ onBackClick: () -> Unit,
+ onFullScreenChange: (Boolean) -> Unit
) = OxygenTopAppBar(
title = {
Text(
@@ -143,11 +149,16 @@ private fun TopBar(
},
navigationIcon = OxygenIcons.Back,
navigationIconContentDescription = stringResource(R.string.core_back),
+ actionIcon = if (isFullScreen) OxygenIcons.FullScreenExit else OxygenIcons.FullScreen,
+ actionIconContentDescription = stringResource(if (isFullScreen) R.string.core_exit_full_screen else R.string.core_full_screen),
colors = TopAppBarDefaults.centerAlignedTopAppBarColors(
containerColor = Color.Transparent,
scrolledContainerColor = Color.Transparent
),
- onNavigationClick = onBackClick
+ onNavigationClick = onBackClick,
+ onActionClick = {
+ onFullScreenChange(!isFullScreen)
+ }
)
@Composable
diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml
index dca7b9b..ca52924 100644
--- a/app/src/main/res/values-zh/strings.xml
+++ b/app/src/main/res/values-zh/strings.xml
@@ -14,6 +14,8 @@
搜索
加载中…
⚠️ 无法连接至互联网
+ 全屏
+ 退出全屏
安装
安装中……
更新
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 39477a5..5189579 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -13,6 +13,8 @@
Search
Loading…
⚠️ Unable to connect to the internet
+ Full Screen
+ Exit Full Screen
Install
Installing…
Upgrade