diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/dialogs/ChatDialog.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/dialogs/ChatDialog.kt index 5d6679bd28..431125ca8e 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/dialogs/ChatDialog.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/dialogs/ChatDialog.kt @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later package org.yuzu.yuzu_emu.dialogs @@ -20,6 +20,8 @@ import org.yuzu.yuzu_emu.databinding.DialogChatBinding import org.yuzu.yuzu_emu.databinding.ItemChatMessageBinding import org.yuzu.yuzu_emu.features.settings.model.StringSetting import org.yuzu.yuzu_emu.network.NetPlayManager +import org.yuzu.yuzu_emu.utils.CompatUtils +import org.yuzu.yuzu_emu.utils.FullscreenHelper import java.text.SimpleDateFormat import java.util.* @@ -34,6 +36,13 @@ class ChatDialog(context: Context) : BottomSheetDialog(context) { private lateinit var binding: DialogChatBinding private lateinit var chatAdapter: ChatAdapter private val handler = Handler(Looper.getMainLooper()) + private val hideSystemBars: Boolean by lazy { + runCatching { + FullscreenHelper.shouldHideSystemBars(CompatUtils.findActivity(context)) + }.getOrElse { + FullscreenHelper.isFullscreenEnabled(context) + } + } // TODO(alekpop, crueter): Top drawer for message notifications, perhaps use system notifs? // TODO(alekpop, crueter): Context menu actions for chat users @@ -41,6 +50,7 @@ class ChatDialog(context: Context) : BottomSheetDialog(context) { @SuppressLint("NotifyDataSetChanged") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + setOnShowListener { applyFullscreenMode() } binding = DialogChatBinding.inflate(LayoutInflater.from(context)) setContentView(binding.root) @@ -75,6 +85,11 @@ class ChatDialog(context: Context) : BottomSheetDialog(context) { } } + override fun onStart() { + super.onStart() + applyFullscreenMode() + } + override fun dismiss() { NetPlayManager.setChatOpen(false) super.dismiss() @@ -108,6 +123,12 @@ class ChatDialog(context: Context) : BottomSheetDialog(context) { private fun scrollToBottom() { binding.chatRecyclerView.scrollToPosition(chatAdapter.itemCount - 1) } + + private fun applyFullscreenMode() { + window?.let { window -> + FullscreenHelper.applyToWindow(window, hideSystemBars) + } + } } class ChatAdapter(private val messages: List) : diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/dialogs/LobbyBrowser.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/dialogs/LobbyBrowser.kt index 6d5c6ef23f..10ff2da6c7 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/dialogs/LobbyBrowser.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/dialogs/LobbyBrowser.kt @@ -31,15 +31,25 @@ import org.yuzu.yuzu_emu.databinding.DialogLobbyBrowserBinding import org.yuzu.yuzu_emu.databinding.ItemLobbyRoomBinding import org.yuzu.yuzu_emu.features.settings.model.StringSetting import org.yuzu.yuzu_emu.network.NetPlayManager +import org.yuzu.yuzu_emu.utils.CompatUtils +import org.yuzu.yuzu_emu.utils.FullscreenHelper import java.util.Locale class LobbyBrowser(context: Context) : BottomSheetDialog(context) { private lateinit var binding: DialogLobbyBrowserBinding private lateinit var adapter: LobbyRoomAdapter private val handler = Handler(Looper.getMainLooper()) + private val hideSystemBars: Boolean by lazy { + runCatching { + FullscreenHelper.shouldHideSystemBars(CompatUtils.findActivity(context)) + }.getOrElse { + FullscreenHelper.isFullscreenEnabled(context) + } + } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + setOnShowListener { applyFullscreenMode() } behavior.state = BottomSheetBehavior.STATE_EXPANDED behavior.skipCollapsed = @@ -81,6 +91,7 @@ class LobbyBrowser(context: Context) : BottomSheetDialog(context) { behavior.expandedOffset = 0 behavior.skipCollapsed = true behavior.state = BottomSheetBehavior.STATE_EXPANDED + applyFullscreenMode() } private fun setupRecyclerView() { @@ -274,4 +285,10 @@ class LobbyBrowser(context: Context) : BottomSheetDialog(context) { } private inner class ScoreItem(val score: Double, val item: NetPlayManager.RoomInfo) + + private fun applyFullscreenMode() { + window?.let { window -> + FullscreenHelper.applyToWindow(window, hideSystemBars) + } + } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/dialogs/NetPlayDialog.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/dialogs/NetPlayDialog.kt index 73452b4b69..45ce5fb0cf 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/dialogs/NetPlayDialog.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/dialogs/NetPlayDialog.kt @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later package org.yuzu.yuzu_emu.dialogs @@ -36,6 +36,7 @@ import org.yuzu.yuzu_emu.features.settings.model.StringSetting import org.yuzu.yuzu_emu.network.NetDataValidators import org.yuzu.yuzu_emu.network.NetPlayManager import org.yuzu.yuzu_emu.utils.CompatUtils +import org.yuzu.yuzu_emu.utils.FullscreenHelper import org.yuzu.yuzu_emu.utils.GameHelper class NetPlayDialog(context: Context) : BottomSheetDialog(context) { @@ -43,9 +44,17 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) { private val gameNameList: MutableList> = mutableListOf() private val gameIdList: MutableList> = mutableListOf() + private val hideSystemBars: Boolean by lazy { + runCatching { + FullscreenHelper.shouldHideSystemBars(CompatUtils.findActivity(context)) + }.getOrElse { + FullscreenHelper.isFullscreenEnabled(context) + } + } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + setOnShowListener { applyFullscreenMode() } behavior.state = BottomSheetBehavior.STATE_EXPANDED behavior.state = BottomSheetBehavior.STATE_EXPANDED @@ -118,6 +127,11 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) { } } + override fun onStart() { + super.onStart() + applyFullscreenMode() + } + data class NetPlayItems( val option: Int, val name: String, @@ -352,6 +366,11 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) { TextValidatorWatcher.validStates.clear() val activity = CompatUtils.findActivity(context) val dialog = BottomSheetDialog(activity) + dialog.setOnShowListener { + dialog.window?.let { window -> + FullscreenHelper.applyToWindow(window, hideSystemBars) + } + } dialog.behavior.state = BottomSheetBehavior.STATE_EXPANDED dialog.behavior.state = BottomSheetBehavior.STATE_EXPANDED @@ -582,6 +601,12 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) { dialog.show() } + private fun applyFullscreenMode() { + window?.let { window -> + FullscreenHelper.applyToWindow(window, hideSystemBars) + } + } + private fun showModerationDialog() { val activity = CompatUtils.findActivity(context) val dialog = MaterialAlertDialogBuilder(activity) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt index 0f89533d8e..b438812d58 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt @@ -104,6 +104,8 @@ object Settings { const val PREF_THEME_MODE = "ThemeMode" const val PREF_BLACK_BACKGROUNDS = "BlackBackgrounds" const val PREF_STATIC_THEME_COLOR = "StaticThemeColor" + const val PREF_APP_FULLSCREEN = "AppFullscreen" + const val APP_FULLSCREEN_DEFAULT = false enum class EmulationOrientation(val int: Int) { Unspecified(0), diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt index d33bbc3d7d..ad1ecba64c 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt @@ -103,6 +103,7 @@ class SettingsActivity : AppCompatActivity() { ) setInsets() + applyFullscreenPreference() } fun navigateBack() { @@ -122,6 +123,18 @@ class SettingsActivity : AppCompatActivity() { } } + override fun onResume() { + super.onResume() + applyFullscreenPreference() + } + + override fun onWindowFocusChanged(hasFocus: Boolean) { + super.onWindowFocusChanged(hasFocus) + if (hasFocus) { + applyFullscreenPreference() + } + } + override fun onStop() { super.onStop() Log.info("[SettingsActivity] Settings activity stopping. Saving settings to INI...") @@ -188,4 +201,8 @@ class SettingsActivity : AppCompatActivity() { windowInsets } } + + private fun applyFullscreenPreference() { + FullscreenHelper.applyToActivity(this) + } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt index ff25584c92..853e1e3e4b 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt @@ -29,6 +29,8 @@ import org.yuzu.yuzu_emu.features.settings.model.view.* import org.yuzu.yuzu_emu.utils.InputHandler import org.yuzu.yuzu_emu.utils.NativeConfig import org.yuzu.yuzu_emu.utils.DirectoryInitialization +import org.yuzu.yuzu_emu.utils.FullscreenHelper +import androidx.core.content.edit import androidx.fragment.app.FragmentActivity import org.yuzu.yuzu_emu.fragments.MessageDialogFragment @@ -1187,6 +1189,39 @@ class SettingsFragmentPresenter( ) ) + val fullscreenSetting: AbstractBooleanSetting = object : AbstractBooleanSetting { + override fun getBoolean(needsGlobal: Boolean): Boolean = + FullscreenHelper.isFullscreenEnabled(context) + + override fun setBoolean(value: Boolean) { + FullscreenHelper.setFullscreenEnabled(context, value) + settingsViewModel.setShouldRecreate(true) + } + + override val key: String = Settings.PREF_APP_FULLSCREEN + override val isRuntimeModifiable: Boolean = true + override val pairedSettingKey: String = "" + override val isSwitchable: Boolean = false + override var global: Boolean = true + override val isSaveable: Boolean = true + override val defaultValue: Boolean = Settings.APP_FULLSCREEN_DEFAULT + + override fun getValueAsString(needsGlobal: Boolean): String = + getBoolean(needsGlobal).toString() + + override fun reset() { + setBoolean(defaultValue) + } + } + + add( + SwitchSetting( + fullscreenSetting, + titleId = R.string.fullscreen_mode, + descriptionId = R.string.fullscreen_mode_description + ) + ) + add(HeaderSetting(R.string.buttons)) add(BooleanSetting.ENABLE_FOLDER_BUTTON.key) add(BooleanSetting.ENABLE_QLAUNCH_BUTTON.key) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsSubscreenActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsSubscreenActivity.kt index 11ecd355fb..91888dce12 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsSubscreenActivity.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsSubscreenActivity.kt @@ -20,6 +20,7 @@ import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.YuzuApplication import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding import org.yuzu.yuzu_emu.utils.DirectoryInitialization +import org.yuzu.yuzu_emu.utils.FullscreenHelper import org.yuzu.yuzu_emu.utils.InsetsHelper import org.yuzu.yuzu_emu.utils.ThemeHelper @@ -89,6 +90,7 @@ class SettingsSubscreenActivity : AppCompatActivity() { ) setInsets() + applyFullscreenPreference() } override fun onStart() { @@ -98,6 +100,18 @@ class SettingsSubscreenActivity : AppCompatActivity() { } } + override fun onResume() { + super.onResume() + applyFullscreenPreference() + } + + override fun onWindowFocusChanged(hasFocus: Boolean) { + super.onWindowFocusChanged(hasFocus) + if (hasFocus) { + applyFullscreenPreference() + } + } + fun navigateBack() { val navHostFragment = supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment @@ -149,4 +163,8 @@ class SettingsSubscreenActivity : AppCompatActivity() { windowInsets } } + + private fun applyFullscreenPreference() { + FullscreenHelper.applyToActivity(this) + } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ProfileManagerFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ProfileManagerFragment.kt index 2786906f6b..bd37c4c9c7 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ProfileManagerFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ProfileManagerFragment.kt @@ -9,6 +9,7 @@ import android.view.View import android.view.ViewGroup import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat +import androidx.core.view.updatePadding import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.navigation.fragment.findNavController @@ -22,6 +23,7 @@ import org.yuzu.yuzu_emu.databinding.FragmentProfileManagerBinding import org.yuzu.yuzu_emu.model.HomeViewModel import org.yuzu.yuzu_emu.model.UserProfile import org.yuzu.yuzu_emu.utils.NativeConfig +import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins class ProfileManagerFragment : Fragment() { private var _binding: FragmentProfileManagerBinding? = null @@ -172,11 +174,19 @@ class ProfileManagerFragment : Fragment() { val leftInsets = barInsets.left + cutoutInsets.left val rightInsets = barInsets.right + cutoutInsets.right - val fabLayoutParams = binding.buttonAddUser.layoutParams as ViewGroup.MarginLayoutParams - fabLayoutParams.leftMargin = leftInsets + 24 - fabLayoutParams.rightMargin = rightInsets + 24 - fabLayoutParams.bottomMargin = barInsets.bottom + 24 - binding.buttonAddUser.layoutParams = fabLayoutParams + binding.toolbarProfiles.updateMargins(left = leftInsets, right = rightInsets) + binding.listProfiles.updateMargins(left = leftInsets, right = rightInsets) + binding.listProfiles.updatePadding( + bottom = barInsets.bottom + + resources.getDimensionPixelSize(R.dimen.spacing_bottom_list_fab) + ) + + val fabSpacing = resources.getDimensionPixelSize(R.dimen.spacing_fab) + binding.buttonAddUser.updateMargins( + left = leftInsets + fabSpacing, + right = rightInsets + fabSpacing, + bottom = barInsets.bottom + fabSpacing + ) windowInsets } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt index 3a771edfcb..02368bfc16 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt @@ -169,6 +169,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider { checkForUpdates() } setInsets() + applyFullscreenPreference() } private fun checkForUpdates() { @@ -345,6 +346,14 @@ class MainActivity : AppCompatActivity(), ThemeProvider { override fun onResume() { ThemeHelper.setCorrectTheme(this) super.onResume() + applyFullscreenPreference() + } + + override fun onWindowFocusChanged(hasFocus: Boolean) { + super.onWindowFocusChanged(hasFocus) + if (hasFocus) { + applyFullscreenPreference() + } } private fun setInsets() = ViewCompat.setOnApplyWindowInsetsListener( @@ -364,6 +373,10 @@ class MainActivity : AppCompatActivity(), ThemeProvider { windowInsets } + private fun applyFullscreenPreference() { + FullscreenHelper.applyToActivity(this) + } + override fun setTheme(resId: Int) { super.setTheme(resId) themeId = resId diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FullscreenHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FullscreenHelper.kt new file mode 100644 index 0000000000..62c83e1806 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FullscreenHelper.kt @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +package org.yuzu.yuzu_emu.utils + +import android.app.Activity +import android.content.Context +import android.view.Window +import androidx.core.content.edit +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.WindowInsetsControllerCompat +import androidx.preference.PreferenceManager +import org.yuzu.yuzu_emu.features.settings.model.Settings + +object FullscreenHelper { + fun isFullscreenEnabled(context: Context): Boolean { + return PreferenceManager.getDefaultSharedPreferences(context).getBoolean( + Settings.PREF_APP_FULLSCREEN, + Settings.APP_FULLSCREEN_DEFAULT + ) + } + + fun setFullscreenEnabled(context: Context, enabled: Boolean) { + PreferenceManager.getDefaultSharedPreferences(context).edit { + putBoolean(Settings.PREF_APP_FULLSCREEN, enabled) + } + } + + fun shouldHideSystemBars(activity: Activity): Boolean { + val rootInsets = ViewCompat.getRootWindowInsets(activity.window.decorView) + val barsCurrentlyHidden = + rootInsets?.isVisible(WindowInsetsCompat.Type.systemBars())?.not() ?: false + return isFullscreenEnabled(activity) || barsCurrentlyHidden + } + + fun applyToWindow(window: Window, hideSystemBars: Boolean) { + val controller = WindowInsetsControllerCompat(window, window.decorView) + + if (hideSystemBars) { + controller.hide(WindowInsetsCompat.Type.systemBars()) + controller.systemBarsBehavior = + WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE + } else { + controller.show(WindowInsetsCompat.Type.systemBars()) + } + } + + fun applyToActivity(activity: Activity) { + applyToWindow(activity.window, isFullscreenEnabled(activity)) + } +} diff --git a/src/android/app/src/main/res/layout/fragment_profile_manager.xml b/src/android/app/src/main/res/layout/fragment_profile_manager.xml index e70a3f3da5..67828ca69e 100644 --- a/src/android/app/src/main/res/layout/fragment_profile_manager.xml +++ b/src/android/app/src/main/res/layout/fragment_profile_manager.xml @@ -5,21 +5,22 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - android:background="?attr/colorSurface" - android:fitsSystemWindows="true"> + android:background="?attr/colorSurface"> + android:fitsSystemWindows="true" + android:touchscreenBlocksFocus="false"> diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index e9bd0f1d1c..343cc4b21a 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -1160,6 +1160,8 @@ Material You App Settings Theme And Color + Fullscreen mode + Hide Android system bars across app screens. Swipe from an edge to reveal them temporarily. Change theme mode