mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-10-01 17:31:14 +01:00
Remove the unused profile screen.
This commit is contained in:
committed by
Alex Hart
parent
b87b12ad97
commit
aed11c8a1d
@@ -82,9 +82,6 @@ import org.signal.registration.screens.countrycode.Country
|
||||
import org.signal.registration.screens.countrycode.CountryCodePickerRepository
|
||||
import org.signal.registration.screens.countrycode.CountryCodePickerScreen
|
||||
import org.signal.registration.screens.countrycode.CountryCodePickerViewModel
|
||||
import org.signal.registration.screens.createprofile.CreateProfileScreen
|
||||
import org.signal.registration.screens.createprofile.CreateProfileScreenEvents
|
||||
import org.signal.registration.screens.createprofile.CreateProfileViewModel
|
||||
import org.signal.registration.screens.devicetransfer.complete.DeviceTransferCompleteScreen
|
||||
import org.signal.registration.screens.devicetransfer.complete.DeviceTransferCompleteViewModel
|
||||
import org.signal.registration.screens.devicetransfer.instructions.DeviceTransferInstructionsScreen
|
||||
@@ -93,8 +90,6 @@ import org.signal.registration.screens.devicetransfer.progress.DeviceTransferPro
|
||||
import org.signal.registration.screens.devicetransfer.progress.DeviceTransferProgressViewModel
|
||||
import org.signal.registration.screens.devicetransfer.setup.DeviceTransferSetupScreen
|
||||
import org.signal.registration.screens.devicetransfer.setup.DeviceTransferSetupViewModel
|
||||
import org.signal.registration.screens.discoverability.PhoneNumberDiscoverabilityScreen
|
||||
import org.signal.registration.screens.discoverability.PhoneNumberDiscoverabilityViewModel
|
||||
import org.signal.registration.screens.linkaccount.LinkAccountScreen
|
||||
import org.signal.registration.screens.linkaccount.LinkAccountScreenAction
|
||||
import org.signal.registration.screens.linkaccount.LinkAccountViewModel
|
||||
@@ -394,12 +389,6 @@ sealed interface RegistrationRoute : NavKey, Parcelable {
|
||||
@Serializable
|
||||
data object DeviceTransferComplete : RegistrationRoute
|
||||
|
||||
@Serializable
|
||||
data object Profile : RegistrationRoute
|
||||
|
||||
@Serializable
|
||||
data class PhoneNumberDiscoverability(val initialDiscoverable: Boolean) : RegistrationRoute
|
||||
|
||||
@Serializable
|
||||
data object FullyComplete : RegistrationRoute
|
||||
}
|
||||
@@ -409,7 +398,6 @@ private const val COUNTRY_CODE_RESULT = "country_code_result"
|
||||
private const val BACKUP_CREDENTIAL_RESULT = "backup_credential_result"
|
||||
private const val AEP_FOR_LOCAL_BACKUP_RESULT = "aep_for_local_backup_result"
|
||||
private const val LOCAL_BACKUP_RESTORE_RESULT = "local_backup_restore_result"
|
||||
private const val PHONE_NUMBER_DISCOVERABILITY_RESULT = "phone_number_discoverability_result"
|
||||
private const val TWO_FACTOR_CODE_RESULT = "two_factor_code_result"
|
||||
|
||||
/** Opens [url] in a browser, surfacing a toast if the device has none. */
|
||||
@@ -1321,42 +1309,6 @@ private fun EntryProviderScope<NavKey>.navigationEntries(
|
||||
)
|
||||
}
|
||||
|
||||
entry<RegistrationRoute.Profile> {
|
||||
val viewModel: CreateProfileViewModel = viewModel {
|
||||
CreateProfileViewModel(
|
||||
repository = registrationRepository,
|
||||
parentEventEmitter = registrationViewModel::onEvent
|
||||
)
|
||||
}
|
||||
val state by viewModel.state.collectAsStateWithLifecycle()
|
||||
|
||||
ResultEffect<Boolean>(registrationViewModel.resultBus, PHONE_NUMBER_DISCOVERABILITY_RESULT) { discoverable ->
|
||||
viewModel.onEvent(CreateProfileScreenEvents.DiscoverabilityChanged(discoverable))
|
||||
}
|
||||
|
||||
CreateProfileScreen(
|
||||
state = state,
|
||||
onEvent = viewModel::onEvent
|
||||
)
|
||||
}
|
||||
|
||||
entry<RegistrationRoute.PhoneNumberDiscoverability> { key ->
|
||||
val viewModel: PhoneNumberDiscoverabilityViewModel = viewModel {
|
||||
PhoneNumberDiscoverabilityViewModel(
|
||||
initialDiscoverable = key.initialDiscoverable,
|
||||
parentEventEmitter = registrationViewModel::onEvent,
|
||||
resultBus = registrationViewModel.resultBus,
|
||||
resultKey = PHONE_NUMBER_DISCOVERABILITY_RESULT
|
||||
)
|
||||
}
|
||||
val state by viewModel.state.collectAsStateWithLifecycle()
|
||||
|
||||
PhoneNumberDiscoverabilityScreen(
|
||||
state = state,
|
||||
onEvent = viewModel::onEvent
|
||||
)
|
||||
}
|
||||
|
||||
entry<RegistrationRoute.FullyComplete> {
|
||||
LaunchedEffect(Unit) {
|
||||
onRegistrationComplete()
|
||||
|
||||
-419
@@ -1,419 +0,0 @@
|
||||
/*
|
||||
* Copyright 2026 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.registration.screens.createprofile
|
||||
|
||||
import android.graphics.BitmapFactory
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.PickVisualMediaRequest
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.signal.core.ui.WindowBreakpoint
|
||||
import org.signal.core.ui.compose.AllDevicePreviews
|
||||
import org.signal.core.ui.compose.Buttons
|
||||
import org.signal.core.ui.compose.Dialogs
|
||||
import org.signal.core.ui.compose.Previews
|
||||
import org.signal.core.ui.compose.SignalIcons
|
||||
import org.signal.core.ui.compose.TextFields
|
||||
import org.signal.core.ui.rememberWindowBreakpoint
|
||||
import org.signal.registration.R
|
||||
import org.signal.registration.screens.RegistrationScaffold
|
||||
import org.signal.registration.test.TestTags
|
||||
|
||||
/**
|
||||
* Profile creation screen for the registration flow. Captures the user's given name, family name,
|
||||
* avatar, and phone-number discoverability before completing registration.
|
||||
*
|
||||
* Dispatches to a per-[WindowBreakpoint] layout following the pattern in `WelcomeScreen`. All three
|
||||
* breakpoints currently share the [CompactLayout] body — Medium/Large variants can be split out
|
||||
* later without changing this entry point.
|
||||
*/
|
||||
@Composable
|
||||
fun CreateProfileScreen(
|
||||
state: CreateProfileState,
|
||||
onEvent: (CreateProfileScreenEvents) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val pickAvatarLauncher = rememberLauncherForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri ->
|
||||
if (uri != null) {
|
||||
val bytes = runCatching {
|
||||
context.contentResolver.openInputStream(uri)?.use { it.readBytes() }
|
||||
}.getOrNull()
|
||||
if (bytes != null) {
|
||||
onEvent(CreateProfileScreenEvents.AvatarSelected(bytes))
|
||||
}
|
||||
}
|
||||
}
|
||||
val onAvatarClick = {
|
||||
pickAvatarLauncher.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly))
|
||||
}
|
||||
|
||||
if (state.showUploadFailedDialog) {
|
||||
Dialogs.SimpleMessageDialog(
|
||||
message = stringResource(R.string.VerificationCodeScreen__an_unexpected_error_occurred),
|
||||
dismiss = stringResource(android.R.string.ok),
|
||||
onDismiss = { onEvent(CreateProfileScreenEvents.UploadFailedDialogDismissed) }
|
||||
)
|
||||
}
|
||||
|
||||
if (state.isLoading) {
|
||||
Box(modifier = modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
CircularProgressIndicator()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
when (rememberWindowBreakpoint()) {
|
||||
is WindowBreakpoint.Small -> CompactLayout(state, onEvent, onAvatarClick, modifier)
|
||||
is WindowBreakpoint.Medium -> MediumLayout(state, onEvent, onAvatarClick, modifier)
|
||||
is WindowBreakpoint.Large -> LargeLayout(state, onEvent, onAvatarClick, modifier)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CompactLayout(
|
||||
state: CreateProfileState,
|
||||
onEvent: (CreateProfileScreenEvents) -> Unit,
|
||||
onAvatarClick: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val givenNameInteractionSource = remember { MutableInteractionSource() }
|
||||
val familyNameInteractionSource = remember { MutableInteractionSource() }
|
||||
|
||||
RegistrationScaffold(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.testTag(TestTags.CREATE_PROFILE_SCREEN),
|
||||
content = {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = 24.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.CreateProfileScreen__set_up_your_profile),
|
||||
style = MaterialTheme.typography.headlineMedium,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.CreateProfileScreen__your_profile_is_end_to_end_encrypted),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
|
||||
Avatar(avatarBytes = state.avatar, onClick = onAvatarClick)
|
||||
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
|
||||
OutlinedTextField(
|
||||
value = state.givenName,
|
||||
onValueChange = { onEvent(CreateProfileScreenEvents.GivenNameChanged(it)) },
|
||||
label = { TextFields.Label(stringResource(R.string.CreateProfileScreen__first_name_required), state.givenName.isNotEmpty(), givenNameInteractionSource) },
|
||||
interactionSource = givenNameInteractionSource,
|
||||
singleLine = true,
|
||||
enabled = !state.isSubmitting,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
capitalization = KeyboardCapitalization.Words,
|
||||
imeAction = ImeAction.Next
|
||||
),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(TestTags.CREATE_PROFILE_GIVEN_NAME_FIELD)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
OutlinedTextField(
|
||||
value = state.familyName,
|
||||
onValueChange = { onEvent(CreateProfileScreenEvents.FamilyNameChanged(it)) },
|
||||
label = { TextFields.Label(stringResource(R.string.CreateProfileScreen__last_name_optional), state.familyName.isNotEmpty(), familyNameInteractionSource) },
|
||||
interactionSource = familyNameInteractionSource,
|
||||
singleLine = true,
|
||||
enabled = !state.isSubmitting,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
capitalization = KeyboardCapitalization.Words,
|
||||
imeAction = ImeAction.Done
|
||||
),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(TestTags.CREATE_PROFILE_FAMILY_NAME_FIELD)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
WhoCanFindMeRow(
|
||||
discoverable = state.discoverableByPhoneNumber,
|
||||
enabled = !state.isSubmitting,
|
||||
onClick = { onEvent(CreateProfileScreenEvents.WhoCanFindMeClicked) }
|
||||
)
|
||||
}
|
||||
},
|
||||
footer = {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 24.dp, vertical = 16.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Buttons.LargeTonal(
|
||||
onClick = { onEvent(CreateProfileScreenEvents.NextClicked) },
|
||||
enabled = state.isFormValid && !state.isSubmitting,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.widthIn(max = 320.dp)
|
||||
.testTag(TestTags.CREATE_PROFILE_NEXT_BUTTON)
|
||||
) {
|
||||
if (state.isSubmitting) {
|
||||
CircularProgressIndicator(
|
||||
color = MaterialTheme.colorScheme.onSecondaryContainer,
|
||||
strokeWidth = 2.dp,
|
||||
modifier = Modifier.size(20.dp)
|
||||
)
|
||||
} else {
|
||||
Text(stringResource(R.string.CreateProfileScreen__next))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MediumLayout(
|
||||
state: CreateProfileState,
|
||||
onEvent: (CreateProfileScreenEvents) -> Unit,
|
||||
onAvatarClick: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
// TODO [registration] dedicated medium-width layout. For now, reuse the compact body.
|
||||
CompactLayout(state = state, onEvent = onEvent, onAvatarClick = onAvatarClick, modifier = modifier)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LargeLayout(
|
||||
state: CreateProfileState,
|
||||
onEvent: (CreateProfileScreenEvents) -> Unit,
|
||||
onAvatarClick: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
// TODO [registration] dedicated large-width layout. For now, reuse the compact body.
|
||||
CompactLayout(state = state, onEvent = onEvent, onAvatarClick = onAvatarClick, modifier = modifier)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun WhoCanFindMeRow(
|
||||
discoverable: Boolean,
|
||||
enabled: Boolean,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(enabled = enabled, onClick = onClick)
|
||||
.padding(vertical = 12.dp)
|
||||
.testTag(TestTags.CREATE_PROFILE_WHO_CAN_FIND_ME_ROW),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
painter = if (discoverable) painterResource(R.drawable.symbol_group_24) else SignalIcons.Lock.painter,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.padding(horizontal = 8.dp))
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
text = stringResource(R.string.WhoCanSeeMyPhoneNumberFragment__who_can_find_me_by_number),
|
||||
style = MaterialTheme.typography.bodyLarge
|
||||
)
|
||||
Text(
|
||||
text = stringResource(
|
||||
if (discoverable) R.string.PhoneNumberPrivacy_everyone else R.string.PhoneNumberPrivacy_nobody
|
||||
),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
Icon(
|
||||
painter = SignalIcons.ChevronRight.painter,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Avatar(
|
||||
avatarBytes: ByteArray?,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val bitmap = remember(avatarBytes) {
|
||||
avatarBytes?.let {
|
||||
runCatching { BitmapFactory.decodeByteArray(it, 0, it.size) }.getOrNull()
|
||||
}
|
||||
}
|
||||
|
||||
Box(modifier = modifier.size(112.dp)) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(112.dp)
|
||||
.clip(CircleShape)
|
||||
.background(MaterialTheme.colorScheme.surfaceVariant)
|
||||
.clickable(onClick = onClick),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
if (bitmap != null) {
|
||||
androidx.compose.foundation.Image(
|
||||
bitmap = bitmap.asImageBitmap(),
|
||||
contentDescription = stringResource(R.string.CreateProfileScreen__set_avatar_description),
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = Modifier.fillMaxSize()
|
||||
)
|
||||
} else {
|
||||
Icon(
|
||||
painter = SignalIcons.Camera.painter,
|
||||
contentDescription = stringResource(R.string.CreateProfileScreen__set_avatar_description),
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.size(40.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomEnd)
|
||||
.offset(x = 4.dp, y = 4.dp)
|
||||
.size(36.dp)
|
||||
.clip(CircleShape)
|
||||
.background(MaterialTheme.colorScheme.primary)
|
||||
.clickable(onClick = onClick),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Icon(
|
||||
painter = SignalIcons.Camera.painter,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onPrimary,
|
||||
modifier = Modifier.size(20.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllDevicePreviews
|
||||
@Composable
|
||||
private fun CreateProfileScreenLoadingPreview() {
|
||||
Previews.Preview {
|
||||
CreateProfileScreen(
|
||||
state = CreateProfileState(isLoading = true),
|
||||
onEvent = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@AllDevicePreviews
|
||||
@Composable
|
||||
private fun CreateProfileScreenEmptyPreview() {
|
||||
Previews.Preview {
|
||||
CreateProfileScreen(
|
||||
state = CreateProfileState(isLoading = false),
|
||||
onEvent = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@AllDevicePreviews
|
||||
@Composable
|
||||
private fun CreateProfileScreenWithNamePreview() {
|
||||
Previews.Preview {
|
||||
CreateProfileScreen(
|
||||
state = CreateProfileState(
|
||||
givenName = "Alice",
|
||||
familyName = "Anderson",
|
||||
isLoading = false
|
||||
),
|
||||
onEvent = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@AllDevicePreviews
|
||||
@Composable
|
||||
private fun CreateProfileScreenNobodyPreview() {
|
||||
Previews.Preview {
|
||||
CreateProfileScreen(
|
||||
state = CreateProfileState(
|
||||
givenName = "Alice",
|
||||
familyName = "Anderson",
|
||||
discoverableByPhoneNumber = false,
|
||||
isLoading = false
|
||||
),
|
||||
onEvent = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@AllDevicePreviews
|
||||
@Composable
|
||||
private fun CreateProfileScreenSubmittingPreview() {
|
||||
Previews.Preview {
|
||||
CreateProfileScreen(
|
||||
state = CreateProfileState(
|
||||
givenName = "Alice",
|
||||
familyName = "Anderson",
|
||||
isLoading = false,
|
||||
isSubmitting = true
|
||||
),
|
||||
onEvent = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
-29
@@ -1,29 +0,0 @@
|
||||
/*
|
||||
* Copyright 2026 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.registration.screens.createprofile
|
||||
|
||||
sealed class CreateProfileScreenEvents {
|
||||
data class GivenNameChanged(val value: String) : CreateProfileScreenEvents() {
|
||||
override fun toString(): String = "GivenNameChanged(value=${value.length} chars)"
|
||||
}
|
||||
data class FamilyNameChanged(val value: String) : CreateProfileScreenEvents() {
|
||||
override fun toString(): String = "FamilyNameChanged(value=${value.length} chars)"
|
||||
}
|
||||
data class AvatarSelected(val bytes: ByteArray) : CreateProfileScreenEvents() {
|
||||
override fun toString(): String = "AvatarSelected(${bytes.size} bytes)"
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is AvatarSelected) return false
|
||||
return bytes.contentEquals(other.bytes)
|
||||
}
|
||||
override fun hashCode(): Int = bytes.contentHashCode()
|
||||
}
|
||||
data object AvatarCleared : CreateProfileScreenEvents()
|
||||
data object WhoCanFindMeClicked : CreateProfileScreenEvents()
|
||||
data class DiscoverabilityChanged(val discoverable: Boolean) : CreateProfileScreenEvents()
|
||||
data object NextClicked : CreateProfileScreenEvents()
|
||||
data object UploadFailedDialogDismissed : CreateProfileScreenEvents()
|
||||
}
|
||||
-53
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
* Copyright 2026 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.registration.screens.createprofile
|
||||
|
||||
data class CreateProfileState(
|
||||
val givenName: String = "",
|
||||
val familyName: String = "",
|
||||
val avatar: ByteArray? = null,
|
||||
val discoverableByPhoneNumber: Boolean = true,
|
||||
val isLoading: Boolean = true,
|
||||
val isSubmitting: Boolean = false,
|
||||
val showUploadFailedDialog: Boolean = false
|
||||
) {
|
||||
|
||||
val isFormValid: Boolean
|
||||
get() = givenName.trim().isNotEmpty()
|
||||
|
||||
override fun toString(): String {
|
||||
return "CreateProfileState(givenName=${givenName.length} chars, familyName=${familyName.length} chars, avatar=${avatar?.size ?: 0} bytes, discoverableByPhoneNumber=$discoverableByPhoneNumber, isLoading=$isLoading, isSubmitting=$isSubmitting, showUploadFailedDialog=$showUploadFailedDialog)"
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is CreateProfileState) return false
|
||||
if (givenName != other.givenName) return false
|
||||
if (familyName != other.familyName) return false
|
||||
if (avatar != null) {
|
||||
if (other.avatar == null) return false
|
||||
if (!avatar.contentEquals(other.avatar)) return false
|
||||
} else if (other.avatar != null) {
|
||||
return false
|
||||
}
|
||||
if (discoverableByPhoneNumber != other.discoverableByPhoneNumber) return false
|
||||
if (isLoading != other.isLoading) return false
|
||||
if (isSubmitting != other.isSubmitting) return false
|
||||
if (showUploadFailedDialog != other.showUploadFailedDialog) return false
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = givenName.hashCode()
|
||||
result = 31 * result + familyName.hashCode()
|
||||
result = 31 * result + (avatar?.contentHashCode() ?: 0)
|
||||
result = 31 * result + discoverableByPhoneNumber.hashCode()
|
||||
result = 31 * result + isLoading.hashCode()
|
||||
result = 31 * result + isSubmitting.hashCode()
|
||||
result = 31 * result + showUploadFailedDialog.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
-139
@@ -1,139 +0,0 @@
|
||||
/*
|
||||
* Copyright 2026 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.registration.screens.createprofile
|
||||
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import org.signal.core.ui.compose.EventDrivenViewModel
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.libsignal.net.RequestResult
|
||||
import org.signal.registration.RegistrationFlowEvent
|
||||
import org.signal.registration.RegistrationRepository
|
||||
import org.signal.registration.RegistrationRoute
|
||||
import org.signal.registration.screens.util.navigateTo
|
||||
|
||||
/**
|
||||
* ViewModel for the registration profile-creation screen. Holds the user's typed name and selected
|
||||
* avatar bytes and submits them to [RegistrationRepository.setProfile] when the user advances.
|
||||
*/
|
||||
class CreateProfileViewModel(
|
||||
private val repository: RegistrationRepository,
|
||||
private val parentEventEmitter: (RegistrationFlowEvent) -> Unit
|
||||
) : EventDrivenViewModel<CreateProfileScreenEvents>(TAG, shouldLogEvents = true) {
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(CreateProfileViewModel::class)
|
||||
}
|
||||
|
||||
private val _state = MutableStateFlow(CreateProfileState())
|
||||
val state: StateFlow<CreateProfileState> = _state
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
val stored = repository.getStoredProfileData()
|
||||
Log.i(TAG, "[init] Loaded stored profile data. givenName=${stored.givenName.isNotEmpty()}, familyName=${stored.familyName.isNotEmpty()}, avatar=${stored.avatar != null}")
|
||||
|
||||
val seeded = _state.value.copy(
|
||||
givenName = stored.givenName,
|
||||
familyName = stored.familyName,
|
||||
avatar = stored.avatar,
|
||||
discoverableByPhoneNumber = stored.discoverableByPhoneNumber ?: true,
|
||||
isLoading = false
|
||||
)
|
||||
|
||||
if (stored.givenName.isNotEmpty() && stored.avatar != null) {
|
||||
Log.i(TAG, "[init] Profile name + avatar already present. Auto-submitting and skipping screen.")
|
||||
_state.value = seeded.copy(isSubmitting = true)
|
||||
submitProfile(seeded)
|
||||
} else {
|
||||
_state.value = seeded
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun processEvent(event: CreateProfileScreenEvents) {
|
||||
applyEvent(state.value, event, parentEventEmitter, repository) { _state.value = it }
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
suspend fun applyEvent(
|
||||
state: CreateProfileState,
|
||||
event: CreateProfileScreenEvents,
|
||||
parentEventEmitter: (RegistrationFlowEvent) -> Unit,
|
||||
repository: RegistrationRepository,
|
||||
stateEmitter: (CreateProfileState) -> Unit
|
||||
) {
|
||||
when (event) {
|
||||
is CreateProfileScreenEvents.GivenNameChanged -> {
|
||||
stateEmitter(state.copy(givenName = event.value))
|
||||
}
|
||||
is CreateProfileScreenEvents.FamilyNameChanged -> {
|
||||
stateEmitter(state.copy(familyName = event.value))
|
||||
}
|
||||
is CreateProfileScreenEvents.AvatarSelected -> {
|
||||
stateEmitter(state.copy(avatar = event.bytes))
|
||||
}
|
||||
CreateProfileScreenEvents.AvatarCleared -> {
|
||||
stateEmitter(state.copy(avatar = null))
|
||||
}
|
||||
CreateProfileScreenEvents.WhoCanFindMeClicked -> {
|
||||
parentEventEmitter.navigateTo(RegistrationRoute.PhoneNumberDiscoverability(state.discoverableByPhoneNumber))
|
||||
}
|
||||
is CreateProfileScreenEvents.DiscoverabilityChanged -> {
|
||||
stateEmitter(state.copy(discoverableByPhoneNumber = event.discoverable))
|
||||
}
|
||||
CreateProfileScreenEvents.NextClicked -> {
|
||||
if (state.isSubmitting || !state.isFormValid) {
|
||||
return
|
||||
}
|
||||
stateEmitter(state.copy(isSubmitting = true))
|
||||
submitProfile(state, parentEventEmitter, repository, stateEmitter)
|
||||
}
|
||||
CreateProfileScreenEvents.UploadFailedDialogDismissed -> {
|
||||
stateEmitter(state.copy(showUploadFailedDialog = false))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun submitProfile(state: CreateProfileState) {
|
||||
submitProfile(state, parentEventEmitter, repository) { _state.value = it }
|
||||
}
|
||||
|
||||
private suspend fun submitProfile(
|
||||
state: CreateProfileState,
|
||||
parentEventEmitter: (RegistrationFlowEvent) -> Unit,
|
||||
repository: RegistrationRepository,
|
||||
stateEmitter: (CreateProfileState) -> Unit
|
||||
) {
|
||||
val result = repository.setProfile(
|
||||
givenName = state.givenName.trim(),
|
||||
familyName = state.familyName.trim(),
|
||||
avatar = state.avatar,
|
||||
discoverableByPhoneNumber = state.discoverableByPhoneNumber
|
||||
)
|
||||
when (result) {
|
||||
is RequestResult.Success -> {
|
||||
Log.i(TAG, "[submitProfile] Profile saved.")
|
||||
parentEventEmitter(RegistrationFlowEvent.RegistrationComplete)
|
||||
}
|
||||
is RequestResult.NonSuccess -> {
|
||||
Log.w(TAG, "[submitProfile] Profile save failed: ${result.error}")
|
||||
stateEmitter(state.copy(isSubmitting = false, showUploadFailedDialog = true))
|
||||
}
|
||||
is RequestResult.RetryableNetworkError -> {
|
||||
Log.w(TAG, "[submitProfile] Network error saving profile.", result.networkError)
|
||||
stateEmitter(state.copy(isSubmitting = false, showUploadFailedDialog = true))
|
||||
}
|
||||
is RequestResult.ApplicationError -> {
|
||||
Log.w(TAG, "[submitProfile] Application error saving profile.", result.cause)
|
||||
stateEmitter(state.copy(isSubmitting = false, showUploadFailedDialog = true))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-198
@@ -1,198 +0,0 @@
|
||||
/*
|
||||
* Copyright 2026 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.registration.screens.discoverability
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.RadioButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.signal.core.ui.compose.AllDevicePreviews
|
||||
import org.signal.core.ui.compose.Buttons
|
||||
import org.signal.core.ui.compose.Previews
|
||||
import org.signal.core.ui.compose.SignalIcons
|
||||
import org.signal.registration.R
|
||||
import org.signal.registration.screens.RegistrationScaffold
|
||||
import org.signal.registration.test.TestTags
|
||||
|
||||
@Composable
|
||||
fun PhoneNumberDiscoverabilityScreen(
|
||||
state: PhoneNumberDiscoverabilityState,
|
||||
onEvent: (PhoneNumberDiscoverabilityScreenEvents) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
RegistrationScaffold(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.testTag(TestTags.PHONE_NUMBER_DISCOVERABILITY_SCREEN),
|
||||
topBar = {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 4.dp, vertical = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
IconButton(
|
||||
onClick = { onEvent(PhoneNumberDiscoverabilityScreenEvents.BackClicked) },
|
||||
modifier = Modifier.testTag(TestTags.PHONE_NUMBER_DISCOVERABILITY_BACK_BUTTON)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = SignalIcons.ArrowStart.imageVector,
|
||||
contentDescription = stringResource(R.string.PhoneNumberDiscoverabilityScreen__back)
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.padding(horizontal = 4.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.WhoCanSeeMyPhoneNumberFragment__who_can_find_me_by_number),
|
||||
style = MaterialTheme.typography.titleLarge
|
||||
)
|
||||
}
|
||||
},
|
||||
content = {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = 8.dp)
|
||||
) {
|
||||
DiscoverabilityOption(
|
||||
label = stringResource(R.string.PhoneNumberPrivacy_everyone),
|
||||
selected = state.discoverable,
|
||||
onClick = { onEvent(PhoneNumberDiscoverabilityScreenEvents.EveryoneSelected) },
|
||||
modifier = Modifier.testTag(TestTags.PHONE_NUMBER_DISCOVERABILITY_EVERYONE_OPTION)
|
||||
)
|
||||
|
||||
DiscoverabilityOption(
|
||||
label = stringResource(R.string.PhoneNumberPrivacy_nobody),
|
||||
selected = !state.discoverable,
|
||||
onClick = { onEvent(PhoneNumberDiscoverabilityScreenEvents.NobodySelected) },
|
||||
modifier = Modifier.testTag(TestTags.PHONE_NUMBER_DISCOVERABILITY_NOBODY_OPTION)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
Text(
|
||||
text = stringResource(
|
||||
if (state.discoverable) {
|
||||
R.string.WhoCanSeeMyPhoneNumberFragment__anyone_who_has_your
|
||||
} else {
|
||||
R.string.WhoCanSeeMyPhoneNumberFragment__nobody_will_be_able
|
||||
}
|
||||
),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(horizontal = 16.dp)
|
||||
)
|
||||
}
|
||||
},
|
||||
footer = {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 24.dp, vertical = 16.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Buttons.LargeTonal(
|
||||
onClick = { onEvent(PhoneNumberDiscoverabilityScreenEvents.SaveClicked) },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.widthIn(max = 320.dp)
|
||||
.testTag(TestTags.PHONE_NUMBER_DISCOVERABILITY_SAVE_BUTTON)
|
||||
) {
|
||||
Text(stringResource(R.string.PhoneNumberDiscoverabilityScreen__save))
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
if (state.showNobodyConfirmation) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { onEvent(PhoneNumberDiscoverabilityScreenEvents.NobodyDismissed) },
|
||||
title = { Text(stringResource(R.string.PhoneNumberPrivacySettingsFragment__nobody_can_find_me_warning_title)) },
|
||||
text = { Text(stringResource(R.string.PhoneNumberPrivacySettingsFragment__nobody_can_find_me_warning_message)) },
|
||||
dismissButton = {
|
||||
TextButton(onClick = { onEvent(PhoneNumberDiscoverabilityScreenEvents.NobodyDismissed) }) {
|
||||
Text(stringResource(R.string.PhoneNumberPrivacySettingsFragment__cancel))
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = { onEvent(PhoneNumberDiscoverabilityScreenEvents.NobodyConfirmed) }) {
|
||||
Text(stringResource(android.R.string.ok))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DiscoverabilityOption(
|
||||
label: String,
|
||||
selected: Boolean,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(onClick = onClick)
|
||||
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Start
|
||||
) {
|
||||
RadioButton(selected = selected, onClick = onClick)
|
||||
Spacer(modifier = Modifier.padding(horizontal = 8.dp))
|
||||
Text(text = label, style = MaterialTheme.typography.bodyLarge)
|
||||
}
|
||||
}
|
||||
|
||||
@AllDevicePreviews
|
||||
@Composable
|
||||
private fun PhoneNumberDiscoverabilityScreenEveryonePreview() {
|
||||
Previews.Preview {
|
||||
PhoneNumberDiscoverabilityScreen(
|
||||
state = PhoneNumberDiscoverabilityState(discoverable = true),
|
||||
onEvent = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@AllDevicePreviews
|
||||
@Composable
|
||||
private fun PhoneNumberDiscoverabilityScreenNobodyPreview() {
|
||||
Previews.Preview {
|
||||
PhoneNumberDiscoverabilityScreen(
|
||||
state = PhoneNumberDiscoverabilityState(discoverable = false),
|
||||
onEvent = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@AllDevicePreviews
|
||||
@Composable
|
||||
private fun PhoneNumberDiscoverabilityScreenConfirmPreview() {
|
||||
Previews.Preview {
|
||||
PhoneNumberDiscoverabilityScreen(
|
||||
state = PhoneNumberDiscoverabilityState(discoverable = true, showNobodyConfirmation = true),
|
||||
onEvent = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
/*
|
||||
* Copyright 2026 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.registration.screens.discoverability
|
||||
|
||||
sealed class PhoneNumberDiscoverabilityScreenEvents {
|
||||
data object EveryoneSelected : PhoneNumberDiscoverabilityScreenEvents()
|
||||
data object NobodySelected : PhoneNumberDiscoverabilityScreenEvents()
|
||||
data object NobodyConfirmed : PhoneNumberDiscoverabilityScreenEvents()
|
||||
data object NobodyDismissed : PhoneNumberDiscoverabilityScreenEvents()
|
||||
data object SaveClicked : PhoneNumberDiscoverabilityScreenEvents()
|
||||
data object BackClicked : PhoneNumberDiscoverabilityScreenEvents()
|
||||
}
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
/*
|
||||
* Copyright 2026 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.registration.screens.discoverability
|
||||
|
||||
data class PhoneNumberDiscoverabilityState(
|
||||
val discoverable: Boolean = true,
|
||||
val showNobodyConfirmation: Boolean = false
|
||||
)
|
||||
-75
@@ -1,75 +0,0 @@
|
||||
/*
|
||||
* Copyright 2026 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.registration.screens.discoverability
|
||||
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.signal.core.ui.compose.EventDrivenViewModel
|
||||
import org.signal.core.ui.navigation.ResultEventBus
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.registration.RegistrationFlowEvent
|
||||
import org.signal.registration.screens.util.navigateBack
|
||||
|
||||
class PhoneNumberDiscoverabilityViewModel(
|
||||
initialDiscoverable: Boolean,
|
||||
private val parentEventEmitter: (RegistrationFlowEvent) -> Unit,
|
||||
private val resultBus: ResultEventBus,
|
||||
private val resultKey: String
|
||||
) : EventDrivenViewModel<PhoneNumberDiscoverabilityScreenEvents>(TAG, shouldLogEvents = true) {
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(PhoneNumberDiscoverabilityViewModel::class)
|
||||
}
|
||||
|
||||
private val _state = MutableStateFlow(PhoneNumberDiscoverabilityState(discoverable = initialDiscoverable))
|
||||
val state: StateFlow<PhoneNumberDiscoverabilityState> = _state
|
||||
|
||||
init {
|
||||
_state
|
||||
.onEach { Log.d(TAG, "[State] $it") }
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
override suspend fun processEvent(event: PhoneNumberDiscoverabilityScreenEvents) {
|
||||
applyEvent(state.value, event, parentEventEmitter, resultBus, resultKey) { _state.value = it }
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
suspend fun applyEvent(
|
||||
state: PhoneNumberDiscoverabilityState,
|
||||
event: PhoneNumberDiscoverabilityScreenEvents,
|
||||
parentEventEmitter: (RegistrationFlowEvent) -> Unit,
|
||||
resultBus: ResultEventBus,
|
||||
resultKey: String,
|
||||
stateEmitter: (PhoneNumberDiscoverabilityState) -> Unit
|
||||
) {
|
||||
when (event) {
|
||||
PhoneNumberDiscoverabilityScreenEvents.EveryoneSelected -> {
|
||||
stateEmitter(state.copy(discoverable = true))
|
||||
}
|
||||
PhoneNumberDiscoverabilityScreenEvents.NobodySelected -> {
|
||||
stateEmitter(state.copy(showNobodyConfirmation = true))
|
||||
}
|
||||
PhoneNumberDiscoverabilityScreenEvents.NobodyConfirmed -> {
|
||||
stateEmitter(state.copy(discoverable = false, showNobodyConfirmation = false))
|
||||
}
|
||||
PhoneNumberDiscoverabilityScreenEvents.NobodyDismissed -> {
|
||||
stateEmitter(state.copy(showNobodyConfirmation = false))
|
||||
}
|
||||
PhoneNumberDiscoverabilityScreenEvents.SaveClicked -> {
|
||||
resultBus.sendResult(resultKey, state.discoverable)
|
||||
parentEventEmitter.navigateBack()
|
||||
}
|
||||
PhoneNumberDiscoverabilityScreenEvents.BackClicked -> {
|
||||
parentEventEmitter.navigateBack()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -142,20 +142,6 @@ object TestTags {
|
||||
const val COUNTRY_CODE_SEARCH_FIELD = "country_code_search_field"
|
||||
const val COUNTRY_CODE_CLOSE_BUTTON = "country_code_close_button"
|
||||
|
||||
// Create Profile Screen
|
||||
const val CREATE_PROFILE_SCREEN = "create_profile_screen"
|
||||
const val CREATE_PROFILE_GIVEN_NAME_FIELD = "create_profile_given_name_field"
|
||||
const val CREATE_PROFILE_FAMILY_NAME_FIELD = "create_profile_family_name_field"
|
||||
const val CREATE_PROFILE_WHO_CAN_FIND_ME_ROW = "create_profile_who_can_find_me_row"
|
||||
const val CREATE_PROFILE_NEXT_BUTTON = "create_profile_next_button"
|
||||
|
||||
// Phone Number Discoverability Screen
|
||||
const val PHONE_NUMBER_DISCOVERABILITY_SCREEN = "phone_number_discoverability_screen"
|
||||
const val PHONE_NUMBER_DISCOVERABILITY_EVERYONE_OPTION = "phone_number_discoverability_everyone_option"
|
||||
const val PHONE_NUMBER_DISCOVERABILITY_NOBODY_OPTION = "phone_number_discoverability_nobody_option"
|
||||
const val PHONE_NUMBER_DISCOVERABILITY_SAVE_BUTTON = "phone_number_discoverability_save_button"
|
||||
const val PHONE_NUMBER_DISCOVERABILITY_BACK_BUTTON = "phone_number_discoverability_back_button"
|
||||
|
||||
// Device Transfer Complete Screen
|
||||
const val DEVICE_TRANSFER_COMPLETE_SCREEN = "device_transfer_complete_screen"
|
||||
const val DEVICE_TRANSFER_COMPLETE_CONTINUE_BUTTON = "device_transfer_complete_continue_button"
|
||||
|
||||
@@ -558,41 +558,6 @@
|
||||
<string name="DeviceTransferComplete__your_account_is_now_on_this_device">Your account is now on this device.</string>
|
||||
<string name="DeviceTransferComplete__continue_registration">Continue</string>
|
||||
|
||||
<!-- Create profile screen -->
|
||||
<!-- Title displayed at the top of the create-profile screen during registration. -->
|
||||
<string name="CreateProfileScreen__set_up_your_profile">Set up your profile</string>
|
||||
<!-- Subtitle on create-profile screen explaining that the profile is end-to-end encrypted and visible to recipients. -->
|
||||
<string name="CreateProfileScreen__your_profile_is_end_to_end_encrypted">Your profile and changes to it will be visible to people you message, contacts, and groups.</string>
|
||||
<!-- Label/hint for the first-name field, indicating it is required. -->
|
||||
<string name="CreateProfileScreen__first_name_required">First name (required)</string>
|
||||
<!-- Label/hint for the last-name field, indicating it is optional. -->
|
||||
<string name="CreateProfileScreen__last_name_optional">Last name (optional)</string>
|
||||
<!-- Action button text to advance from create-profile screen. -->
|
||||
<string name="CreateProfileScreen__next">Next</string>
|
||||
<!-- Content description for the avatar selection control. -->
|
||||
<string name="CreateProfileScreen__set_avatar_description">Set avatar</string>
|
||||
|
||||
<!-- Phone number discoverability picker -->
|
||||
<!-- Title row label shown on the create-profile screen pointing at the discoverability picker. -->
|
||||
<string name="WhoCanSeeMyPhoneNumberFragment__who_can_find_me_by_number">Who can find me by number?</string>
|
||||
<!-- Description shown on the picker when "Everyone" is selected. -->
|
||||
<string name="WhoCanSeeMyPhoneNumberFragment__anyone_who_has_your">Anyone who has your phone number will see you\'re on Signal and can start chats with you.</string>
|
||||
<!-- Description shown on the picker when "Nobody" is selected. -->
|
||||
<string name="WhoCanSeeMyPhoneNumberFragment__nobody_will_be_able">Nobody will be able to see you\'re on Signal unless you message them or have an existing chat with them.</string>
|
||||
<!-- Radio option label for "Everyone can find me by number". -->
|
||||
<string name="PhoneNumberPrivacy_everyone">Everyone</string>
|
||||
<!-- Radio option label for "Nobody can find me by number". -->
|
||||
<string name="PhoneNumberPrivacy_nobody">Nobody</string>
|
||||
<!-- Title for the confirmation dialog shown when the user selects "Nobody". -->
|
||||
<string name="PhoneNumberPrivacySettingsFragment__nobody_can_find_me_warning_title">Are you sure?</string>
|
||||
<!-- Body for the confirmation dialog shown when the user selects "Nobody". -->
|
||||
<string name="PhoneNumberPrivacySettingsFragment__nobody_can_find_me_warning_message">Setting \"Who can find me by number\" to \"Nobody\" will make it harder for people to find you on Signal.</string>
|
||||
<!-- Cancel button on the "Nobody" confirmation dialog. -->
|
||||
<string name="PhoneNumberPrivacySettingsFragment__cancel">Cancel</string>
|
||||
<!-- Save button on the discoverability picker. -->
|
||||
<string name="PhoneNumberDiscoverabilityScreen__save">Save</string>
|
||||
<!-- Content description for the back arrow on the discoverability picker. -->
|
||||
<string name="PhoneNumberDiscoverabilityScreen__back">Back</string>
|
||||
<!-- Error message shown when the captcha fails to load. -->
|
||||
<string name="CaptchaScreen__failed_to_load_captcha">Failed to load captcha</string>
|
||||
<!-- Button to cancel out of the captcha screen. -->
|
||||
|
||||
-29
@@ -1,29 +0,0 @@
|
||||
/*
|
||||
* Copyright 2026 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.registration.screens.createprofile
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import com.android.tools.screenshot.PreviewTest
|
||||
import org.signal.core.ui.compose.Previews
|
||||
import org.signal.core.ui.compose.ScreenshotPreviews
|
||||
|
||||
class CreateProfileScreenScreenshotTests {
|
||||
@PreviewTest
|
||||
@ScreenshotPreviews
|
||||
@Composable
|
||||
fun CreateProfileScreenPreview() {
|
||||
Previews.Preview {
|
||||
CreateProfileScreen(
|
||||
state = CreateProfileState(
|
||||
givenName = "Alice",
|
||||
familyName = "Anderson",
|
||||
isLoading = false
|
||||
),
|
||||
onEvent = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
/*
|
||||
* Copyright 2026 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.registration.screens.discoverability
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import com.android.tools.screenshot.PreviewTest
|
||||
import org.signal.core.ui.compose.Previews
|
||||
import org.signal.core.ui.compose.ScreenshotPreviews
|
||||
|
||||
class PhoneNumberDiscoverabilityScreenScreenshotTests {
|
||||
@PreviewTest
|
||||
@ScreenshotPreviews
|
||||
@Composable
|
||||
fun PhoneNumberDiscoverabilityScreenPreview() {
|
||||
Previews.Preview {
|
||||
PhoneNumberDiscoverabilityScreen(
|
||||
state = PhoneNumberDiscoverabilityState(discoverable = true),
|
||||
onEvent = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:51630b23f4e64fc135694b7d31ac9e43d427feb0fa6a718e7636ecb2d08a847d
|
||||
size 91390
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e2ee6b25920fa70f88c8e8d0ca0358b7b43084d6b89d52eb1ff9f1e94e09563e
|
||||
size 91661
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:af8930683c82650d79b7b4d4f38440c1a39525daf57378d840275ce06ea11903
|
||||
size 92268
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:694beaa30a8a2792b814a50b26d158be58584d0b0d01db87b48dd707a98b301b
|
||||
size 45982
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6b134de26394d46d6059994120ba14349a2fdcf38c087c7f7478bcadf9b65a88
|
||||
size 76287
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:a76c35f2864ec618ad9d7ef86b8e5ba79f5f6800f887f8c74f5e613bb7b209cf
|
||||
size 76803
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:3d9ce7423ba1282fdce59422bd8024f714bcde34cbd997759af2730bdfc7d4e7
|
||||
size 81162
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:70ef3f6c5afc2001e9b2a3d5fe466e2a98187825f700e2a64b5815f410e0221d
|
||||
size 99034
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:17b3e3cca904d031cd2f227d15fd4120d65708b6f413bf21d14a258c1c92e925
|
||||
size 99666
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:5bf789154fda500496e14266d711b1c573704c60028726b2b9867a071e861e73
|
||||
size 101423
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:910fcc416ccb82ec783522ef29e84b551963c6f25da6f72bfe74aca489b1ae40
|
||||
size 62012
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c9bd57cd415076915a586adbc966d00a4d5aed5d1aec07ec8ab31bb128394240
|
||||
size 62348
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:2710c2b37c152ad9ab4b53e2fa9accc65da9ab03378b4354b942d7a61bf78f5c
|
||||
size 62663
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d93740b86d36302ba2d558b39191d19a6376c6d2ccc0e771fc2a38254a4518b2
|
||||
size 46567
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:9814d822e306de2f75fb28589e80ac7b38c254fb51a4e29e259eac99ae5eee21
|
||||
size 49107
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:afa8a206a7ca45b8493ead733a42c07fc548a42b4160a5cc4e1852e44c59701b
|
||||
size 49414
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:0326780c0ad5687c1253c4383572d1c7cc2716a3b6e269fddfd20bee5ad572c2
|
||||
size 52908
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:25a5116d58974a3b411cb7b19428427d85b7ab0ece367243be88cf31312d28f3
|
||||
size 69766
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:bf046e84391f1fb88f9635efa86413bd421ba770e3b3e15066b8a01906d3ce08
|
||||
size 70015
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:a5c9feb5ada26e088f75f47be47e56e4fca5b585b554cd1a5d5be744fecab005
|
||||
size 72279
|
||||
-142
@@ -1,142 +0,0 @@
|
||||
/*
|
||||
* Copyright 2026 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.registration.screens.createprofile
|
||||
|
||||
import android.app.Application
|
||||
import androidx.compose.ui.test.assertIsDisplayed
|
||||
import androidx.compose.ui.test.assertIsEnabled
|
||||
import androidx.compose.ui.test.assertIsNotEnabled
|
||||
import androidx.compose.ui.test.junit4.createComposeRule
|
||||
import androidx.compose.ui.test.onNodeWithTag
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.compose.ui.test.performTextInput
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
import org.signal.core.ui.CoreUiDependenciesRule
|
||||
import org.signal.core.ui.compose.theme.SignalTheme
|
||||
import org.signal.registration.test.TestTags
|
||||
|
||||
/**
|
||||
* Tests for CreateProfileScreen that validate event emissions.
|
||||
* Uses Robolectric to run fast JUnit tests without an emulator.
|
||||
*/
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(application = Application::class)
|
||||
class CreateProfileScreenTest {
|
||||
|
||||
@get:Rule
|
||||
val composeTestRule = createComposeRule()
|
||||
|
||||
@get:Rule
|
||||
val coreUiDependenciesRule = CoreUiDependenciesRule(ApplicationProvider.getApplicationContext())
|
||||
|
||||
@Test
|
||||
fun `screen displays given name and family name fields`() {
|
||||
// Given
|
||||
composeTestRule.setContent {
|
||||
SignalTheme {
|
||||
CreateProfileScreen(
|
||||
state = CreateProfileState(isLoading = false),
|
||||
onEvent = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Then
|
||||
composeTestRule.onNodeWithTag(TestTags.CREATE_PROFILE_GIVEN_NAME_FIELD).assertIsDisplayed()
|
||||
composeTestRule.onNodeWithTag(TestTags.CREATE_PROFILE_FAMILY_NAME_FIELD).assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when typing in given name field, GivenNameChanged event is emitted`() {
|
||||
// Given
|
||||
var emittedEvent: CreateProfileScreenEvents? = null
|
||||
|
||||
composeTestRule.setContent {
|
||||
SignalTheme {
|
||||
CreateProfileScreen(
|
||||
state = CreateProfileState(isLoading = false),
|
||||
onEvent = { event ->
|
||||
emittedEvent = event
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// When
|
||||
composeTestRule.onNodeWithTag(TestTags.CREATE_PROFILE_GIVEN_NAME_FIELD).performTextInput("Alice")
|
||||
|
||||
// Then
|
||||
assert(emittedEvent is CreateProfileScreenEvents.GivenNameChanged)
|
||||
}
|
||||
|
||||
@Config(qualifiers = "w360dp-h1200dp")
|
||||
@Test
|
||||
fun `when who can find me row is clicked, WhoCanFindMeClicked event is emitted`() {
|
||||
// Given
|
||||
var emittedEvent: CreateProfileScreenEvents? = null
|
||||
|
||||
composeTestRule.setContent {
|
||||
SignalTheme {
|
||||
CreateProfileScreen(
|
||||
state = CreateProfileState(isLoading = false),
|
||||
onEvent = { event ->
|
||||
emittedEvent = event
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// When
|
||||
composeTestRule.onNodeWithTag(TestTags.CREATE_PROFILE_WHO_CAN_FIND_ME_ROW).performClick()
|
||||
|
||||
// Then
|
||||
assert(emittedEvent == CreateProfileScreenEvents.WhoCanFindMeClicked)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when given name is blank, Next button is disabled`() {
|
||||
// Given
|
||||
composeTestRule.setContent {
|
||||
SignalTheme {
|
||||
CreateProfileScreen(
|
||||
state = CreateProfileState(isLoading = false),
|
||||
onEvent = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Then
|
||||
composeTestRule.onNodeWithTag(TestTags.CREATE_PROFILE_NEXT_BUTTON).assertIsNotEnabled()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when given name is present, Next button is enabled and emits NextClicked`() {
|
||||
// Given
|
||||
var emittedEvent: CreateProfileScreenEvents? = null
|
||||
|
||||
composeTestRule.setContent {
|
||||
SignalTheme {
|
||||
CreateProfileScreen(
|
||||
state = CreateProfileState(givenName = "Alice", isLoading = false),
|
||||
onEvent = { event ->
|
||||
emittedEvent = event
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// When
|
||||
composeTestRule.onNodeWithTag(TestTags.CREATE_PROFILE_NEXT_BUTTON).assertIsEnabled().performClick()
|
||||
|
||||
// Then
|
||||
assert(emittedEvent == CreateProfileScreenEvents.NextClicked)
|
||||
}
|
||||
}
|
||||
-146
@@ -1,146 +0,0 @@
|
||||
/*
|
||||
* Copyright 2026 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.registration.screens.discoverability
|
||||
|
||||
import android.app.Application
|
||||
import androidx.compose.ui.test.assertIsDisplayed
|
||||
import androidx.compose.ui.test.junit4.createComposeRule
|
||||
import androidx.compose.ui.test.onNodeWithTag
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
import org.signal.core.ui.CoreUiDependenciesRule
|
||||
import org.signal.core.ui.compose.theme.SignalTheme
|
||||
import org.signal.registration.test.TestTags
|
||||
|
||||
/**
|
||||
* Tests for PhoneNumberDiscoverabilityScreen that validate event emissions.
|
||||
* Uses Robolectric to run fast JUnit tests without an emulator.
|
||||
*/
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(application = Application::class)
|
||||
class PhoneNumberDiscoverabilityScreenTest {
|
||||
|
||||
@get:Rule
|
||||
val composeTestRule = createComposeRule()
|
||||
|
||||
@get:Rule
|
||||
val coreUiDependenciesRule = CoreUiDependenciesRule(ApplicationProvider.getApplicationContext())
|
||||
|
||||
@Test
|
||||
fun `screen displays everyone and nobody options and save button`() {
|
||||
// Given
|
||||
composeTestRule.setContent {
|
||||
SignalTheme {
|
||||
PhoneNumberDiscoverabilityScreen(
|
||||
state = PhoneNumberDiscoverabilityState(),
|
||||
onEvent = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Then
|
||||
composeTestRule.onNodeWithTag(TestTags.PHONE_NUMBER_DISCOVERABILITY_EVERYONE_OPTION).assertIsDisplayed()
|
||||
composeTestRule.onNodeWithTag(TestTags.PHONE_NUMBER_DISCOVERABILITY_NOBODY_OPTION).assertIsDisplayed()
|
||||
composeTestRule.onNodeWithTag(TestTags.PHONE_NUMBER_DISCOVERABILITY_SAVE_BUTTON).assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when nobody option is clicked, NobodySelected event is emitted`() {
|
||||
// Given
|
||||
var emittedEvent: PhoneNumberDiscoverabilityScreenEvents? = null
|
||||
|
||||
composeTestRule.setContent {
|
||||
SignalTheme {
|
||||
PhoneNumberDiscoverabilityScreen(
|
||||
state = PhoneNumberDiscoverabilityState(),
|
||||
onEvent = { event ->
|
||||
emittedEvent = event
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// When
|
||||
composeTestRule.onNodeWithTag(TestTags.PHONE_NUMBER_DISCOVERABILITY_NOBODY_OPTION).performClick()
|
||||
|
||||
// Then
|
||||
assert(emittedEvent == PhoneNumberDiscoverabilityScreenEvents.NobodySelected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when everyone option is clicked, EveryoneSelected event is emitted`() {
|
||||
// Given
|
||||
var emittedEvent: PhoneNumberDiscoverabilityScreenEvents? = null
|
||||
|
||||
composeTestRule.setContent {
|
||||
SignalTheme {
|
||||
PhoneNumberDiscoverabilityScreen(
|
||||
state = PhoneNumberDiscoverabilityState(discoverable = false),
|
||||
onEvent = { event ->
|
||||
emittedEvent = event
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// When
|
||||
composeTestRule.onNodeWithTag(TestTags.PHONE_NUMBER_DISCOVERABILITY_EVERYONE_OPTION).performClick()
|
||||
|
||||
// Then
|
||||
assert(emittedEvent == PhoneNumberDiscoverabilityScreenEvents.EveryoneSelected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when save button is clicked, SaveClicked event is emitted`() {
|
||||
// Given
|
||||
var emittedEvent: PhoneNumberDiscoverabilityScreenEvents? = null
|
||||
|
||||
composeTestRule.setContent {
|
||||
SignalTheme {
|
||||
PhoneNumberDiscoverabilityScreen(
|
||||
state = PhoneNumberDiscoverabilityState(),
|
||||
onEvent = { event ->
|
||||
emittedEvent = event
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// When
|
||||
composeTestRule.onNodeWithTag(TestTags.PHONE_NUMBER_DISCOVERABILITY_SAVE_BUTTON).performClick()
|
||||
|
||||
// Then
|
||||
assert(emittedEvent == PhoneNumberDiscoverabilityScreenEvents.SaveClicked)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when back button is clicked, BackClicked event is emitted`() {
|
||||
// Given
|
||||
var emittedEvent: PhoneNumberDiscoverabilityScreenEvents? = null
|
||||
|
||||
composeTestRule.setContent {
|
||||
SignalTheme {
|
||||
PhoneNumberDiscoverabilityScreen(
|
||||
state = PhoneNumberDiscoverabilityState(),
|
||||
onEvent = { event ->
|
||||
emittedEvent = event
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// When
|
||||
composeTestRule.onNodeWithTag(TestTags.PHONE_NUMBER_DISCOVERABILITY_BACK_BUTTON).performClick()
|
||||
|
||||
// Then
|
||||
assert(emittedEvent == PhoneNumberDiscoverabilityScreenEvents.BackClicked)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user