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.enableEdgeToEdge
|
||||||
import androidx.activity.result.ActivityResult
|
import androidx.activity.result.ActivityResult
|
||||||
import androidx.activity.result.ActivityResultLauncher
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
@@ -26,7 +24,6 @@ import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.pluralStringResource
|
import androidx.compose.ui.res.pluralStringResource
|
||||||
@@ -187,6 +184,14 @@ private fun AddMembersScreenUi(
|
|||||||
if (uiState.isLookingUpRecipient) {
|
if (uiState.isLookingUpRecipient) {
|
||||||
Dialogs.IndeterminateProgressDialog()
|
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,
|
callbacks: UiCallbacks,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
Box(modifier = modifier) {
|
RecipientPicker(
|
||||||
RecipientPicker(
|
searchQuery = uiState.searchQuery,
|
||||||
searchQuery = uiState.searchQuery,
|
displayModes = setOf(RecipientPicker.DisplayMode.PUSH),
|
||||||
displayModes = setOf(RecipientPicker.DisplayMode.PUSH),
|
selectionLimits = uiState.selectionLimits,
|
||||||
selectionLimits = uiState.selectionLimits,
|
preselectedRecipients = uiState.existingMembersMinusSelf,
|
||||||
preselectedRecipients = uiState.existingMembersMinusSelf,
|
pendingRecipientSelections = uiState.pendingRecipientSelections,
|
||||||
pendingRecipientSelections = uiState.pendingRecipientSelections,
|
isRefreshing = false,
|
||||||
isRefreshing = false,
|
listBottomPadding = 64.dp,
|
||||||
listBottomPadding = 64.dp,
|
clipListToPadding = false,
|
||||||
clipListToPadding = false,
|
callbacks = RecipientPickerCallbacks(
|
||||||
callbacks = RecipientPickerCallbacks(
|
listActions = callbacks,
|
||||||
listActions = callbacks,
|
findByUsername = callbacks,
|
||||||
findByUsername = callbacks,
|
findByPhoneNumber = callbacks
|
||||||
findByPhoneNumber = callbacks
|
),
|
||||||
),
|
modifier = modifier.fillMaxSize()
|
||||||
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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private interface UiCallbacks :
|
private interface UiCallbacks :
|
||||||
|
|||||||
@@ -21,9 +21,7 @@ import androidx.compose.animation.EnterTransition
|
|||||||
import androidx.compose.animation.ExitTransition
|
import androidx.compose.animation.ExitTransition
|
||||||
import androidx.compose.animation.SizeTransform
|
import androidx.compose.animation.SizeTransform
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.FilledTonalIconButton
|
import androidx.compose.material3.FilledTonalIconButton
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
@@ -34,7 +32,6 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
@@ -185,6 +182,35 @@ private fun CreateGroupScreenUi(
|
|||||||
if (uiState.isLookingUpRecipient) {
|
if (uiState.isLookingUpRecipient) {
|
||||||
Dialogs.IndeterminateProgressDialog()
|
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,
|
callbacks: UiCallbacks,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
Box(modifier = modifier) {
|
RecipientPicker(
|
||||||
RecipientPicker(
|
searchQuery = uiState.searchQuery,
|
||||||
searchQuery = uiState.searchQuery,
|
displayModes = setOf(RecipientPicker.DisplayMode.PUSH),
|
||||||
displayModes = setOf(RecipientPicker.DisplayMode.PUSH),
|
selectionLimits = uiState.selectionLimits,
|
||||||
selectionLimits = uiState.selectionLimits,
|
pendingRecipientSelections = uiState.pendingRecipientSelections,
|
||||||
pendingRecipientSelections = uiState.pendingRecipientSelections,
|
isRefreshing = false,
|
||||||
isRefreshing = false,
|
listBottomPadding = 64.dp,
|
||||||
listBottomPadding = 64.dp,
|
clipListToPadding = false,
|
||||||
clipListToPadding = false,
|
callbacks = remember(callbacks) {
|
||||||
callbacks = remember(callbacks) {
|
RecipientPickerCallbacks(
|
||||||
RecipientPickerCallbacks(
|
listActions = callbacks,
|
||||||
listActions = callbacks,
|
findByUsername = callbacks,
|
||||||
findByUsername = callbacks,
|
findByPhoneNumber = callbacks
|
||||||
findByPhoneNumber = callbacks
|
)
|
||||||
)
|
},
|
||||||
},
|
modifier = modifier.fillMaxSize()
|
||||||
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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private interface UiCallbacks :
|
private interface UiCallbacks :
|
||||||
|
|||||||
@@ -7,7 +7,13 @@ package org.thoughtcrime.securesms.recipients.ui
|
|||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Box
|
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.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.foundation.layout.widthIn
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.MaterialTheme
|
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.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.res.vectorResource
|
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.AllDevicePreviews
|
||||||
import org.signal.core.ui.compose.Previews
|
import org.signal.core.ui.compose.Previews
|
||||||
import org.signal.core.ui.compose.Scaffolds
|
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.
|
* Provides the common adaptive layout structure for recipient picker screens.
|
||||||
*/
|
*/
|
||||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3AdaptiveApi::class)
|
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3AdaptiveApi::class, ExperimentalLayoutApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun RecipientPickerScaffold(
|
fun RecipientPickerScaffold(
|
||||||
title: String,
|
title: String,
|
||||||
@@ -44,7 +51,8 @@ fun RecipientPickerScaffold(
|
|||||||
onNavigateUpClick: () -> Unit,
|
onNavigateUpClick: () -> Unit,
|
||||||
topAppBarActions: @Composable () -> Unit,
|
topAppBarActions: @Composable () -> Unit,
|
||||||
snackbarHostState: SnackbarHostState,
|
snackbarHostState: SnackbarHostState,
|
||||||
primaryContent: @Composable () -> Unit
|
primaryContent: @Composable () -> Unit,
|
||||||
|
floatingActionButton: (@Composable () -> Unit)? = null
|
||||||
) {
|
) {
|
||||||
val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass
|
val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass
|
||||||
val isSplitPane = windowSizeClass.isSplitPane(forceSplitPane = forceSplitPane)
|
val isSplitPane = windowSizeClass.isSplitPane(forceSplitPane = forceSplitPane)
|
||||||
@@ -68,7 +76,10 @@ fun RecipientPickerScaffold(
|
|||||||
modifier = Modifier.fillMaxSize()
|
modifier = Modifier.fillMaxSize()
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
primaryContent()
|
Box {
|
||||||
|
primaryContent()
|
||||||
|
FloatingActionButtonContainer(floatingActionButton)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -79,6 +90,7 @@ fun RecipientPickerScaffold(
|
|||||||
) {
|
) {
|
||||||
Box(modifier = Modifier.widthIn(max = windowSizeClass.detailPaneMaxContentWidth)) {
|
Box(modifier = Modifier.widthIn(max = windowSizeClass.detailPaneMaxContentWidth)) {
|
||||||
primaryContent()
|
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
|
@AllDevicePreviews
|
||||||
@Composable
|
@Composable
|
||||||
private fun RecipientPickerScaffoldPreview() {
|
private fun RecipientPickerScaffoldPreview() {
|
||||||
|
|||||||
Reference in New Issue
Block a user