Add fixes to country picker.

This commit is contained in:
Michelle Tang
2025-02-05 14:19:59 -05:00
committed by Greyson Parrelli
parent e840efcecc
commit 02e7c035aa
8 changed files with 121 additions and 34 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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