Fix crash when starting a DM (#6419)

`AnchoredDraggable.requireOffset` was called before it was populated when displaying  `CreateDmConfirmationBottomSheet`, because the keyboard and the bottom sheet were causing conflicting animations related to the insets.

Hiding the keyboard before displaying the bottom sheet seems to fix the issue, and `skipPartiallyExpanded` results in a better UX (and also worked around the issue by itself).
This commit is contained in:
Jorge Martin Espinosa
2026-03-23 16:00:04 +01:00
committed by GitHub
parent 93ab9f43dc
commit 9074692189
3 changed files with 44 additions and 4 deletions

View File

@@ -21,8 +21,10 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
@@ -31,6 +33,7 @@ import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.features.startchat.api.ConfirmingStartDmWithMatrixUser
import io.element.android.features.startchat.impl.R
import io.element.android.features.startchat.impl.components.UserListView
import io.element.android.libraries.androidutils.ui.hideKeyboardAndAwaitAnimation
import io.element.android.libraries.designsystem.components.async.AsyncActionView
import io.element.android.libraries.designsystem.components.async.AsyncActionViewDefaults
import io.element.android.libraries.designsystem.components.button.BackButton
@@ -47,6 +50,7 @@ import io.element.android.libraries.matrix.ui.components.CreateDmConfirmationBot
import io.element.android.libraries.matrix.ui.components.MatrixUserRow
import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.launch
@Composable
fun StartChatView(
@@ -59,6 +63,8 @@ fun StartChatView(
onRoomDirectorySearchClick: () -> Unit,
modifier: Modifier = Modifier,
) {
val coroutineScope = rememberCoroutineScope()
Scaffold(
modifier = modifier.fillMaxWidth(),
topBar = {
@@ -73,6 +79,8 @@ fun StartChatView(
.consumeWindowInsets(paddingValues),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
val view = LocalView.current
UserListView(
modifier = Modifier.fillMaxWidth(),
// Do not render suggestions in this case, the suggestion will be rendered
@@ -81,7 +89,10 @@ fun StartChatView(
recentDirectRooms = persistentListOf(),
),
onSelectUser = {
coroutineScope.launch {
view.hideKeyboardAndAwaitAnimation()
state.eventSink(StartChatEvents.StartDM(it))
}
},
onDeselectUser = { },
)

View File

@@ -9,12 +9,15 @@
package io.element.android.libraries.androidutils.ui
import android.os.Build
import android.os.Bundle
import android.os.ResultReceiver
import android.view.View
import android.view.ViewTreeObserver
import android.view.WindowInsets
import android.view.inputmethod.InputMethodManager
import androidx.core.content.getSystemService
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.sync.Mutex
import kotlin.coroutines.resume
fun View.hideKeyboard() {
@@ -22,6 +25,32 @@ fun View.hideKeyboard() {
imm?.hideSoftInputFromWindow(windowToken, 0)
}
suspend fun View.hideKeyboardAndAwaitAnimation() {
val imm = context?.getSystemService<InputMethodManager>()
val mutex = Mutex()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
setOnApplyWindowInsetsListener { view, insets ->
if (!insets.isVisible(WindowInsets.Type.ime())) {
mutex.unlock()
}
insets
}
imm?.hideSoftInputFromWindow(windowToken, 0)
} else {
@Suppress("DEPRECATION")
imm?.hideSoftInputFromWindow(windowToken, 0, object : ResultReceiver(null) {
override fun onReceiveResult(resultCode: Int, resultData: Bundle?) {
if (resultCode == InputMethodManager.RESULT_UNCHANGED_HIDDEN ||
resultCode == InputMethodManager.RESULT_HIDDEN) {
mutex.unlock()
}
}
})
}
mutex.lock()
}
fun View.showKeyboard(andRequestFocus: Boolean = false) {
if (andRequestFocus) {
requestFocus()

View File

@@ -15,6 +15,7 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Text
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -54,14 +55,14 @@ fun CreateDmConfirmationBottomSheet(
ModalBottomSheet(
modifier = modifier,
onDismissRequest = onDismiss,
sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true),
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
.padding(top = 24.dp, bottom = 16.dp, start = 16.dp, end = 16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Spacer(modifier = Modifier.height(24.dp))
Avatar(
avatarData = matrixUser.getAvatarData(AvatarSize.DmCreationConfirmation),
avatarType = AvatarType.User,
@@ -93,7 +94,6 @@ fun CreateDmConfirmationBottomSheet(
onClick = onDismiss,
text = stringResource(CommonStrings.action_cancel),
)
Spacer(modifier = Modifier.height(16.dp))
}
}
}