Skip to content

Commit 8e06637

Browse files
Prevent soft keyboard from covering recipient picker floating action button.
1 parent df07f4f commit 8e06637

File tree

3 files changed

+106
-90
lines changed

3 files changed

+106
-90
lines changed

app/src/main/java/org/thoughtcrime/securesms/groups/ui/addmembers/AddMembersActivity.kt

Lines changed: 24 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,14 @@ import androidx.activity.compose.setContent
1616
import androidx.activity.enableEdgeToEdge
1717
import androidx.activity.result.ActivityResult
1818
import androidx.activity.result.ActivityResultLauncher
19-
import androidx.compose.foundation.layout.Box
2019
import androidx.compose.foundation.layout.fillMaxSize
21-
import androidx.compose.foundation.layout.padding
2220
import androidx.compose.material3.ExperimentalMaterial3Api
2321
import androidx.compose.material3.SnackbarHostState
2422
import androidx.compose.material3.Text
2523
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
2624
import androidx.compose.runtime.Composable
2725
import androidx.compose.runtime.getValue
2826
import androidx.compose.runtime.remember
29-
import androidx.compose.ui.Alignment
3027
import androidx.compose.ui.Modifier
3128
import androidx.compose.ui.platform.LocalContext
3229
import androidx.compose.ui.res.pluralStringResource
@@ -187,6 +184,14 @@ private fun AddMembersScreenUi(
187184
if (uiState.isLookingUpRecipient) {
188185
Dialogs.IndeterminateProgressDialog()
189186
}
187+
},
188+
floatingActionButton = {
189+
Buttons.MediumTonal(
190+
enabled = uiState.newSelections.isNotEmpty(),
191+
onClick = callbacks::onDoneClicked
192+
) {
193+
Text(text = stringResource(R.string.AddMembersActivity__done))
194+
}
190195
}
191196
)
192197
}
@@ -197,37 +202,22 @@ private fun AddMembersRecipientPicker(
197202
callbacks: UiCallbacks,
198203
modifier: Modifier = Modifier
199204
) {
200-
Box(modifier = modifier) {
201-
RecipientPicker(
202-
searchQuery = uiState.searchQuery,
203-
displayModes = setOf(RecipientPicker.DisplayMode.PUSH),
204-
selectionLimits = uiState.selectionLimits,
205-
preselectedRecipients = uiState.existingMembersMinusSelf,
206-
pendingRecipientSelections = uiState.pendingRecipientSelections,
207-
isRefreshing = false,
208-
listBottomPadding = 64.dp,
209-
clipListToPadding = false,
210-
callbacks = RecipientPickerCallbacks(
211-
listActions = callbacks,
212-
findByUsername = callbacks,
213-
findByPhoneNumber = callbacks
214-
),
215-
modifier = modifier.fillMaxSize()
216-
)
217-
218-
Box(
219-
modifier = Modifier
220-
.align(Alignment.BottomEnd)
221-
.padding(start = 16.dp, end = 16.dp, bottom = 16.dp)
222-
) {
223-
Buttons.MediumTonal(
224-
enabled = uiState.newSelections.isNotEmpty(),
225-
onClick = callbacks::onDoneClicked
226-
) {
227-
Text(text = stringResource(R.string.AddMembersActivity__done))
228-
}
229-
}
230-
}
205+
RecipientPicker(
206+
searchQuery = uiState.searchQuery,
207+
displayModes = setOf(RecipientPicker.DisplayMode.PUSH),
208+
selectionLimits = uiState.selectionLimits,
209+
preselectedRecipients = uiState.existingMembersMinusSelf,
210+
pendingRecipientSelections = uiState.pendingRecipientSelections,
211+
isRefreshing = false,
212+
listBottomPadding = 64.dp,
213+
clipListToPadding = false,
214+
callbacks = RecipientPickerCallbacks(
215+
listActions = callbacks,
216+
findByUsername = callbacks,
217+
findByPhoneNumber = callbacks
218+
),
219+
modifier = modifier.fillMaxSize()
220+
)
231221
}
232222

233223
private interface UiCallbacks :

app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/CreateGroupActivity.kt

Lines changed: 46 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,7 @@ import androidx.compose.animation.EnterTransition
2121
import androidx.compose.animation.ExitTransition
2222
import androidx.compose.animation.SizeTransform
2323
import androidx.compose.animation.core.tween
24-
import androidx.compose.foundation.layout.Box
2524
import androidx.compose.foundation.layout.fillMaxSize
26-
import androidx.compose.foundation.layout.padding
2725
import androidx.compose.material3.ExperimentalMaterial3Api
2826
import androidx.compose.material3.FilledTonalIconButton
2927
import androidx.compose.material3.Icon
@@ -34,7 +32,6 @@ import androidx.compose.runtime.Composable
3432
import androidx.compose.runtime.LaunchedEffect
3533
import androidx.compose.runtime.getValue
3634
import androidx.compose.runtime.remember
37-
import androidx.compose.ui.Alignment
3835
import androidx.compose.ui.Modifier
3936
import androidx.compose.ui.graphics.vector.ImageVector
4037
import androidx.compose.ui.platform.LocalContext
@@ -185,6 +182,35 @@ private fun CreateGroupScreenUi(
185182
if (uiState.isLookingUpRecipient) {
186183
Dialogs.IndeterminateProgressDialog()
187184
}
185+
},
186+
floatingActionButton = {
187+
AnimatedContent(
188+
targetState = uiState.newSelections.isNotEmpty(),
189+
transitionSpec = {
190+
ContentTransform(
191+
targetContentEnter = EnterTransition.None,
192+
initialContentExit = ExitTransition.None
193+
) using SizeTransform(sizeAnimationSpec = { _, _ -> tween(300) })
194+
}
195+
) { hasSelectedContacts ->
196+
if (hasSelectedContacts) {
197+
FilledTonalIconButton(
198+
onClick = callbacks::onNextClicked,
199+
content = {
200+
Icon(
201+
imageVector = ImageVector.vectorResource(R.drawable.ic_arrow_end_24),
202+
contentDescription = stringResource(R.string.CreateGroupActivity__accessibility_next)
203+
)
204+
}
205+
)
206+
} else {
207+
Buttons.MediumTonal(
208+
onClick = callbacks::onNextClicked
209+
) {
210+
Text(text = stringResource(R.string.CreateGroupActivity__skip))
211+
}
212+
}
213+
}
188214
}
189215
)
190216
}
@@ -195,56 +221,23 @@ private fun CreateGroupRecipientPicker(
195221
callbacks: UiCallbacks,
196222
modifier: Modifier = Modifier
197223
) {
198-
Box(modifier = modifier) {
199-
RecipientPicker(
200-
searchQuery = uiState.searchQuery,
201-
displayModes = setOf(RecipientPicker.DisplayMode.PUSH),
202-
selectionLimits = uiState.selectionLimits,
203-
pendingRecipientSelections = uiState.pendingRecipientSelections,
204-
isRefreshing = false,
205-
listBottomPadding = 64.dp,
206-
clipListToPadding = false,
207-
callbacks = remember(callbacks) {
208-
RecipientPickerCallbacks(
209-
listActions = callbacks,
210-
findByUsername = callbacks,
211-
findByPhoneNumber = callbacks
212-
)
213-
},
214-
modifier = modifier.fillMaxSize()
215-
)
216-
217-
AnimatedContent(
218-
targetState = uiState.newSelections.isNotEmpty(),
219-
transitionSpec = {
220-
ContentTransform(
221-
targetContentEnter = EnterTransition.None,
222-
initialContentExit = ExitTransition.None
223-
) using SizeTransform(sizeAnimationSpec = { _, _ -> tween(300) })
224-
},
225-
modifier = Modifier
226-
.align(Alignment.BottomEnd)
227-
.padding(start = 16.dp, end = 16.dp, bottom = 16.dp)
228-
) { hasSelectedContacts ->
229-
if (hasSelectedContacts) {
230-
FilledTonalIconButton(
231-
onClick = callbacks::onNextClicked,
232-
content = {
233-
Icon(
234-
imageVector = ImageVector.vectorResource(R.drawable.ic_arrow_end_24),
235-
contentDescription = stringResource(R.string.CreateGroupActivity__accessibility_next)
236-
)
237-
}
238-
)
239-
} else {
240-
Buttons.MediumTonal(
241-
onClick = callbacks::onNextClicked
242-
) {
243-
Text(text = stringResource(R.string.CreateGroupActivity__skip))
244-
}
245-
}
246-
}
247-
}
224+
RecipientPicker(
225+
searchQuery = uiState.searchQuery,
226+
displayModes = setOf(RecipientPicker.DisplayMode.PUSH),
227+
selectionLimits = uiState.selectionLimits,
228+
pendingRecipientSelections = uiState.pendingRecipientSelections,
229+
isRefreshing = false,
230+
listBottomPadding = 64.dp,
231+
clipListToPadding = false,
232+
callbacks = remember(callbacks) {
233+
RecipientPickerCallbacks(
234+
listActions = callbacks,
235+
findByUsername = callbacks,
236+
findByPhoneNumber = callbacks
237+
)
238+
},
239+
modifier = modifier.fillMaxSize()
240+
)
248241
}
249242

250243
private interface UiCallbacks :

app/src/main/java/org/thoughtcrime/securesms/recipients/ui/RecipientPickerScaffold.kt

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,13 @@ package org.thoughtcrime.securesms.recipients.ui
77

88
import androidx.compose.foundation.background
99
import androidx.compose.foundation.layout.Box
10+
import androidx.compose.foundation.layout.BoxScope
11+
import androidx.compose.foundation.layout.ExperimentalLayoutApi
12+
import androidx.compose.foundation.layout.WindowInsets
1013
import androidx.compose.foundation.layout.fillMaxSize
14+
import androidx.compose.foundation.layout.imePadding
15+
import androidx.compose.foundation.layout.isImeVisible
16+
import androidx.compose.foundation.layout.padding
1117
import androidx.compose.foundation.layout.widthIn
1218
import androidx.compose.material3.ExperimentalMaterial3Api
1319
import androidx.compose.material3.MaterialTheme
@@ -23,6 +29,7 @@ import androidx.compose.ui.graphics.Color
2329
import androidx.compose.ui.graphics.vector.ImageVector
2430
import androidx.compose.ui.res.stringResource
2531
import androidx.compose.ui.res.vectorResource
32+
import androidx.compose.ui.unit.dp
2633
import org.signal.core.ui.compose.AllDevicePreviews
2734
import org.signal.core.ui.compose.Previews
2835
import org.signal.core.ui.compose.Scaffolds
@@ -36,15 +43,16 @@ import org.thoughtcrime.securesms.window.rememberAppScaffoldNavigator
3643
/**
3744
* Provides the common adaptive layout structure for recipient picker screens.
3845
*/
39-
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3AdaptiveApi::class)
46+
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3AdaptiveApi::class, ExperimentalLayoutApi::class)
4047
@Composable
4148
fun RecipientPickerScaffold(
4249
title: String,
4350
forceSplitPane: Boolean,
4451
onNavigateUpClick: () -> Unit,
4552
topAppBarActions: @Composable () -> Unit,
4653
snackbarHostState: SnackbarHostState,
47-
primaryContent: @Composable () -> Unit
54+
primaryContent: @Composable () -> Unit,
55+
floatingActionButton: (@Composable () -> Unit)? = null
4856
) {
4957
val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass
5058
val isSplitPane = windowSizeClass.isSplitPane(forceSplitPane = forceSplitPane)
@@ -68,7 +76,10 @@ fun RecipientPickerScaffold(
6876
modifier = Modifier.fillMaxSize()
6977
)
7078
} else {
71-
primaryContent()
79+
Box {
80+
primaryContent()
81+
FloatingActionButtonContainer(floatingActionButton)
82+
}
7283
}
7384
},
7485

@@ -79,6 +90,7 @@ fun RecipientPickerScaffold(
7990
) {
8091
Box(modifier = Modifier.widthIn(max = windowSizeClass.detailPaneMaxContentWidth)) {
8192
primaryContent()
93+
FloatingActionButtonContainer(floatingActionButton)
8294
}
8395
}
8496
},
@@ -93,6 +105,27 @@ fun RecipientPickerScaffold(
93105
)
94106
}
95107

108+
@OptIn(ExperimentalLayoutApi::class)
109+
@Composable
110+
private fun BoxScope.FloatingActionButtonContainer(
111+
button: (@Composable () -> Unit)?
112+
) {
113+
if (button != null) {
114+
Box(
115+
modifier = Modifier
116+
.align(Alignment.BottomEnd)
117+
.imePadding()
118+
.padding(
119+
start = 16.dp,
120+
end = 16.dp,
121+
bottom = if (WindowInsets.isImeVisible) 0.dp else 16.dp
122+
)
123+
) {
124+
button()
125+
}
126+
}
127+
}
128+
96129
@AllDevicePreviews
97130
@Composable
98131
private fun RecipientPickerScaffoldPreview() {

0 commit comments

Comments
 (0)