From 02e7c035aa1bf1b9509f42eec70ab6c1b9c2684f Mon Sep 17 00:00:00 2001 From: Michelle Tang Date: Wed, 5 Feb 2025 14:19:59 -0500 Subject: [PATCH] Add fixes to country picker. --- .../registration/ui/RegistrationViewModel.kt | 3 ++ .../ui/countrycode/CountryCodeFragment.kt | 45 ++++++++++++++++--- .../phonenumber/EnterPhoneNumberFragment.kt | 18 +++++--- .../phonenumber/EnterPhoneNumberViewModel.kt | 14 +++--- .../ui/RegistrationViewModel.kt | 3 ++ .../ui/countrycode/CountryCodeFragment.kt | 40 ++++++++++++++--- .../phonenumber/EnterPhoneNumberFragment.kt | 18 +++++--- .../phonenumber/EnterPhoneNumberViewModel.kt | 14 +++--- 8 files changed, 121 insertions(+), 34 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/ui/RegistrationViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/registration/ui/RegistrationViewModel.kt index 26fa11fcd3..c7100fcf12 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/ui/RegistrationViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/ui/RegistrationViewModel.kt @@ -113,6 +113,9 @@ class RegistrationViewModel : ViewModel() { val phoneNumber: Phonenumber.PhoneNumber? get() = store.value.phoneNumber + val country: Country? + get() = store.value.country + fun maybePrefillE164(context: Context) { Log.v(TAG, "maybePrefillE164()") if (Permissions.hasAll(context, Manifest.permission.READ_PHONE_STATE, Manifest.permission.READ_PHONE_NUMBERS)) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/ui/countrycode/CountryCodeFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/registration/ui/countrycode/CountryCodeFragment.kt index 41920dffdd..56e8d5d5d4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/ui/countrycode/CountryCodeFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/ui/countrycode/CountryCodeFragment.kt @@ -15,8 +15,10 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text @@ -24,8 +26,13 @@ import androidx.compose.material3.TextField import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.rememberVectorPainter @@ -34,6 +41,7 @@ import androidx.compose.ui.res.vectorResource 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.KeyboardType import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.text.withStyle import androidx.compose.ui.unit.dp @@ -42,6 +50,7 @@ import androidx.fragment.app.viewModels import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.fragment.findNavController import org.signal.core.ui.Dividers +import org.signal.core.ui.IconButtons.IconButton import org.signal.core.ui.Previews import org.signal.core.ui.Scaffolds import org.signal.core.ui.SignalPreview @@ -236,21 +245,43 @@ fun SearchBar( hint: String = stringResource(R.string.CountryCodeFragment__search_by), onSearch: (String) -> Unit = {} ) { + val focusRequester = remember { FocusRequester() } + var showKeyboard by remember { mutableStateOf(false) } + TextField( value = text, onValueChange = { onSearch(it) }, placeholder = { Text(hint) }, - // TODO(michelle): Add keyboard switch to dialpad -// trailingIcon = { -// Icon( -// imageVector = ImageVector.vectorResource(R.drawable.symbol_number_pad_24), -// contentDescription = "Search icon" -// ) -// }, + trailingIcon = { + IconButton(onClick = { + showKeyboard = !showKeyboard + focusRequester.requestFocus() + }) { + if (showKeyboard) { + Icon( + imageVector = ImageVector.vectorResource(R.drawable.symbol_keyboard_24), + contentDescription = null + ) + } else { + Icon( + imageVector = ImageVector.vectorResource(R.drawable.symbol_number_pad_24), + contentDescription = null + ) + } + } + }, + keyboardOptions = KeyboardOptions( + keyboardType = if (showKeyboard) { + KeyboardType.Number + } else { + KeyboardType.Text + } + ), shape = RoundedCornerShape(32.dp), modifier = modifier .fillMaxWidth() .height(54.dp) + .focusRequester(focusRequester) .padding(horizontal = 16.dp), visualTransformation = VisualTransformation.None, colors = TextFieldDefaults.colors( diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/ui/phonenumber/EnterPhoneNumberFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/registration/ui/phonenumber/EnterPhoneNumberFragment.kt index 4b3b500d3c..1e55aaab6b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/ui/phonenumber/EnterPhoneNumberFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/ui/phonenumber/EnterPhoneNumberFragment.kt @@ -125,7 +125,6 @@ class EnterPhoneNumberFragment : LoggingFragment(R.layout.fragment_registration_ sharedViewModel.uiState.observe(viewLifecycleOwner) { sharedState -> presentRegisterButton(sharedState) updateEnabledControls(sharedState.inProgress, sharedState.isReRegister) - updateCountrySelection(sharedState.country) sharedState.networkError?.let { presentNetworkError(it) @@ -185,7 +184,13 @@ class EnterPhoneNumberFragment : LoggingFragment(R.layout.fragment_registration_ initializeInputFields() val existingPhoneNumber = sharedViewModel.phoneNumber - if (existingPhoneNumber != null) { + val country = sharedViewModel.country + + if (country != null) { + binding.countryEmoji.text = country.emoji + binding.country.text = country.name + spinnerView.setText(country.countryCode) + } else if (existingPhoneNumber != null) { fragmentViewModel.restoreState(existingPhoneNumber) spinnerView.setText(existingPhoneNumber.countryCode.toString()) phoneNumberInputLayout.setText(existingPhoneNumber.nationalNumber.toString()) @@ -200,9 +205,7 @@ class EnterPhoneNumberFragment : LoggingFragment(R.layout.fragment_registration_ if (country != null) { binding.countryEmoji.text = country.emoji binding.country.text = country.name - binding.countryCode.editText?.setText(country.countryCode) } - sharedViewModel.clearCountry() } private fun reformatText(text: Editable?) { @@ -248,7 +251,12 @@ class EnterPhoneNumberFragment : LoggingFragment(R.layout.fragment_registration_ val sanitized = s.toString().filter { c -> c.isDigit() } if (sanitized.isNotNullOrBlank()) { val countryCode: Int = sanitized.toInt() - fragmentViewModel.setCountry(countryCode) + if (sharedViewModel.country != null) { + fragmentViewModel.setCountry(countryCode, sharedViewModel.country) + sharedViewModel.clearCountry() + } else { + fragmentViewModel.setCountry(countryCode) + } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/ui/phonenumber/EnterPhoneNumberViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/registration/ui/phonenumber/EnterPhoneNumberViewModel.kt index 5df9dc986c..64528590c6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/ui/phonenumber/EnterPhoneNumberViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/ui/phonenumber/EnterPhoneNumberViewModel.kt @@ -62,22 +62,24 @@ class EnterPhoneNumberViewModel : ViewModel() { store.update { it.copy(phoneNumber = phoneNumber ?: "") } } - fun setCountry(digits: Int) { + fun setCountry(digits: Int, country: Country? = null) { val matchingIndex = countryCodeToAdapterIndex(digits) if (matchingIndex == -1) { Log.d(TAG, "Invalid country code specified $digits") return } + val matchedCountry = Country( + name = PhoneNumberFormatter.getRegionDisplayName(supportedCountryPrefixes[matchingIndex].regionCode).orElse(""), + emoji = CountryUtils.countryToEmoji(supportedCountryPrefixes[matchingIndex].regionCode), + countryCode = digits.toString() + ) + store.update { it.copy( countryPrefixIndex = matchingIndex, phoneNumberRegionCode = supportedCountryPrefixes[matchingIndex].regionCode, - country = Country( - name = PhoneNumberFormatter.getRegionDisplayName(supportedCountryPrefixes[matchingIndex].regionCode).orElse(""), - emoji = CountryUtils.countryToEmoji(supportedCountryPrefixes[matchingIndex].regionCode), - countryCode = digits.toString() - ) + country = country ?: matchedCountry ) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/RegistrationViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/RegistrationViewModel.kt index ca10f50179..6de5eab31e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/RegistrationViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/RegistrationViewModel.kt @@ -129,6 +129,9 @@ class RegistrationViewModel : ViewModel() { val phoneNumber: Phonenumber.PhoneNumber? get() = store.value.phoneNumber + val country: Country? + get() = store.value.country + fun maybePrefillE164(context: Context) { Log.v(TAG, "maybePrefillE164()") if (Permissions.hasAll(context, Manifest.permission.READ_PHONE_STATE, Manifest.permission.READ_PHONE_NUMBERS)) { 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 48730d3323..0ddde47b4c 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 @@ -15,6 +15,7 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon @@ -25,8 +26,13 @@ import androidx.compose.material3.TextField import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.rememberVectorPainter @@ -35,6 +41,7 @@ import androidx.compose.ui.res.vectorResource 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.KeyboardType import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.text.withStyle import androidx.compose.ui.unit.dp @@ -43,6 +50,7 @@ import androidx.fragment.app.viewModels import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.fragment.findNavController import org.signal.core.ui.Dividers +import org.signal.core.ui.IconButtons.IconButton import org.signal.core.ui.Previews import org.signal.core.ui.Scaffolds import org.signal.core.ui.SignalPreview @@ -237,21 +245,43 @@ fun SearchBar( hint: String = stringResource(R.string.CountryCodeFragment__search_by), onSearch: (String) -> Unit = {} ) { + val focusRequester = remember { FocusRequester() } + var showKeyboard by remember { mutableStateOf(false) } + TextField( value = text, onValueChange = { onSearch(it) }, placeholder = { Text(hint) }, trailingIcon = { - // TODO(michelle): Add keyboard switch to dialpad - Icon( - imageVector = ImageVector.vectorResource(R.drawable.symbol_number_pad_24), - contentDescription = "Search icon" - ) + IconButton(onClick = { + showKeyboard = !showKeyboard + focusRequester.requestFocus() + }) { + if (showKeyboard) { + Icon( + imageVector = ImageVector.vectorResource(R.drawable.symbol_keyboard_24), + contentDescription = null + ) + } else { + Icon( + imageVector = ImageVector.vectorResource(R.drawable.symbol_number_pad_24), + contentDescription = null + ) + } + } }, + keyboardOptions = KeyboardOptions( + keyboardType = if (showKeyboard) { + KeyboardType.Number + } else { + KeyboardType.Text + } + ), shape = RoundedCornerShape(32.dp), modifier = modifier .fillMaxWidth() .height(54.dp) + .focusRequester(focusRequester) .padding(horizontal = 16.dp), visualTransformation = VisualTransformation.None, colors = TextFieldDefaults.colors( diff --git a/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/phonenumber/EnterPhoneNumberFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/phonenumber/EnterPhoneNumberFragment.kt index 650aa02c56..a46edd7790 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/phonenumber/EnterPhoneNumberFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/phonenumber/EnterPhoneNumberFragment.kt @@ -130,7 +130,6 @@ class EnterPhoneNumberFragment : LoggingFragment(R.layout.fragment_registration_ sharedViewModel.uiState.observe(viewLifecycleOwner) { sharedState -> presentRegisterButton(sharedState) updateEnabledControls(sharedState.inProgress, sharedState.isReRegister) - updateCountrySelection(sharedState.country) sharedState.networkError?.let { presentNetworkError(it) @@ -190,7 +189,13 @@ class EnterPhoneNumberFragment : LoggingFragment(R.layout.fragment_registration_ initializeInputFields() val existingPhoneNumber = sharedViewModel.phoneNumber - if (existingPhoneNumber != null) { + + val country = sharedViewModel.country + if (country != null) { + binding.countryEmoji.text = country.emoji + binding.country.text = country.name + spinnerView.setText(country.countryCode) + } else if (existingPhoneNumber != null) { fragmentViewModel.restoreState(existingPhoneNumber) spinnerView.setText(existingPhoneNumber.countryCode.toString()) phoneNumberInputLayout.setText(existingPhoneNumber.nationalNumber.toString()) @@ -210,9 +215,7 @@ class EnterPhoneNumberFragment : LoggingFragment(R.layout.fragment_registration_ if (country != null) { binding.countryEmoji.text = country.emoji binding.country.text = country.name - binding.countryCode.editText?.setText(country.countryCode) } - sharedViewModel.clearCountry() } private fun reformatText(text: Editable?) { @@ -258,7 +261,12 @@ class EnterPhoneNumberFragment : LoggingFragment(R.layout.fragment_registration_ val sanitized = s.toString().filter { c -> c.isDigit() } if (sanitized.isNotNullOrBlank()) { val countryCode: Int = sanitized.toInt() - fragmentViewModel.setCountry(countryCode) + if (sharedViewModel.country != null) { + fragmentViewModel.setCountry(countryCode, sharedViewModel.country) + sharedViewModel.clearCountry() + } else { + fragmentViewModel.setCountry(countryCode) + } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/phonenumber/EnterPhoneNumberViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/phonenumber/EnterPhoneNumberViewModel.kt index c0fb3a2e51..f19e57e8c0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/phonenumber/EnterPhoneNumberViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/phonenumber/EnterPhoneNumberViewModel.kt @@ -60,22 +60,24 @@ class EnterPhoneNumberViewModel : ViewModel() { store.update { it.copy(phoneNumber = phoneNumber ?: "") } } - fun setCountry(digits: Int) { + fun setCountry(digits: Int, country: Country? = null) { val matchingIndex = countryCodeToAdapterIndex(digits) if (matchingIndex == -1) { Log.d(TAG, "Invalid country code specified $digits") return } + val matchedCountry = Country( + name = PhoneNumberFormatter.getRegionDisplayName(supportedCountryPrefixes[matchingIndex].regionCode).orElse(""), + emoji = CountryUtils.countryToEmoji(supportedCountryPrefixes[matchingIndex].regionCode), + countryCode = digits.toString() + ) + store.update { it.copy( countryPrefixIndex = matchingIndex, phoneNumberRegionCode = supportedCountryPrefixes[matchingIndex].regionCode, - country = Country( - name = PhoneNumberFormatter.getRegionDisplayName(supportedCountryPrefixes[matchingIndex].regionCode).orElse(""), - emoji = CountryUtils.countryToEmoji(supportedCountryPrefixes[matchingIndex].regionCode), - countryCode = digits.toString() - ) + country = country ?: matchedCountry ) } }