Add confirmation dialog when inviting users with unknown identities (#6523)

* feat: Add confirmation modal when inviting unknown users

* tests: Add preview tests for invite confirmation modal

* tests: Add unit tests for invite confirmation modal

* feat: Switch confirmation sheet contents based on identity state

* tests: Add history sharing unit tests for `DefaultStartDMActionTest`

* tests: Update snapshots for `CreateDmConfirmationBottomSheet`

* chore: Fix tiny nits

* fix: Remove default param on `ConfirmingStartDmWithMatrixUser`

* refactor: Use new AsyncAction over boolean flag

* fix: Add sleeps to tests

* refactor: Remove `PromptOrInvite` and switch on async action

* fix: Remove redundant `assertThat`

* feat: Alllow invite confirmation modal to be dismissed

* tests: Update snapshots for InvitePeopleView

* fix: Adjust `CreateDmConfirmationBottomSheet` to conform to design

* feat: Use localazy translations and plurals

* fix: When users are unselected, unselect them in search results too

* tests: Use aMatrixUserList to provide multiple users

* Update screenshots

* fix: Add missing parameter in UserProfilePresenterTest

---------

Co-authored-by: Andy Balaam <andy.balaam@matrix.org>
Co-authored-by: ElementBot <android@element.io>
This commit is contained in:
Skye Elliot 2026-04-15 10:25:58 +01:00 committed by GitHub
parent e0554bbaf3
commit 897c68e7b7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
30 changed files with 618 additions and 58 deletions

View file

@ -8,6 +8,7 @@
package io.element.android.libraries.matrix.ui.components
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
@ -22,9 +23,13 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.libraries.designsystem.atomic.molecules.ButtonRowMolecule
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
import io.element.android.libraries.designsystem.components.BigIcon
import io.element.android.libraries.designsystem.components.avatar.Avatar
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.components.avatar.AvatarType
@ -33,6 +38,7 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.Button
import io.element.android.libraries.designsystem.theme.components.IconSource
import io.element.android.libraries.designsystem.theme.components.ModalBottomSheet
import io.element.android.libraries.designsystem.theme.components.OutlinedButton
import io.element.android.libraries.designsystem.theme.components.TextButton
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.ui.R
@ -48,10 +54,23 @@ import io.element.android.libraries.ui.strings.CommonStrings
@Composable
fun CreateDmConfirmationBottomSheet(
matrixUser: MatrixUser,
enableKeyShareOnInvite: Boolean,
isUserIdentityUnknown: Boolean,
onSendInvite: () -> Unit,
onDismiss: () -> Unit,
modifier: Modifier = Modifier,
) {
val titleContent = if (enableKeyShareOnInvite && isUserIdentityUnknown) {
stringResource(R.string.screen_bottom_sheet_create_dm_unknown_user_title)
} else {
stringResource(R.string.screen_bottom_sheet_create_dm_title)
}
val descriptionContent = if (enableKeyShareOnInvite && isUserIdentityUnknown) {
stringResource(R.string.screen_bottom_sheet_create_dm_unknown_user_content)
} else {
stringResource(R.string.screen_bottom_sheet_create_dm_message, matrixUser.getFullName())
}
ModalBottomSheet(
modifier = modifier,
onDismissRequest = onDismiss,
@ -63,47 +82,95 @@ fun CreateDmConfirmationBottomSheet(
.padding(top = 24.dp, bottom = 16.dp, start = 16.dp, end = 16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Avatar(
avatarData = matrixUser.getAvatarData(AvatarSize.DmCreationConfirmation),
avatarType = AvatarType.User,
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = stringResource(R.string.screen_bottom_sheet_create_dm_title),
style = ElementTheme.typography.fontHeadingMdBold,
color = ElementTheme.colors.textPrimary,
textAlign = TextAlign.Center,
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = stringResource(R.string.screen_bottom_sheet_create_dm_message, matrixUser.getFullName()),
style = ElementTheme.typography.fontBodyMdRegular,
color = ElementTheme.colors.textSecondary,
textAlign = TextAlign.Center,
)
Spacer(modifier = Modifier.height(40.dp))
Button(
modifier = Modifier.fillMaxWidth(),
onClick = onSendInvite,
leadingIcon = IconSource.Vector(CompoundIcons.UserAdd()),
text = stringResource(R.string.screen_bottom_sheet_create_dm_confirmation_button_title),
)
Spacer(modifier = Modifier.height(16.dp))
TextButton(
modifier = Modifier.fillMaxWidth(),
onClick = onDismiss,
text = stringResource(CommonStrings.action_cancel),
)
if (isUserIdentityUnknown) {
IconTitleSubtitleMolecule(
modifier = Modifier.padding(
bottom = 16.dp,
start = 16.dp,
end = 16.dp,
),
title = titleContent,
subTitle = descriptionContent,
iconStyle = BigIcon.Style.Default(CompoundIcons.UserAddSolid()),
)
MatrixUserRow(matrixUser)
Spacer(modifier = Modifier.height(32.dp))
ButtonRowMolecule(
horizontalArrangement = Arrangement.spacedBy(16.dp),
) {
OutlinedButton(
modifier = Modifier.weight(1f),
text = stringResource(CommonStrings.action_cancel),
onClick = onDismiss
)
Button(
modifier = Modifier.weight(1f),
text = stringResource(CommonStrings.action_continue),
onClick = onSendInvite
)
}
Spacer(modifier = Modifier.height(32.dp))
} else {
Avatar(
avatarData = matrixUser.getAvatarData(AvatarSize.DmCreationConfirmation),
avatarType = AvatarType.User,
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = titleContent,
style = ElementTheme.typography.fontHeadingMdBold,
color = ElementTheme.colors.textPrimary,
textAlign = TextAlign.Center,
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = descriptionContent,
style = ElementTheme.typography.fontBodyMdRegular,
color = ElementTheme.colors.textSecondary,
textAlign = TextAlign.Center,
)
Spacer(modifier = Modifier.height(40.dp))
Button(
modifier = Modifier.fillMaxWidth(),
onClick = onSendInvite,
leadingIcon = IconSource.Vector(CompoundIcons.UserAdd()),
text = stringResource(R.string.screen_bottom_sheet_create_dm_confirmation_button_title),
)
Spacer(modifier = Modifier.height(16.dp))
TextButton(
modifier = Modifier.fillMaxWidth(),
onClick = onDismiss,
text = stringResource(CommonStrings.action_cancel),
)
}
}
}
}
@PreviewsDayNight
@Composable
internal fun CreateDmConfirmationBottomSheetPreview(@PreviewParameter(MatrixUserProvider::class) matrixUser: MatrixUser) = ElementPreview {
internal fun CreateDmConfirmationBottomSheetPreview(@PreviewParameter(
CreateDmConfirmationBottomSheetStateProvider::class
) state: CreateDmConfirmationBottomSheetState) = ElementPreview {
CreateDmConfirmationBottomSheet(
matrixUser = matrixUser,
matrixUser = state.matrixUser,
enableKeyShareOnInvite = state.enableKeyShareOnInvite,
isUserIdentityUnknown = state.isUserIdentityUnknown,
onSendInvite = {},
onDismiss = {},
)
}
data class CreateDmConfirmationBottomSheetState(
val matrixUser: MatrixUser,
val enableKeyShareOnInvite: Boolean,
val isUserIdentityUnknown: Boolean,
)
class CreateDmConfirmationBottomSheetStateProvider : PreviewParameterProvider<CreateDmConfirmationBottomSheetState> {
override val values = sequenceOf(
CreateDmConfirmationBottomSheetState(matrixUser = aMatrixUser(), enableKeyShareOnInvite = false, isUserIdentityUnknown = false),
CreateDmConfirmationBottomSheetState(matrixUser = aMatrixUser(), enableKeyShareOnInvite = true, isUserIdentityUnknown = false),
CreateDmConfirmationBottomSheetState(matrixUser = aMatrixUser(), enableKeyShareOnInvite = true, isUserIdentityUnknown = true),
)
}