Update country picker for findBy and changeNumber.

This commit is contained in:
Michelle Tang
2025-02-07 14:35:45 -05:00
committed by GitHub
parent 20ab362f2c
commit 850c20bcd8
11 changed files with 115 additions and 330 deletions

View File

@@ -1,132 +0,0 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.components.settings.app.changenumber;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.ListFragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;
import androidx.navigation.fragment.NavHostFragment;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.loaders.CountryListLoader;
import java.util.ArrayList;
import java.util.Map;
public final class ChangeNumberCountryPickerFragment extends ListFragment implements LoaderManager.LoaderCallbacks<ArrayList<Map<String, String>>> {
public static final String KEY_COUNTRY = "country";
public static final String KEY_COUNTRY_CODE = "country_code";
private EditText countryFilter;
private ChangeNumberViewModel model;
private String resultKey;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
return inflater.inflate(R.layout.fragment_registration_country_picker, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if (getArguments() != null) {
ChangeNumberCountryPickerFragmentArgs arguments = ChangeNumberCountryPickerFragmentArgs.fromBundle(requireArguments());
resultKey = arguments.getResultKey();
}
if (resultKey == null) {
model = new ViewModelProvider(requireActivity()).get(ChangeNumberViewModel.class);
}
countryFilter = view.findViewById(R.id.country_search);
countryFilter.addTextChangedListener(new FilterWatcher());
LoaderManager.getInstance(this).initLoader(0, null, this).forceLoad();
}
@Override
public void onListItemClick(@NonNull ListView listView, @NonNull View view, int position, long id) {
Map<String, String> item = (Map<String, String>) getListAdapter().getItem(position);
int countryCode = Integer.parseInt(item.get("country_code").replace("+", ""));
String countryName = item.get("country_name");
if (resultKey == null) {
model.setNewCountry(countryCode, countryName);
} else {
Bundle result = new Bundle();
result.putString(KEY_COUNTRY, countryName);
result.putInt(KEY_COUNTRY_CODE, countryCode);
getParentFragmentManager().setFragmentResult(resultKey, result);
}
NavHostFragment.findNavController(this).navigateUp();
}
@Override
public @NonNull Loader<ArrayList<Map<String, String>>> onCreateLoader(int id, @Nullable Bundle args) {
return new CountryListLoader(getActivity());
}
@Override
public void onLoadFinished(@NonNull Loader<ArrayList<Map<String, String>>> loader,
@NonNull ArrayList<Map<String, String>> results)
{
((TextView) getListView().getEmptyView()).setText(
R.string.country_selection_fragment__no_matching_countries);
String[] from = { "country_name", "country_code" };
int[] to = { R.id.country_name, R.id.country_code };
setListAdapter(new SimpleAdapter(getActivity(), results, R.layout.country_list_item, from, to));
applyFilter(countryFilter.getText());
}
private void applyFilter(@NonNull CharSequence text) {
SimpleAdapter listAdapter = (SimpleAdapter) getListAdapter();
if (listAdapter != null) {
listAdapter.getFilter().filter(text);
}
}
@Override
public void onLoaderReset(@NonNull Loader<ArrayList<Map<String, String>>> loader) {
setListAdapter(null);
}
private class FilterWatcher implements TextWatcher {
@Override
public void afterTextChanged(Editable s) {
applyFilter(s);
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
}
}

View File

@@ -14,12 +14,15 @@ import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import org.signal.core.util.getParcelableCompat
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.LoggingFragment
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.ViewBinderDelegate
import org.thoughtcrime.securesms.databinding.FragmentChangeNumberEnterPhoneNumberBinding
import org.thoughtcrime.securesms.registration.ui.countrycode.Country
import org.thoughtcrime.securesms.registration.util.ChangeNumberInputController
import org.thoughtcrime.securesms.registrationv3.ui.countrycode.CountryCodeFragment
import org.thoughtcrime.securesms.util.Dialogs
import org.thoughtcrime.securesms.util.navigation.safeNavigate
@@ -65,15 +68,15 @@ class ChangeNumberEnterPhoneNumberFragment : LoggingFragment(R.layout.fragment_c
override fun onNumberInputDone(view: View) = Unit
override fun onPickCountry(view: View) {
findNavController().safeNavigate(ChangeNumberEnterPhoneNumberFragmentDirections.actionEnterPhoneNumberChangeFragmentToCountryPickerFragment(OLD_NUMBER_COUNTRY_SELECT))
findNavController().safeNavigate(ChangeNumberEnterPhoneNumberFragmentDirections.actionEnterPhoneNumberChangeFragmentToCountryPickerFragment(OLD_NUMBER_COUNTRY_SELECT, viewModel.oldCountry))
}
override fun setNationalNumber(number: String) {
viewModel.setOldNationalNumber(number)
}
override fun setCountry(countryCode: Int) {
viewModel.setOldCountry(countryCode)
override fun setCountry(country: Country) {
viewModel.setOldCountry(country)
}
}
)
@@ -96,25 +99,27 @@ class ChangeNumberEnterPhoneNumberFragment : LoggingFragment(R.layout.fragment_c
}
override fun onPickCountry(view: View) {
findNavController().safeNavigate(ChangeNumberEnterPhoneNumberFragmentDirections.actionEnterPhoneNumberChangeFragmentToCountryPickerFragment(NEW_NUMBER_COUNTRY_SELECT))
findNavController().safeNavigate(ChangeNumberEnterPhoneNumberFragmentDirections.actionEnterPhoneNumberChangeFragmentToCountryPickerFragment(NEW_NUMBER_COUNTRY_SELECT, viewModel.newCountry))
}
override fun setNationalNumber(number: String) {
viewModel.setNewNationalNumber(number)
}
override fun setCountry(countryCode: Int) {
viewModel.setNewCountry(countryCode)
override fun setCountry(country: Country) {
viewModel.setNewCountry(country)
}
}
)
parentFragmentManager.setFragmentResultListener(OLD_NUMBER_COUNTRY_SELECT, this) { _: String, bundle: Bundle ->
viewModel.setOldCountry(bundle.getInt(ChangeNumberCountryPickerFragment.KEY_COUNTRY_CODE), bundle.getString(ChangeNumberCountryPickerFragment.KEY_COUNTRY))
val country = bundle.getParcelableCompat(CountryCodeFragment.RESULT_COUNTRY, Country::class.java)!!
viewModel.setOldCountry(country)
}
parentFragmentManager.setFragmentResultListener(NEW_NUMBER_COUNTRY_SELECT, this) { _: String, bundle: Bundle ->
viewModel.setNewCountry(bundle.getInt(ChangeNumberCountryPickerFragment.KEY_COUNTRY_CODE), bundle.getString(ChangeNumberCountryPickerFragment.KEY_COUNTRY))
val country = bundle.getParcelableCompat(CountryCodeFragment.RESULT_COUNTRY, Country::class.java)!!
viewModel.setNewCountry(country)
}
viewModel.liveOldNumberState.observe(viewLifecycleOwner, oldController::updateNumber)

View File

@@ -7,6 +7,7 @@ package org.thoughtcrime.securesms.components.settings.app.changenumber
import org.thoughtcrime.securesms.registration.data.network.Challenge
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult
import org.thoughtcrime.securesms.registration.ui.countrycode.Country
import org.thoughtcrime.securesms.registration.viewmodel.NumberViewState
import org.whispersystems.signalservice.api.svr.Svr3Credentials
import org.whispersystems.signalservice.internal.push.AuthCredentials
@@ -32,7 +33,9 @@ data class ChangeNumberState(
val captchaToken: String? = null,
val challengesRequested: List<Challenge> = emptyList(),
val challengesPresented: Set<Challenge> = emptySet(),
val allowedToRequestCode: Boolean = false
val allowedToRequestCode: Boolean = false,
val oldCountry: Country? = null,
val newCountry: Country? = null
) {
val challengesRemaining: List<Challenge> = challengesRequested.filterNot { it in challengesPresented }
}

View File

@@ -30,6 +30,7 @@ import org.thoughtcrime.securesms.registration.data.network.SessionMetadataResul
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult
import org.thoughtcrime.securesms.registration.sms.SmsRetrieverReceiver
import org.thoughtcrime.securesms.registration.ui.RegistrationViewModel
import org.thoughtcrime.securesms.registration.ui.countrycode.Country
import org.thoughtcrime.securesms.registration.viewmodel.NumberViewState
import org.thoughtcrime.securesms.registration.viewmodel.SvrAuthCredentialSet
import org.thoughtcrime.securesms.util.dualsim.MccMncProducer
@@ -100,15 +101,24 @@ class ChangeNumberViewModel : ViewModel() {
val svrTriesRemaining: Int
get() = store.value.svrTriesRemaining
val oldCountry: Country?
get() = store.value.oldCountry
val newCountry: Country?
get() = store.value.newCountry
fun setOldNationalNumber(updatedNumber: String) {
store.update {
it.copy(oldPhoneNumber = oldNumberState.toBuilder().nationalNumber(updatedNumber).build())
}
}
fun setOldCountry(countryCode: Int, country: String? = null) {
fun setOldCountry(country: Country) {
store.update {
it.copy(oldPhoneNumber = oldNumberState.toBuilder().selectedCountryDisplayName(country).countryCode(countryCode).build())
it.copy(
oldPhoneNumber = oldNumberState.toBuilder().selectedCountryDisplayName(country.name).countryCode(country.countryCode).build(),
oldCountry = country
)
}
}
@@ -118,9 +128,12 @@ class ChangeNumberViewModel : ViewModel() {
}
}
fun setNewCountry(countryCode: Int, country: String? = null) {
fun setNewCountry(country: Country) {
store.update {
it.copy(number = number.toBuilder().selectedCountryDisplayName(country).countryCode(countryCode).build())
it.copy(
number = number.toBuilder().selectedCountryDisplayName(country.name).countryCode(country.countryCode).build(),
newCountry = country
)
}
}

View File

@@ -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 = {}
)
}
}

View File

@@ -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()
)
}
}

View File

@@ -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))
}
)
}
}
}

View File

@@ -47,6 +47,7 @@ object CountryUtils {
}
}
@JvmStatic
fun countryToEmoji(countryCode: String): String {
return if (countryCode.isNotEmpty()) {
countryCode

View File

@@ -21,7 +21,10 @@ import com.google.i18n.phonenumbers.PhoneNumberUtil;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.LabeledEditText;
import org.thoughtcrime.securesms.registration.ui.countrycode.Country;
import org.thoughtcrime.securesms.registration.ui.countrycode.CountryUtils;
import org.thoughtcrime.securesms.registration.viewmodel.NumberViewState;
import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
/**
* Handle the logic and formatting of phone number input specifically for change number flows.
@@ -150,7 +153,7 @@ public final class ChangeNumberInputController {
private void setCountryDisplay(@Nullable String regionDisplayName) {
countrySpinnerAdapter.clear();
if (regionDisplayName == null) {
if (regionDisplayName == null || regionDisplayName.isEmpty()) {
countrySpinnerAdapter.add(context.getString(R.string.RegistrationActivity_select_your_country));
} else {
countrySpinnerAdapter.add(regionDisplayName);
@@ -245,7 +248,8 @@ public final class ChangeNumberInputController {
}
if (!isUpdating) {
callbacks.setCountry(countryCode);
Country country = new Country(CountryUtils.countryToEmoji(regionCode), PhoneNumberFormatter.getRegionDisplayName(regionCode).orElse(""), countryCode, regionCode);
callbacks.setCountry(country);
}
}
@@ -269,6 +273,6 @@ public final class ChangeNumberInputController {
void setNationalNumber(@NonNull String number);
void setCountry(int countryCode);
void setCountry(@NonNull Country country);
}
}

View File

@@ -75,6 +75,7 @@ class CountryCodeFragment : ComposeFragment() {
companion object {
private val TAG = Log.tag(CountryCodeFragment::class.java)
const val RESULT_KEY = "result_key"
const val REQUEST_KEY_COUNTRY = "request_key_country"
const val REQUEST_COUNTRY = "country"
const val RESULT_COUNTRY = "country"
@@ -86,6 +87,8 @@ class CountryCodeFragment : ComposeFragment() {
override fun FragmentContent() {
val state by viewModel.state.collectAsStateWithLifecycle()
val resultKey = arguments?.getString(RESULT_KEY) ?: REQUEST_KEY_COUNTRY
Screen(
state = state,
title = stringResource(R.string.CountryCodeFragment__your_country),
@@ -93,7 +96,7 @@ class CountryCodeFragment : ComposeFragment() {
onDismissed = { findNavController().popBackStack() },
onClick = { country ->
setFragmentResult(
REQUEST_KEY_COUNTRY,
resultKey,
bundleOf(
RESULT_COUNTRY to country
)

View File

@@ -58,13 +58,18 @@
<fragment
android:id="@+id/countryPickerFragment"
android:name="org.thoughtcrime.securesms.components.settings.app.changenumber.ChangeNumberCountryPickerFragment"
android:name="org.thoughtcrime.securesms.registrationv3.ui.countrycode.CountryCodeFragment"
tools:layout="@layout/fragment_registration_country_picker">
<argument
android:name="result_key"
app:argType="string"
app:nullable="true" />
<argument
android:name="country"
app:argType="org.thoughtcrime.securesms.registration.ui.countrycode.Country"
app:nullable="true" />
</fragment>
<fragment