mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-22 01:40:07 +01:00
Update country picker for findBy and changeNumber.
This commit is contained in:
@@ -13,7 +13,6 @@ import androidx.activity.compose.setContent
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContract
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement.Absolute.spacedBy
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
@@ -26,8 +25,6 @@ import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
@@ -49,14 +46,9 @@ import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.input.VisualTransformation
|
||||
import androidx.compose.ui.text.intl.Locale
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
@@ -83,7 +75,9 @@ import org.thoughtcrime.securesms.components.settings.app.usernamelinks.main.Use
|
||||
import org.thoughtcrime.securesms.invites.InviteActions
|
||||
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberVisualTransformation
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.registration.util.CountryPrefix
|
||||
import org.thoughtcrime.securesms.registration.ui.countrycode.Country
|
||||
import org.thoughtcrime.securesms.registration.ui.countrycode.CountryCodeState
|
||||
import org.thoughtcrime.securesms.registrationv3.ui.countrycode.Screen
|
||||
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme
|
||||
import org.thoughtcrime.securesms.util.viewModel
|
||||
import org.whispersystems.signalservice.api.util.PhoneNumberFormatter
|
||||
@@ -160,7 +154,7 @@ class FindByActivity : PassphraseRequiredActivity() {
|
||||
}
|
||||
}
|
||||
},
|
||||
onSelectCountryPrefixClick = {
|
||||
onSelectCountryClick = {
|
||||
navController.navigate("select-country-prefix")
|
||||
},
|
||||
onQrCodeScanClicked = {
|
||||
@@ -171,23 +165,20 @@ class FindByActivity : PassphraseRequiredActivity() {
|
||||
}
|
||||
|
||||
composable("select-country-prefix") {
|
||||
Scaffolds.Settings(
|
||||
title = stringResource(id = R.string.FindByActivity__select_country_code),
|
||||
onNavigationClick = { navController.popBackStack() },
|
||||
navigationIconPainter = painterResource(id = R.drawable.symbol_arrow_left_24)
|
||||
) { paddingValues ->
|
||||
SelectCountryScreen(
|
||||
paddingValues = paddingValues,
|
||||
searchEntry = state.countryPrefixSearchEntry,
|
||||
onSearchEntryChanged = viewModel::onCountryPrefixSearchEntryChanged,
|
||||
supportedCountryPrefixes = state.supportedCountryPrefixes,
|
||||
onCountryPrefixSelected = {
|
||||
navController.popBackStack()
|
||||
viewModel.onCountryPrefixSelected(it)
|
||||
viewModel.onCountryPrefixSearchEntryChanged("")
|
||||
}
|
||||
)
|
||||
}
|
||||
Screen(
|
||||
state = CountryCodeState(
|
||||
query = state.query,
|
||||
filteredList = state.filteredCountries,
|
||||
countryList = state.supportedCountries
|
||||
),
|
||||
title = stringResource(R.string.FindByActivity__select_country_code),
|
||||
onSearch = { search -> viewModel.filterCountries(search) },
|
||||
onDismissed = { navController.popBackStack() },
|
||||
onClick = { country ->
|
||||
viewModel.onCountrySelected(country)
|
||||
navController.popBackStack()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
dialog("invalid-entry") {
|
||||
@@ -201,8 +192,8 @@ class FindByActivity : PassphraseRequiredActivity() {
|
||||
stringResource(id = R.string.FindByActivity__s_is_not_a_valid_username, state.userEntry)
|
||||
} else {
|
||||
val formattedNumber = remember(state.userEntry) {
|
||||
val cleansed = state.userEntry.removePrefix(state.selectedCountryPrefix.digits.toString())
|
||||
PhoneNumberFormatter.formatE164(state.selectedCountryPrefix.digits.toString(), cleansed)
|
||||
val cleansed = state.userEntry.removePrefix(state.selectedCountry.countryCode.toString())
|
||||
PhoneNumberFormatter.formatE164(state.selectedCountry.countryCode.toString(), cleansed)
|
||||
}
|
||||
stringResource(id = R.string.FindByActivity__s_is_not_a_valid_phone_number, formattedNumber)
|
||||
}
|
||||
@@ -240,8 +231,8 @@ class FindByActivity : PassphraseRequiredActivity() {
|
||||
stringResource(id = R.string.FindByActivity__s_is_not_a_signal_user, state.userEntry)
|
||||
} else {
|
||||
val formattedNumber = remember(state.userEntry) {
|
||||
val cleansed = state.userEntry.removePrefix(state.selectedCountryPrefix.digits.toString())
|
||||
PhoneNumberFormatter.formatE164(state.selectedCountryPrefix.digits.toString(), cleansed)
|
||||
val cleansed = state.userEntry.removePrefix(state.selectedCountry.countryCode.toString())
|
||||
PhoneNumberFormatter.formatE164(state.selectedCountry.countryCode.toString(), cleansed)
|
||||
}
|
||||
stringResource(id = R.string.FindByActivity__s_is_not_a_signal_user_would, formattedNumber)
|
||||
}
|
||||
@@ -303,7 +294,7 @@ private fun Content(
|
||||
state: FindByState,
|
||||
onUserEntryChanged: (String) -> Unit,
|
||||
onNextClick: () -> Unit,
|
||||
onSelectCountryPrefixClick: () -> Unit,
|
||||
onSelectCountryClick: () -> Unit,
|
||||
onQrCodeScanClicked: () -> Unit
|
||||
) {
|
||||
val placeholderLabel = remember(state.mode) {
|
||||
@@ -338,8 +329,8 @@ private fun Content(
|
||||
val visualTransformation = if (state.mode == FindByMode.USERNAME) {
|
||||
VisualTransformation.None
|
||||
} else {
|
||||
remember(state.selectedCountryPrefix) {
|
||||
PhoneNumberVisualTransformation(state.selectedCountryPrefix.regionCode)
|
||||
remember(state.selectedCountry.countryCode) {
|
||||
PhoneNumberVisualTransformation(state.selectedCountry.regionCode)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -355,8 +346,8 @@ private fun Content(
|
||||
{
|
||||
PhoneNumberEntryPrefix(
|
||||
enabled = !state.isLookupInProgress,
|
||||
selectedCountryPrefix = state.selectedCountryPrefix,
|
||||
onSelectCountryPrefixClick = onSelectCountryPrefixClick
|
||||
selectedCountry = state.selectedCountry,
|
||||
onSelectCountryClick = onSelectCountryClick
|
||||
)
|
||||
}
|
||||
},
|
||||
@@ -448,8 +439,8 @@ private fun Content(
|
||||
@Composable
|
||||
private fun PhoneNumberEntryPrefix(
|
||||
enabled: Boolean,
|
||||
selectedCountryPrefix: CountryPrefix,
|
||||
onSelectCountryPrefixClick: () -> Unit
|
||||
selectedCountry: Country,
|
||||
onSelectCountryClick: () -> Unit
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
@@ -459,10 +450,10 @@ private fun PhoneNumberEntryPrefix(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(1000.dp))
|
||||
.clickable(onClick = onSelectCountryPrefixClick, enabled = enabled)
|
||||
.clickable(onClick = onSelectCountryClick, enabled = enabled)
|
||||
) {
|
||||
Text(
|
||||
text = selectedCountryPrefix.toString(),
|
||||
text = "+${selectedCountry.countryCode}",
|
||||
modifier = Modifier
|
||||
.padding(start = 12.dp, top = 6.dp, bottom = 6.dp)
|
||||
)
|
||||
@@ -486,113 +477,6 @@ private fun PhoneNumberEntryPrefix(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SelectCountryScreen(
|
||||
paddingValues: PaddingValues,
|
||||
searchEntry: String,
|
||||
onSearchEntryChanged: (String) -> Unit,
|
||||
onCountryPrefixSelected: (CountryPrefix) -> Unit,
|
||||
supportedCountryPrefixes: List<CountryPrefix>
|
||||
) {
|
||||
val focusRequester = remember {
|
||||
FocusRequester()
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier.padding(paddingValues)
|
||||
) {
|
||||
TextFields.TextField(
|
||||
value = searchEntry,
|
||||
onValueChange = onSearchEntryChanged,
|
||||
placeholder = { Text(text = stringResource(id = R.string.FindByActivity__search)) },
|
||||
shape = RoundedCornerShape(32.dp),
|
||||
colors = TextFieldDefaults.colors(
|
||||
unfocusedIndicatorColor = Color.Transparent,
|
||||
focusedIndicatorColor = Color.Transparent,
|
||||
disabledIndicatorColor = Color.Transparent,
|
||||
errorIndicatorColor = Color.Transparent,
|
||||
cursorColor = MaterialTheme.colorScheme.onSurface
|
||||
),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 10.dp)
|
||||
.focusRequester(focusRequester)
|
||||
.heightIn(min = 44.dp),
|
||||
contentPadding = TextFieldDefaults.contentPaddingWithoutLabel(top = 10.dp, bottom = 10.dp)
|
||||
)
|
||||
|
||||
LazyColumn {
|
||||
items(
|
||||
items = supportedCountryPrefixes
|
||||
) {
|
||||
CountryPrefixRowItem(
|
||||
searchTerm = searchEntry,
|
||||
countryPrefix = it,
|
||||
onClick = { onCountryPrefixSelected(it) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
focusRequester.requestFocus()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CountryPrefixRowItem(
|
||||
searchTerm: String,
|
||||
countryPrefix: CountryPrefix,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
val regionDisplayName = remember(countryPrefix.regionCode, Locale.current) {
|
||||
PhoneNumberFormatter.getRegionDisplayName(countryPrefix.regionCode).orElse(countryPrefix.regionCode)
|
||||
}
|
||||
|
||||
if (searchTerm.isNotBlank() && !regionDisplayName.contains(searchTerm, ignoreCase = true)) {
|
||||
return
|
||||
}
|
||||
|
||||
val highlightedName: AnnotatedString = remember(regionDisplayName, searchTerm) {
|
||||
if (searchTerm.isBlank()) {
|
||||
AnnotatedString(regionDisplayName)
|
||||
} else {
|
||||
buildAnnotatedString {
|
||||
append(regionDisplayName)
|
||||
|
||||
val startIndex = regionDisplayName.indexOf(searchTerm, ignoreCase = true)
|
||||
|
||||
addStyle(
|
||||
style = SpanStyle(
|
||||
fontWeight = FontWeight.Bold
|
||||
),
|
||||
start = startIndex,
|
||||
end = startIndex + searchTerm.length
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column(
|
||||
verticalArrangement = spacedBy((-2).dp),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(onClick = onClick)
|
||||
.padding(horizontal = dimensionResource(id = CoreUiR.dimen.gutter))
|
||||
.padding(top = 16.dp, bottom = 14.dp)
|
||||
) {
|
||||
Text(
|
||||
text = highlightedName
|
||||
)
|
||||
|
||||
Text(
|
||||
text = countryPrefix.toString(),
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(name = "Light Theme", group = "content - phone", uiMode = Configuration.UI_MODE_NIGHT_NO)
|
||||
@Preview(name = "Dark Theme", group = "content - phone", uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
@@ -606,7 +490,7 @@ private fun ContentPreviewPhoneNumber() {
|
||||
),
|
||||
onUserEntryChanged = {},
|
||||
onNextClick = {},
|
||||
onSelectCountryPrefixClick = {},
|
||||
onSelectCountryClick = {},
|
||||
onQrCodeScanClicked = {}
|
||||
)
|
||||
}
|
||||
@@ -625,23 +509,8 @@ private fun ContentPreviewUsername() {
|
||||
),
|
||||
onUserEntryChanged = {},
|
||||
onNextClick = {},
|
||||
onSelectCountryPrefixClick = {},
|
||||
onSelectCountryClick = {},
|
||||
onQrCodeScanClicked = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(name = "Light Theme", group = "select country", uiMode = Configuration.UI_MODE_NIGHT_NO)
|
||||
@Preview(name = "Dark Theme", group = "select country", uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun SelectCountryScreenPreview() {
|
||||
Previews.Preview {
|
||||
SelectCountryScreen(
|
||||
paddingValues = PaddingValues(0.dp),
|
||||
searchEntry = "",
|
||||
onSearchEntryChanged = {},
|
||||
supportedCountryPrefixes = FindByState(mode = FindByMode.PHONE_NUMBER).supportedCountryPrefixes,
|
||||
onCountryPrefixSelected = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,8 @@ import com.google.i18n.phonenumbers.NumberParseException
|
||||
import com.google.i18n.phonenumbers.PhoneNumberUtil
|
||||
import org.signal.core.util.orNull
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.registration.util.CountryPrefix
|
||||
import org.thoughtcrime.securesms.registration.ui.countrycode.Country
|
||||
import org.thoughtcrime.securesms.registration.ui.countrycode.CountryUtils
|
||||
|
||||
/**
|
||||
* State for driving find by number/username screen.
|
||||
@@ -17,12 +18,11 @@ import org.thoughtcrime.securesms.registration.util.CountryPrefix
|
||||
data class FindByState(
|
||||
val mode: FindByMode,
|
||||
val userEntry: String = "",
|
||||
val supportedCountryPrefixes: List<CountryPrefix> = PhoneNumberUtil.getInstance().supportedCallingCodes
|
||||
.map { CountryPrefix(it, PhoneNumberUtil.getInstance().getRegionCodeForCountryCode(it)) }
|
||||
.sortedBy { it.digits.toString() },
|
||||
val selectedCountryPrefix: CountryPrefix = supportedCountryPrefixes.first(),
|
||||
val countryPrefixSearchEntry: String = "",
|
||||
val isLookupInProgress: Boolean = false
|
||||
val supportedCountries: List<Country> = CountryUtils.getCountries(),
|
||||
val filteredCountries: List<Country> = emptyList(),
|
||||
val selectedCountry: Country = supportedCountries.first(),
|
||||
val isLookupInProgress: Boolean = false,
|
||||
val query: String = ""
|
||||
) {
|
||||
companion object {
|
||||
fun startingState(self: Recipient, mode: FindByMode): FindByState {
|
||||
@@ -36,7 +36,7 @@ data class FindByState(
|
||||
|
||||
val state = FindByState(mode = mode)
|
||||
return state.copy(
|
||||
selectedCountryPrefix = state.supportedCountryPrefixes.firstOrNull { it.digits == countryCode } ?: state.supportedCountryPrefixes.first()
|
||||
selectedCountry = state.supportedCountries.firstOrNull { it.countryCode == countryCode } ?: state.supportedCountries.first()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import org.signal.core.util.concurrent.safeBlockingGet
|
||||
import org.thoughtcrime.securesms.profiles.manage.UsernameRepository
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientRepository
|
||||
import org.thoughtcrime.securesms.registration.util.CountryPrefix
|
||||
import org.thoughtcrime.securesms.registration.ui.countrycode.Country
|
||||
import org.thoughtcrime.securesms.util.UsernameUtil
|
||||
|
||||
class FindByViewModel(
|
||||
@@ -40,12 +40,8 @@ class FindByViewModel(
|
||||
internalState.value = state.value.copy(userEntry = cleansed)
|
||||
}
|
||||
|
||||
fun onCountryPrefixSearchEntryChanged(searchEntry: String) {
|
||||
internalState.value = state.value.copy(countryPrefixSearchEntry = searchEntry)
|
||||
}
|
||||
|
||||
fun onCountryPrefixSelected(countryPrefix: CountryPrefix) {
|
||||
internalState.value = state.value.copy(selectedCountryPrefix = countryPrefix)
|
||||
fun onCountrySelected(country: Country) {
|
||||
internalState.value = state.value.copy(selectedCountry = country)
|
||||
}
|
||||
|
||||
suspend fun onNextClicked(context: Context): FindByResult {
|
||||
@@ -80,7 +76,7 @@ class FindByViewModel(
|
||||
@WorkerThread
|
||||
private fun performPhoneLookup(context: Context): FindByResult {
|
||||
val stateSnapshot = state.value
|
||||
val countryCode = stateSnapshot.selectedCountryPrefix.digits
|
||||
val countryCode = stateSnapshot.selectedCountry.countryCode
|
||||
val nationalNumber = stateSnapshot.userEntry.removePrefix(countryCode.toString())
|
||||
|
||||
val e164 = "+$countryCode$nationalNumber"
|
||||
@@ -92,4 +88,22 @@ class FindByViewModel(
|
||||
is RecipientRepository.LookupResult.Success -> FindByResult.Success(result.recipientId)
|
||||
}
|
||||
}
|
||||
|
||||
fun filterCountries(filterBy: String) {
|
||||
if (filterBy.isEmpty()) {
|
||||
internalState.value = state.value.copy(
|
||||
query = filterBy,
|
||||
filteredCountries = emptyList()
|
||||
)
|
||||
} else {
|
||||
internalState.value = state.value.copy(
|
||||
query = filterBy,
|
||||
filteredCountries = state.value.supportedCountries.filter { country: Country ->
|
||||
country.name.contains(filterBy, ignoreCase = true) ||
|
||||
country.countryCode.toString().contains(filterBy) ||
|
||||
(filterBy.equals("usa", ignoreCase = true) && country.name.equals("United States", ignoreCase = true))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user