diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberCountryPickerFragment.java b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberCountryPickerFragment.java deleted file mode 100644 index 2bd26b37e4..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberCountryPickerFragment.java +++ /dev/null @@ -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>> { - - 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 item = (Map) 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>> onCreateLoader(int id, @Nullable Bundle args) { - return new CountryListLoader(getActivity()); - } - - @Override - public void onLoadFinished(@NonNull Loader>> loader, - @NonNull ArrayList> 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>> 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) { - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberEnterPhoneNumberFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberEnterPhoneNumberFragment.kt index b020a5ead9..b58e1e9bf3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberEnterPhoneNumberFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberEnterPhoneNumberFragment.kt @@ -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) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberState.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberState.kt index 0ef529aaa2..2b9f2c6469 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberState.kt @@ -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 = emptyList(), val challengesPresented: Set = emptySet(), - val allowedToRequestCode: Boolean = false + val allowedToRequestCode: Boolean = false, + val oldCountry: Country? = null, + val newCountry: Country? = null ) { val challengesRemaining: List = challengesRequested.filterNot { it in challengesPresented } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberViewModel.kt index fabf0df98b..e6bd0ac2a4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberViewModel.kt @@ -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 + ) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/findby/FindByActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/findby/FindByActivity.kt index 457b8e0fe8..027a72ee8a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/findby/FindByActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/findby/FindByActivity.kt @@ -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 -) { - 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 = {} - ) - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/findby/FindByState.kt b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/findby/FindByState.kt index d701b2f7bf..f39e2ab3a7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/findby/FindByState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/findby/FindByState.kt @@ -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 = 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 = CountryUtils.getCountries(), + val filteredCountries: List = 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() ) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/findby/FindByViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/findby/FindByViewModel.kt index f31ca2e091..418e0eee93 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/findby/FindByViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/findby/FindByViewModel.kt @@ -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)) + } + ) + } + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/ui/countrycode/CountryUtils.kt b/app/src/main/java/org/thoughtcrime/securesms/registration/ui/countrycode/CountryUtils.kt index 3b5fdb52ed..858dc8cda3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/ui/countrycode/CountryUtils.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/ui/countrycode/CountryUtils.kt @@ -47,6 +47,7 @@ object CountryUtils { } } + @JvmStatic fun countryToEmoji(countryCode: String): String { return if (countryCode.isNotEmpty()) { countryCode diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/util/ChangeNumberInputController.java b/app/src/main/java/org/thoughtcrime/securesms/registration/util/ChangeNumberInputController.java index 7f2c23f6aa..ea6b1eefbc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/util/ChangeNumberInputController.java +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/util/ChangeNumberInputController.java @@ -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); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/countrycode/CountryCodeFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/countrycode/CountryCodeFragment.kt index 40deef8e1b..0f29457acd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/countrycode/CountryCodeFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/countrycode/CountryCodeFragment.kt @@ -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 ) diff --git a/app/src/main/res/navigation/app_settings_change_number.xml b/app/src/main/res/navigation/app_settings_change_number.xml index 85b3761ef7..a49476805c 100644 --- a/app/src/main/res/navigation/app_settings_change_number.xml +++ b/app/src/main/res/navigation/app_settings_change_number.xml @@ -58,13 +58,18 @@ + +