diff --git a/feature/registration/src/main/java/org/signal/registration/screens/RegistrationScaffold.kt b/feature/registration/src/main/java/org/signal/registration/screens/RegistrationScaffold.kt index 0553c0b527..694da7d48f 100644 --- a/feature/registration/src/main/java/org/signal/registration/screens/RegistrationScaffold.kt +++ b/feature/registration/src/main/java/org/signal/registration/screens/RegistrationScaffold.kt @@ -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, diff --git a/feature/registration/src/main/java/org/signal/registration/screens/countrycode/CountryCodePickerScreen.kt b/feature/registration/src/main/java/org/signal/registration/screens/countrycode/CountryCodePickerScreen.kt index 4d8fe3e133..dd16d4e7c2 100644 --- a/feature/registration/src/main/java/org/signal/registration/screens/countrycode/CountryCodePickerScreen.kt +++ b/feature/registration/src/main/java/org/signal/registration/screens/countrycode/CountryCodePickerScreen.kt @@ -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() diff --git a/feature/registration/src/main/java/org/signal/registration/screens/phonenumber/PhoneNumberEntryScreen.kt b/feature/registration/src/main/java/org/signal/registration/screens/phonenumber/PhoneNumberEntryScreen.kt index d1041c372c..2339e94d62 100644 --- a/feature/registration/src/main/java/org/signal/registration/screens/phonenumber/PhoneNumberEntryScreen.kt +++ b/feature/registration/src/main/java/org/signal/registration/screens/phonenumber/PhoneNumberEntryScreen.kt @@ -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(