Use TopAppBar on all RegV5 screens.

This commit is contained in:
jeffrey-signal
2026-06-01 15:44:27 -04:00
committed by Cody Henthorne
parent 2adf84a895
commit 8af7606e3f
3 changed files with 121 additions and 89 deletions
@@ -16,13 +16,18 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.SubcomposeLayout
import androidx.compose.ui.platform.LocalResources
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@@ -30,6 +35,8 @@ import androidx.compose.ui.unit.sp
import org.signal.core.ui.WindowBreakpoint
import org.signal.core.ui.compose.AllDevicePreviews
import org.signal.core.ui.compose.Previews
import org.signal.core.ui.getWindowSizeClass
import org.signal.core.ui.isHeightCompact
import org.signal.core.ui.rememberWindowBreakpoint
object RegistrationScaffold {
@@ -132,6 +139,19 @@ object RegistrationScaffold {
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun rememberTopBarScrollBehavior(): TopAppBarScrollBehavior {
val isHeightCompact = LocalResources.current.getWindowSizeClass().isHeightCompact
val topAppBarState = rememberTopAppBarState()
return if (isHeightCompact) {
TopAppBarDefaults.enterAlwaysScrollBehavior(state = topAppBarState)
} else {
TopAppBarDefaults.pinnedScrollBehavior(state = topAppBarState)
}
}
@Composable
fun FooterSurface(
isContentScrolledUnder: Boolean,
@@ -28,6 +28,7 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
@@ -56,6 +57,7 @@ import org.signal.core.ui.compose.Dividers
import org.signal.core.ui.compose.IconButtons.IconButton
import org.signal.core.ui.compose.LargeFontPreviews
import org.signal.core.ui.compose.Previews
import org.signal.core.ui.compose.Scaffolds
import org.signal.core.ui.compose.SignalIcons
import org.signal.registration.R
import org.signal.registration.screens.OnePaneRegistrationScaffold
@@ -85,58 +87,40 @@ private fun OnePaneLayout(
state: CountryCodeState,
onEvent: (CountryCodePickerScreenEvents) -> Unit
) {
val topBarScrollBehavior = RegistrationScaffold.rememberTopBarScrollBehavior()
OnePaneRegistrationScaffold(
modifier = Modifier.fillMaxSize(),
params = layoutParams,
topBar = {
Row(
modifier = Modifier.fillMaxWidth().padding(horizontal = 12.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically
) {
IconButton(
onClick = { onEvent(CountryCodePickerScreenEvents.Dismissed) }
) {
Icon(
imageVector = SignalIcons.X.imageVector,
contentDescription = stringResource(R.string.CountryCodeSelectScreen__close)
)
}
Text(
stringResource(R.string.CountryCodeSelectScreen__your_country),
style = MaterialTheme.typography.titleLarge,
modifier = Modifier.attachDebugLogHelper()
)
}
TopAppBar(
scrollBehavior = topBarScrollBehavior,
onCloseClick = { onEvent(CountryCodePickerScreenEvents.Dismissed) }
)
}
) {
CountryList(state, onEvent)
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun TwoPaneLayout(
params: RegistrationScaffold.Params.TwoPane,
state: CountryCodeState,
onEvent: (CountryCodePickerScreenEvents) -> Unit
) {
val topBarScrollBehavior = RegistrationScaffold.rememberTopBarScrollBehavior()
TwoPaneRegistrationScaffold(
modifier = Modifier.fillMaxSize(),
topBar = {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
IconButton(
onClick = { onEvent(CountryCodePickerScreenEvents.Dismissed) }
) {
Icon(
imageVector = SignalIcons.X.imageVector,
contentDescription = stringResource(R.string.CountryCodeSelectScreen__close)
)
}
}
},
params = params,
topBar = {
TopAppBar(
scrollBehavior = topBarScrollBehavior,
onCloseClick = { onEvent(CountryCodePickerScreenEvents.Dismissed) }
)
},
firstPane = { paddingValues ->
Column(
modifier = Modifier
@@ -146,7 +130,7 @@ private fun TwoPaneLayout(
) {
Text(
stringResource(R.string.CountryCodeSelectScreen__your_country),
style = MaterialTheme.typography.titleLarge,
style = MaterialTheme.typography.headlineMedium,
modifier = Modifier.attachDebugLogHelper()
)
}
@@ -164,6 +148,22 @@ private fun TwoPaneLayout(
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TopAppBar(
scrollBehavior: TopAppBarScrollBehavior,
onCloseClick: () -> Unit
) {
Scaffolds.DefaultTopAppBar(
title = "",
titleContent = { _, _ -> },
onNavigationClick = onCloseClick,
navigationIcon = SignalIcons.X.imageVector,
navigationContentDescription = stringResource(R.string.CountryCodeSelectScreen__close),
scrollBehavior = scrollBehavior
)
}
@Composable
private fun CountryList(state: CountryCodeState, onEvent: (CountryCodePickerScreenEvents) -> Unit) {
val listState = rememberLazyListState()
@@ -24,11 +24,13 @@ import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
@@ -41,6 +43,7 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalResources
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
@@ -57,6 +60,7 @@ import org.signal.core.ui.compose.Dialogs
import org.signal.core.ui.compose.DropdownMenus
import org.signal.core.ui.compose.IconButtons.IconButton
import org.signal.core.ui.compose.Previews
import org.signal.core.ui.compose.Scaffolds
import org.signal.registration.R
import org.signal.registration.screens.OnePaneRegistrationScaffold
import org.signal.registration.screens.RegistrationScaffold
@@ -64,6 +68,7 @@ import org.signal.registration.screens.TwoPaneRegistrationScaffold
import org.signal.registration.screens.attachDebugLogHelper
import org.signal.registration.screens.phonenumber.PhoneNumberEntryState.OneTimeEvent
import org.signal.registration.test.TestTags
import org.signal.core.ui.R as CoreR
/**
* Phone number entry screen
@@ -120,51 +125,7 @@ fun PhoneNumberScreen(
}
}
@Composable
fun TopbarMenu() {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.End
) {
Box {
val menuController = remember { DropdownMenus.MenuController() }
IconButton(
onClick = { menuController.show() }
) {
Icon(
imageVector = ImageVector.vectorResource(org.signal.core.ui.R.drawable.symbol_more_vertical_24),
contentDescription = stringResource(R.string.RegistrationActivity_open_menu)
)
}
DropdownMenus.Menu(
controller = menuController,
offsetX = 24.dp,
offsetY = 0.dp
) {
DropdownMenus.Item(
text = {
Text(text = stringResource(R.string.RegistrationActivity_use_proxy))
},
onClick = {
// TODO: Implement use proxy
menuController.hide()
}
)
DropdownMenus.Item(
text = {
Text(text = stringResource(R.string.RegistrationActivity_link_device))
},
onClick = {
// TODO: Implement link device
menuController.hide()
}
)
}
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun OnePaneLayout(
params: RegistrationScaffold.Params.OnePane,
@@ -175,16 +136,16 @@ private fun OnePaneLayout(
val selectedCountryEmoji = state.countryEmoji
val scrollState = rememberScrollState()
val topBarScrollBehavior = RegistrationScaffold.rememberTopBarScrollBehavior()
OnePaneRegistrationScaffold(
params = params,
topBar = {
TopbarMenu()
},
topBar = { TopAppBar(scrollBehavior = topBarScrollBehavior) },
content = { paddingValues ->
Column(
modifier = Modifier
.fillMaxSize()
.nestedScroll(topBarScrollBehavior.nestedScrollConnection)
.verticalScroll(scrollState)
.padding(paddingValues)
) {
@@ -224,6 +185,7 @@ private fun OnePaneLayout(
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun TwoPaneLayout(
params: RegistrationScaffold.Params.TwoPane,
@@ -233,18 +195,19 @@ private fun TwoPaneLayout(
val selectedCountry = state.countryName
val selectedCountryEmoji = state.countryEmoji
val scrollState = rememberScrollState()
val firstPaneScrollState = rememberScrollState()
val secondPaneScrollState = rememberScrollState()
val topBarScrollBehavior = RegistrationScaffold.rememberTopBarScrollBehavior()
TwoPaneRegistrationScaffold(
params = params,
topBar = {
TopbarMenu()
},
topBar = { TopAppBar(scrollBehavior = topBarScrollBehavior) },
firstPane = { paddingValues ->
Column(
modifier = Modifier
.weight(1f)
.verticalScroll(scrollState)
.nestedScroll(topBarScrollBehavior.nestedScrollConnection)
.verticalScroll(firstPaneScrollState)
.padding(paddingValues)
) {
Description()
@@ -254,7 +217,8 @@ private fun TwoPaneLayout(
Column(
modifier = Modifier
.weight(1f)
.verticalScroll(scrollState)
.nestedScroll(topBarScrollBehavior.nestedScrollConnection)
.verticalScroll(secondPaneScrollState)
.padding(paddingValues)
) {
CountryPicker(
@@ -281,7 +245,7 @@ private fun TwoPaneLayout(
},
footer = {
RegistrationScaffold.FooterSurface(
isContentScrolledUnder = scrollState.canScrollForward
isContentScrolledUnder = firstPaneScrollState.canScrollForward || secondPaneScrollState.canScrollForward
) {
NextButton(state, onEvent)
}
@@ -289,6 +253,54 @@ private fun TwoPaneLayout(
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TopAppBar(
scrollBehavior: TopAppBarScrollBehavior
) {
Scaffolds.DefaultTopAppBar(
title = "",
titleContent = { _, _ -> },
onNavigationClick = { },
navigationIcon = null,
scrollBehavior = scrollBehavior,
actions = {
val menuController = remember { DropdownMenus.MenuController() }
IconButton(
onClick = { menuController.show() },
modifier = Modifier.padding(horizontal = 8.dp)
) {
Icon(
imageVector = ImageVector.vectorResource(CoreR.drawable.symbol_more_vertical_24),
contentDescription = stringResource(R.string.RegistrationActivity_open_menu)
)
}
DropdownMenus.Menu(
controller = menuController,
offsetX = 24.dp,
offsetY = 0.dp
) {
DropdownMenus.Item(
text = { Text(text = stringResource(R.string.RegistrationActivity_use_proxy)) },
onClick = {
TODO("Handle use proxy")
menuController.hide()
}
)
DropdownMenus.Item(
text = { Text(text = stringResource(R.string.RegistrationActivity_link_device)) },
onClick = {
TODO("Handle link device")
menuController.hide()
}
)
}
}
)
}
@Composable
private fun Description() {
Text(