From 5b49060a9d8f091cc4d1ebe967e01fb3e0d67515 Mon Sep 17 00:00:00 2001 From: Tom Pratt Date: Tue, 19 May 2026 09:46:22 +0200 Subject: [PATCH] Fix NetplayScreen edge to edge NetplayScreen was not drawing under the bottom nav bar like other screens. In landscape mode half of the screen scrolls and half is fixed, to make this look natural with edge to edge, provide a fade on the bottom of the scrolling side so things look natural when the screens loads and is resting at 0 scroll offset. --- .../features/netplay/ui/NetplayScreen.kt | 24 +++++++-- .../dolphinemu/ui/theme/DolphinTheme.kt | 49 +++++++++++++++++++ 2 files changed, 68 insertions(+), 5 deletions(-) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/ui/NetplayScreen.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/ui/NetplayScreen.kt index c4831391a5..54ea403eeb 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/ui/NetplayScreen.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/ui/NetplayScreen.kt @@ -108,6 +108,7 @@ import org.dolphinemu.dolphinemu.ui.theme.MenuSpacer import org.dolphinemu.dolphinemu.ui.theme.OutlinedBox import org.dolphinemu.dolphinemu.ui.theme.PreviewTheme import org.dolphinemu.dolphinemu.ui.theme.ReadOnlyTextField +import org.dolphinemu.dolphinemu.ui.theme.bottomFadeOverlay import org.dolphinemu.dolphinemu.ui.theme.rememberSheetState import org.dolphinemu.dolphinemu.utils.CoilUtils import java.util.Locale @@ -164,7 +165,6 @@ fun NetplayScreen( val modifier = Modifier .fillMaxSize() .consumeWindowInsets(innerPadding) - .padding(innerPadding) // State which must live above the landscape/portrait split. var showChat by rememberSaveable { mutableStateOf(false) } @@ -202,6 +202,7 @@ fun NetplayScreen( joinAddresses = joinAddresses, selectedJoinInfoType = selectedJoinInfoType, onSelectedJoinInfoTypeChanged = { selectedJoinInfoType = it }, + contentPadding = innerPadding, modifier = modifier ) } else { @@ -227,6 +228,7 @@ fun NetplayScreen( joinAddresses = joinAddresses, selectedJoinInfoType = selectedJoinInfoType, onSelectedJoinInfoTypeChanged = { selectedJoinInfoType = it }, + contentPadding = innerPadding, modifier = modifier ) } @@ -344,11 +346,13 @@ private fun PortraitContent( joinAddresses: Map, selectedJoinInfoType: JoinInfoType, onSelectedJoinInfoTypeChanged: (JoinInfoType) -> Unit, + contentPadding: PaddingValues, modifier: Modifier = Modifier, ) { Column( modifier = modifier .verticalScroll(rememberScrollState()) + .padding(contentPadding) ) { Chat( messages = messages, @@ -414,11 +418,11 @@ private fun LandscapeContent( joinAddresses: Map, selectedJoinInfoType: JoinInfoType, onSelectedJoinInfoTypeChanged: (JoinInfoType) -> Unit, + contentPadding: PaddingValues, modifier: Modifier = Modifier, ) { Row( modifier = modifier - .padding(horizontal = DolphinTheme.scaffoldPadding) ) { Chat( messages = messages, @@ -426,16 +430,26 @@ private fun LandscapeContent( showBottomSheet = showChat, onShowBottomSheetChanged = onShowChatChanged, modifier = Modifier + .padding(contentPadding) + .padding( + start = DolphinTheme.scaffoldPadding, + end = DolphinTheme.scaffoldPadding / 2, + ) .weight(1f) .fillMaxHeight() ) - Spacer(modifier = Modifier.width(16.dp)) - + val scrollState = rememberScrollState() Column( modifier = Modifier .weight(1f) - .verticalScroll(rememberScrollState()) + .bottomFadeOverlay(scrollState, contentPadding.calculateBottomPadding()) + .verticalScroll(scrollState) + .padding(contentPadding) + .padding( + start = DolphinTheme.scaffoldPadding / 2, + end = DolphinTheme.scaffoldPadding + ) ) { PlayersAndSettings( game = game, diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/theme/DolphinTheme.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/theme/DolphinTheme.kt index 37085b06d9..c142a44d73 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/theme/DolphinTheme.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/theme/DolphinTheme.kt @@ -5,11 +5,13 @@ package org.dolphinemu.dolphinemu.ui.theme import android.content.Context import androidx.annotation.AttrRes import androidx.compose.foundation.LocalIndication +import androidx.compose.foundation.ScrollState import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -28,10 +30,15 @@ import androidx.compose.material3.Text import androidx.compose.material3.darkColorScheme import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext @@ -286,3 +293,45 @@ fun rememberSheetState( ) } } + +@Composable +fun Modifier.bottomFadeOverlay( + scrollState: ScrollState, + fadeHeight: Dp, +): Modifier { + val surfaceColor = MaterialTheme.colorScheme.surface + val bottomPaddingPx = with(LocalDensity.current) { + fadeHeight.toPx() + } + val fadeDelayPx = with(LocalDensity.current) { 8.dp.toPx() } + val fadeDurationPx = with(LocalDensity.current) { 48.dp.toPx() } + val scrollProgress by remember { + derivedStateOf { + if (bottomPaddingPx <= 0f) 1f + else ((scrollState.value.coerceAtLeast(0) - fadeDelayPx) / fadeDurationPx) + .coerceIn(0f, 1f) + } + } + val surfaceStopFraction = (scrollProgress * 2f).coerceIn(0f, 1f) + val transparentStopFraction = ((scrollProgress * 2f) - 1f).coerceIn(0f, 1f) + + return drawWithContent { + drawContent() + if (transparentStopFraction < 1f) { + drawRect( + brush = Brush.verticalGradient( + colorStops = arrayOf( + 0f to Color.Transparent, + transparentStopFraction to Color.Transparent, + surfaceStopFraction to surfaceColor, + 1f to surfaceColor, + ), + startY = size.height - bottomPaddingPx, + endY = size.height, + ), + topLeft = Offset(0f, size.height - bottomPaddingPx), + size = Size(size.width, bottomPaddingPx), + ) + } + } +}