diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/addmembers/AddMembersActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/addmembers/AddMembersActivity.kt index 09b7941cc2..32b7f68d7f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/addmembers/AddMembersActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/addmembers/AddMembersActivity.kt @@ -16,9 +16,7 @@ import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.activity.result.ActivityResult import androidx.activity.result.ActivityResultLauncher -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text @@ -26,7 +24,6 @@ import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.pluralStringResource @@ -187,6 +184,14 @@ private fun AddMembersScreenUi( if (uiState.isLookingUpRecipient) { Dialogs.IndeterminateProgressDialog() } + }, + floatingActionButton = { + Buttons.MediumTonal( + enabled = uiState.newSelections.isNotEmpty(), + onClick = callbacks::onDoneClicked + ) { + Text(text = stringResource(R.string.AddMembersActivity__done)) + } } ) } @@ -197,37 +202,22 @@ private fun AddMembersRecipientPicker( callbacks: UiCallbacks, modifier: Modifier = Modifier ) { - Box(modifier = modifier) { - RecipientPicker( - searchQuery = uiState.searchQuery, - displayModes = setOf(RecipientPicker.DisplayMode.PUSH), - selectionLimits = uiState.selectionLimits, - preselectedRecipients = uiState.existingMembersMinusSelf, - pendingRecipientSelections = uiState.pendingRecipientSelections, - isRefreshing = false, - listBottomPadding = 64.dp, - clipListToPadding = false, - callbacks = RecipientPickerCallbacks( - listActions = callbacks, - findByUsername = callbacks, - findByPhoneNumber = callbacks - ), - modifier = modifier.fillMaxSize() - ) - - Box( - modifier = Modifier - .align(Alignment.BottomEnd) - .padding(start = 16.dp, end = 16.dp, bottom = 16.dp) - ) { - Buttons.MediumTonal( - enabled = uiState.newSelections.isNotEmpty(), - onClick = callbacks::onDoneClicked - ) { - Text(text = stringResource(R.string.AddMembersActivity__done)) - } - } - } + RecipientPicker( + searchQuery = uiState.searchQuery, + displayModes = setOf(RecipientPicker.DisplayMode.PUSH), + selectionLimits = uiState.selectionLimits, + preselectedRecipients = uiState.existingMembersMinusSelf, + pendingRecipientSelections = uiState.pendingRecipientSelections, + isRefreshing = false, + listBottomPadding = 64.dp, + clipListToPadding = false, + callbacks = RecipientPickerCallbacks( + listActions = callbacks, + findByUsername = callbacks, + findByPhoneNumber = callbacks + ), + modifier = modifier.fillMaxSize() + ) } private interface UiCallbacks : diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/CreateGroupActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/CreateGroupActivity.kt index 6b853170df..040d81597c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/CreateGroupActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/CreateGroupActivity.kt @@ -21,9 +21,7 @@ import androidx.compose.animation.EnterTransition import androidx.compose.animation.ExitTransition import androidx.compose.animation.SizeTransform import androidx.compose.animation.core.tween -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.FilledTonalIconButton import androidx.compose.material3.Icon @@ -34,7 +32,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalContext @@ -185,6 +182,35 @@ private fun CreateGroupScreenUi( if (uiState.isLookingUpRecipient) { Dialogs.IndeterminateProgressDialog() } + }, + floatingActionButton = { + AnimatedContent( + targetState = uiState.newSelections.isNotEmpty(), + transitionSpec = { + ContentTransform( + targetContentEnter = EnterTransition.None, + initialContentExit = ExitTransition.None + ) using SizeTransform(sizeAnimationSpec = { _, _ -> tween(300) }) + } + ) { hasSelectedContacts -> + if (hasSelectedContacts) { + FilledTonalIconButton( + onClick = callbacks::onNextClicked, + content = { + Icon( + imageVector = ImageVector.vectorResource(R.drawable.ic_arrow_end_24), + contentDescription = stringResource(R.string.CreateGroupActivity__accessibility_next) + ) + } + ) + } else { + Buttons.MediumTonal( + onClick = callbacks::onNextClicked + ) { + Text(text = stringResource(R.string.CreateGroupActivity__skip)) + } + } + } } ) } @@ -195,56 +221,23 @@ private fun CreateGroupRecipientPicker( callbacks: UiCallbacks, modifier: Modifier = Modifier ) { - Box(modifier = modifier) { - RecipientPicker( - searchQuery = uiState.searchQuery, - displayModes = setOf(RecipientPicker.DisplayMode.PUSH), - selectionLimits = uiState.selectionLimits, - pendingRecipientSelections = uiState.pendingRecipientSelections, - isRefreshing = false, - listBottomPadding = 64.dp, - clipListToPadding = false, - callbacks = remember(callbacks) { - RecipientPickerCallbacks( - listActions = callbacks, - findByUsername = callbacks, - findByPhoneNumber = callbacks - ) - }, - modifier = modifier.fillMaxSize() - ) - - AnimatedContent( - targetState = uiState.newSelections.isNotEmpty(), - transitionSpec = { - ContentTransform( - targetContentEnter = EnterTransition.None, - initialContentExit = ExitTransition.None - ) using SizeTransform(sizeAnimationSpec = { _, _ -> tween(300) }) - }, - modifier = Modifier - .align(Alignment.BottomEnd) - .padding(start = 16.dp, end = 16.dp, bottom = 16.dp) - ) { hasSelectedContacts -> - if (hasSelectedContacts) { - FilledTonalIconButton( - onClick = callbacks::onNextClicked, - content = { - Icon( - imageVector = ImageVector.vectorResource(R.drawable.ic_arrow_end_24), - contentDescription = stringResource(R.string.CreateGroupActivity__accessibility_next) - ) - } - ) - } else { - Buttons.MediumTonal( - onClick = callbacks::onNextClicked - ) { - Text(text = stringResource(R.string.CreateGroupActivity__skip)) - } - } - } - } + RecipientPicker( + searchQuery = uiState.searchQuery, + displayModes = setOf(RecipientPicker.DisplayMode.PUSH), + selectionLimits = uiState.selectionLimits, + pendingRecipientSelections = uiState.pendingRecipientSelections, + isRefreshing = false, + listBottomPadding = 64.dp, + clipListToPadding = false, + callbacks = remember(callbacks) { + RecipientPickerCallbacks( + listActions = callbacks, + findByUsername = callbacks, + findByPhoneNumber = callbacks + ) + }, + modifier = modifier.fillMaxSize() + ) } private interface UiCallbacks : diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/RecipientPickerScaffold.kt b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/RecipientPickerScaffold.kt index 1a310159fd..d2f9e07f26 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/RecipientPickerScaffold.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/RecipientPickerScaffold.kt @@ -7,7 +7,13 @@ package org.thoughtcrime.securesms.recipients.ui import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxScope +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.imePadding +import androidx.compose.foundation.layout.isImeVisible +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.widthIn import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme @@ -23,6 +29,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.unit.dp import org.signal.core.ui.compose.AllDevicePreviews import org.signal.core.ui.compose.Previews import org.signal.core.ui.compose.Scaffolds @@ -36,7 +43,7 @@ import org.thoughtcrime.securesms.window.rememberAppScaffoldNavigator /** * Provides the common adaptive layout structure for recipient picker screens. */ -@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3AdaptiveApi::class) +@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3AdaptiveApi::class, ExperimentalLayoutApi::class) @Composable fun RecipientPickerScaffold( title: String, @@ -44,7 +51,8 @@ fun RecipientPickerScaffold( onNavigateUpClick: () -> Unit, topAppBarActions: @Composable () -> Unit, snackbarHostState: SnackbarHostState, - primaryContent: @Composable () -> Unit + primaryContent: @Composable () -> Unit, + floatingActionButton: (@Composable () -> Unit)? = null ) { val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass val isSplitPane = windowSizeClass.isSplitPane(forceSplitPane = forceSplitPane) @@ -68,7 +76,10 @@ fun RecipientPickerScaffold( modifier = Modifier.fillMaxSize() ) } else { - primaryContent() + Box { + primaryContent() + FloatingActionButtonContainer(floatingActionButton) + } } }, @@ -79,6 +90,7 @@ fun RecipientPickerScaffold( ) { Box(modifier = Modifier.widthIn(max = windowSizeClass.detailPaneMaxContentWidth)) { primaryContent() + FloatingActionButtonContainer(floatingActionButton) } } }, @@ -93,6 +105,27 @@ fun RecipientPickerScaffold( ) } +@OptIn(ExperimentalLayoutApi::class) +@Composable +private fun BoxScope.FloatingActionButtonContainer( + button: (@Composable () -> Unit)? +) { + if (button != null) { + Box( + modifier = Modifier + .align(Alignment.BottomEnd) + .imePadding() + .padding( + start = 16.dp, + end = 16.dp, + bottom = if (WindowInsets.isImeVisible) 0.dp else 16.dp + ) + ) { + button() + } + } +} + @AllDevicePreviews @Composable private fun RecipientPickerScaffoldPreview() {