Centralize recipient lookup in RecipientRepository.

This commit is contained in:
jeffrey-signal
2025-11-13 10:47:04 -05:00
committed by Cody Henthorne
parent 2e1291b3c3
commit 0e46ab33e8
14 changed files with 216 additions and 200 deletions

View File

@@ -123,9 +123,9 @@ public class AddMembersActivity extends PushContactSelectionActivity implements
if (result instanceof RecipientRepository.LookupResult.Success) {
enableDone();
callback.accept(true);
} else if (result instanceof RecipientRepository.LookupResult.NotFound || result instanceof RecipientRepository.LookupResult.InvalidEntry) {
} else if (result instanceof RecipientRepository.PhoneLookupResult.NotFound || result instanceof RecipientRepository.PhoneLookupResult.InvalidPhone) {
new MaterialAlertDialogBuilder(this)
.setMessage(getString(R.string.NewConversationActivity__s_is_not_a_signal_user, number))
.setMessage(getString(R.string.RecipientLookup_error__s_is_not_a_signal_user, number))
.setPositiveButton(android.R.string.ok, null)
.show();
callback.accept(false);

View File

@@ -56,6 +56,7 @@ import org.thoughtcrime.securesms.groups.SelectionLimits
import org.thoughtcrime.securesms.groups.ui.creategroup.CreateGroupUiState.NavTarget
import org.thoughtcrime.securesms.groups.ui.creategroup.CreateGroupUiState.UserMessage
import org.thoughtcrime.securesms.groups.ui.creategroup.details.AddGroupDetailsActivity
import org.thoughtcrime.securesms.recipients.ui.RecipientLookupFailureMessage
import org.thoughtcrime.securesms.recipients.ui.RecipientPicker
import org.thoughtcrime.securesms.recipients.ui.RecipientPickerCallbacks
import org.thoughtcrime.securesms.recipients.ui.RecipientPickerScaffold
@@ -275,31 +276,15 @@ private fun UserMessagesHost(
userMessage: UserMessage?,
onDismiss: (UserMessage) -> Unit
) {
val context: Context = LocalContext.current
when (userMessage) {
null -> {}
is UserMessage.Info.NetworkError -> Dialogs.SimpleMessageDialog(
message = stringResource(R.string.NetworkFailure__network_error_check_your_connection_and_try_again),
dismiss = stringResource(android.R.string.ok),
onDismiss = { onDismiss(userMessage) }
)
is UserMessage.Info.RecipientNotSignalUser -> Dialogs.SimpleMessageDialog(
message = stringResource(R.string.NewConversationActivity__s_is_not_a_signal_user, userMessage.phone.displayText),
dismiss = stringResource(android.R.string.ok),
onDismiss = { onDismiss(userMessage) }
)
is UserMessage.Info.RecipientsNotSignalUsers -> Dialogs.SimpleMessageDialog(
message = pluralStringResource(
id = R.plurals.CreateGroupActivity_not_signal_users,
count = userMessage.recipients.size,
userMessage.recipients.joinToString(", ") { it.getDisplayName(context) }
),
dismiss = stringResource(android.R.string.ok),
onDismiss = { onDismiss(userMessage) }
)
is UserMessage.RecipientLookupFailed -> {
RecipientLookupFailureMessage(
failure = userMessage.failure,
onDismissed = { onDismiss(userMessage) }
)
}
}
}

View File

@@ -7,30 +7,22 @@ package org.thoughtcrime.securesms.groups.ui.creategroup
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.signal.core.util.Stopwatch
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.contacts.SelectedContact
import org.thoughtcrime.securesms.contacts.sync.ContactDiscovery
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.groups.SelectionLimits
import org.thoughtcrime.securesms.groups.ui.creategroup.CreateGroupUiState.NavTarget
import org.thoughtcrime.securesms.groups.ui.creategroup.CreateGroupUiState.UserMessage.Info
import org.thoughtcrime.securesms.groups.ui.creategroup.CreateGroupUiState.UserMessage
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.recipients.PhoneNumber
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.recipients.RecipientRepository
import org.thoughtcrime.securesms.recipients.ui.RecipientSelection
import org.thoughtcrime.securesms.util.RemoteConfig
import java.io.IOException
import kotlin.time.Duration.Companion.seconds
class CreateGroupViewModel : ViewModel() {
companion object {
@@ -52,31 +44,17 @@ class CreateGroupViewModel : ViewModel() {
private suspend fun recipientExists(phone: PhoneNumber): Boolean {
internalUiState.update { it.copy(isLookingUpRecipient = true) }
val lookupResult = withContext(Dispatchers.IO) {
RecipientRepository.lookupNewE164(inputE164 = phone.value)
}
return when (lookupResult) {
is RecipientRepository.LookupResult.Success -> {
return when (val lookupResult = RecipientRepository.lookup(phone)) {
is RecipientRepository.PhoneLookupResult.Found -> {
internalUiState.update { it.copy(isLookingUpRecipient = false) }
true
}
is RecipientRepository.LookupResult.NotFound, is RecipientRepository.LookupResult.InvalidEntry -> {
is RecipientRepository.LookupResult.Failure -> {
internalUiState.update {
it.copy(
isLookingUpRecipient = false,
userMessage = Info.RecipientNotSignalUser(phone)
)
}
false
}
is RecipientRepository.LookupResult.NetworkError -> {
internalUiState.update {
it.copy(
isLookingUpRecipient = false,
userMessage = Info.NetworkError
userMessage = UserMessage.RecipientLookupFailed(failure = lookupResult)
)
}
false
@@ -108,58 +86,31 @@ class CreateGroupViewModel : ViewModel() {
fun continueToGroupDetails() {
viewModelScope.launch {
val stopwatch = Stopwatch(title = "Recipient Refresh")
internalUiState.update { it.copy(isLookingUpRecipient = true) }
val selectedRecipients = uiState.value.newSelections.asRecipients(stopwatch)
stopwatch.split(label = "registered")
stopwatch.stop(tag = TAG)
val notSignalUsers = selectedRecipients.filter { !it.isRegistered || !it.hasServiceId }
if (notSignalUsers.isNotEmpty()) {
internalUiState.update {
it.copy(
isLookingUpRecipient = false,
userMessage = Info.RecipientsNotSignalUsers(recipients = notSignalUsers)
)
val selectedRecipientIds = uiState.value.newSelections.map { it.orCreateRecipientId }
when (val lookupResult = RecipientRepository.lookup(recipientIds = selectedRecipientIds)) {
is RecipientRepository.IdLookupResult.FoundAll -> {
internalUiState.update {
it.copy(
isLookingUpRecipient = false,
pendingDestination = NavTarget.AddGroupDetails(recipientIds = selectedRecipientIds)
)
}
}
} else {
internalUiState.update {
it.copy(
isLookingUpRecipient = false,
pendingDestination = NavTarget.AddGroupDetails(recipientIds = selectedRecipients.map(Recipient::id))
)
is RecipientRepository.LookupResult.Failure -> {
internalUiState.update {
it.copy(
isLookingUpRecipient = false,
userMessage = UserMessage.RecipientLookupFailed(failure = lookupResult)
)
}
}
}
}
}
private fun List<SelectedContact>.asRecipients(stopwatch: Stopwatch): List<Recipient> {
val selectedRecipientIds: List<RecipientId> = this.map { it.orCreateRecipientId }
val recipientsNeedingRegistrationCheck = Recipient
.resolvedList(selectedRecipientIds)
.also { stopwatch.split(label = "resolve") }
.filter { !it.isRegistered || !it.hasServiceId }
.toSet()
Log.d(TAG, "Need to do ${recipientsNeedingRegistrationCheck.size} registration checks.")
recipientsNeedingRegistrationCheck.forEach { recipient ->
try {
ContactDiscovery.refresh(
context = AppDependencies.application,
recipient = recipient,
notifyOfNewUsers = false,
timeoutMs = 10.seconds.inWholeMilliseconds
)
} catch (e: IOException) {
Log.w(TAG, "Failed to refresh registered status for ${recipient.id}", e)
}
}
return Recipient.resolvedList(selectedRecipientIds)
}
fun clearUserMessage() {
internalUiState.update { it.copy(userMessage = null) }
}
@@ -181,11 +132,7 @@ data class CreateGroupUiState(
val userMessage: UserMessage? = null
) {
sealed interface UserMessage {
sealed interface Info : UserMessage {
data class RecipientNotSignalUser(val phone: PhoneNumber) : Info
data class RecipientsNotSignalUsers(val recipients: List<Recipient>) : Info
data object NetworkError : Info
}
data class RecipientLookupFailed(val failure: RecipientRepository.LookupResult.Failure) : UserMessage
}
sealed interface NavTarget {