Scaffold / Appbar composable

Custom composable that switched between compact and medium size app bar depending on a combination of orientation and screen size. (Small app bar when horizontal on small devices, medium app bar otherwise. Medium app bar supports collapse on scroll.
This commit is contained in:
Tom Pratt 2026-05-11 07:33:16 -07:00
parent 32cd349207
commit 3998f06cb4
3 changed files with 79 additions and 34 deletions

View File

@ -4,6 +4,7 @@ package org.dolphinemu.dolphinemu.features.netplay.ui
import android.content.Intent
import android.content.res.Configuration
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints
@ -44,6 +45,7 @@ import androidx.compose.material3.Card
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExposedDropdownMenuAnchorType
import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.ExposedDropdownMenuDefaults
import androidx.compose.material3.ExtendedFloatingActionButton
@ -53,11 +55,8 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MediumTopAppBar
import androidx.compose.material3.MenuAnchorType
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SheetValue
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
@ -103,6 +102,7 @@ import org.dolphinemu.dolphinemu.features.netplay.model.Player
import org.dolphinemu.dolphinemu.features.netplay.model.SaveTransferProgress
import org.dolphinemu.dolphinemu.features.netplay.model.TraversalState
import org.dolphinemu.dolphinemu.model.GameFile
import org.dolphinemu.dolphinemu.ui.theme.DolphinScaffold
import org.dolphinemu.dolphinemu.ui.theme.DolphinTheme
import org.dolphinemu.dolphinemu.ui.theme.MenuSpacer
import org.dolphinemu.dolphinemu.ui.theme.OutlinedBox
@ -140,19 +140,19 @@ fun NetplayScreen(
gameDigestProgress: GameDigestProgress?,
joinAddresses: Map<JoinInfoType, JoinAddress>,
) {
Scaffold(
topBar = {
MediumTopAppBar(
title = { Text(stringResource(R.string.netplay_title)) },
navigationIcon = {
IconButton(onClick = onBackClicked) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = "Back",
)
}
},
)
val scrollState = rememberScrollState()
DolphinScaffold(
title = {
Text(stringResource(R.string.netplay_title))
},
navigationIcon = {
IconButton(onClick = onBackClicked) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = "Back",
)
}
},
floatingActionButton = {
if (isHosting) {
@ -202,6 +202,7 @@ fun NetplayScreen(
joinAddresses = joinAddresses,
selectedJoinInfoType = selectedJoinInfoType,
onSelectedJoinInfoTypeChanged = { selectedJoinInfoType = it },
scrollState = scrollState,
contentPadding = innerPadding,
modifier = modifier
)
@ -228,6 +229,7 @@ fun NetplayScreen(
joinAddresses = joinAddresses,
selectedJoinInfoType = selectedJoinInfoType,
onSelectedJoinInfoTypeChanged = { selectedJoinInfoType = it },
scrollState = scrollState,
contentPadding = innerPadding,
modifier = modifier
)
@ -346,12 +348,13 @@ private fun PortraitContent(
joinAddresses: Map<JoinInfoType, JoinAddress>,
selectedJoinInfoType: JoinInfoType,
onSelectedJoinInfoTypeChanged: (JoinInfoType) -> Unit,
scrollState: ScrollState,
contentPadding: PaddingValues,
modifier: Modifier = Modifier,
) {
Column(
modifier = modifier
.verticalScroll(rememberScrollState())
.verticalScroll(scrollState)
.padding(contentPadding)
) {
Chat(
@ -418,6 +421,7 @@ private fun LandscapeContent(
joinAddresses: Map<JoinInfoType, JoinAddress>,
selectedJoinInfoType: JoinInfoType,
onSelectedJoinInfoTypeChanged: (JoinInfoType) -> Unit,
scrollState: ScrollState,
contentPadding: PaddingValues,
modifier: Modifier = Modifier,
) {
@ -843,7 +847,7 @@ private fun JoinInfoDropdown(
label = { Text(stringResource(R.string.netplay_host_address_label)) },
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded) },
modifier = Modifier
.menuAnchor(MenuAnchorType.PrimaryNotEditable)
.menuAnchor(ExposedDropdownMenuAnchorType.PrimaryNotEditable)
.fillMaxWidth()
)
@ -1001,7 +1005,7 @@ private fun NetworkModeDropdown(
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) },
colors = ExposedDropdownMenuDefaults.outlinedTextFieldColors(),
modifier = Modifier
.menuAnchor(MenuAnchorType.PrimaryNotEditable)
.menuAnchor(ExposedDropdownMenuAnchorType.PrimaryNotEditable)
.fillMaxWidth(),
)
ExposedDropdownMenu(

View File

@ -30,11 +30,9 @@ import androidx.compose.material3.ExtendedFloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MediumTopAppBar
import androidx.compose.material3.MenuAnchorType
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SecondaryTabRow
import androidx.compose.material3.Tab
import androidx.compose.material3.Text
@ -57,6 +55,7 @@ import kotlinx.coroutines.flow.emptyFlow
import org.dolphinemu.dolphinemu.R
import org.dolphinemu.dolphinemu.features.netplay.model.ConnectionRole
import org.dolphinemu.dolphinemu.features.netplay.model.ConnectionType
import org.dolphinemu.dolphinemu.ui.theme.DolphinScaffold
import org.dolphinemu.dolphinemu.ui.theme.DolphinTheme
import org.dolphinemu.dolphinemu.ui.theme.MenuSpacer
@ -88,19 +87,15 @@ fun NetplaySetupScreen(
onHostClicked: () -> Unit,
onConnectClicked: () -> Unit,
) {
Scaffold(
topBar = {
MediumTopAppBar(
title = { Text(stringResource(R.string.netplay_setup_title)) },
navigationIcon = {
IconButton(onClick = onBackClicked) {
Icon(
Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = "Back"
)
}
},
)
DolphinScaffold(
title = { Text(stringResource(R.string.netplay_setup_title)) },
navigationIcon = {
IconButton(onClick = onBackClicked) {
Icon(
Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = "Back"
)
}
},
floatingActionButton = {
ExtendedFloatingActionButton(

View File

@ -3,6 +3,7 @@
package org.dolphinemu.dolphinemu.ui.theme
import android.content.Context
import android.content.res.Configuration
import androidx.annotation.AttrRes
import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.ScrollState
@ -21,12 +22,16 @@ import androidx.compose.material3.ColorScheme
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MediumTopAppBar
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.OutlinedTextFieldDefaults
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SheetState
import androidx.compose.material3.SheetValue
import androidx.compose.material3.SheetValue.Hidden
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
@ -41,6 +46,8 @@ 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.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.TextStyle
@ -147,6 +154,45 @@ private fun Context.toDolphinColorScheme(isDark: Boolean): ColorScheme {
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DolphinScaffold(
title: @Composable () -> Unit,
navigationIcon: @Composable () -> Unit,
modifier: Modifier = Modifier,
floatingActionButton: @Composable () -> Unit = {},
content: @Composable (PaddingValues) -> Unit,
) {
val isCompactLandscape = with(LocalConfiguration.current) {
orientation == Configuration.ORIENTATION_LANDSCAPE && smallestScreenWidthDp < 600
}
val topAppBarScrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
Scaffold(
modifier = modifier.then(
if (isCompactLandscape) Modifier
else Modifier.nestedScroll(topAppBarScrollBehavior.nestedScrollConnection)
),
topBar = {
if (isCompactLandscape) {
TopAppBar(
title = title,
navigationIcon = navigationIcon,
)
} else {
MediumTopAppBar(
title = title,
navigationIcon = navigationIcon,
scrollBehavior = topAppBarScrollBehavior,
)
}
},
floatingActionButton = floatingActionButton,
content = content,
)
}
@Composable
fun MenuSpacer() = Spacer(modifier = Modifier.height(16.dp))