mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-20 02:58:45 +00:00
Prevent soft keyboard from covering recipient picker floating action button.
This commit is contained in:
@@ -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 :
|
||||
|
||||
@@ -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 :
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user