Closed adrianegraphene closed 2 months ago
Hi... I just published 0.10.0
containing this feature. You can check the updated README 🙂
Thank you! That worked well for me. I was using your formatter in conjunction with another KMP library "CountryPickerBasicTextField". With your update, I had an issue that I believe was due to that other library. For anyone who's going through the same issue. Here's the overcomplicated way I made this work "naturally".
Thanks again for the speedy response! I am all good now.
package com.fyncom.robocash.ui.composables
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Text
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.text.intl.Locale
import androidx.compose.ui.unit.dp
import com.bayocode.kphonenumber.KPhoneNumber
import com.fyncom.robocash.SharedRes.strings
import com.fyncom.robocash.ui.theme.DarkBlue
import com.fyncom.robocash.ui.theme.DarkPurple
import com.fyncom.robocash.ui.theme.FyncomRed
import com.fyncom.robocash.ui.theme.PaleCoral
import com.fyncom.robocash.utils.get
import com.fyncom.robocash.utils.validatePhoneNumber
import io.github.aakira.napier.Napier
import network.chaintech.cmpcountrycodepicker.model.CountryDetails
import network.chaintech.cmpcountrycodepicker.ui.CountryPickerBasicTextField
private var _phoneNumber by mutableStateOf("")
private var _fullPhoneNumber by mutableStateOf("")
private var _isNumberValid by mutableStateOf(false)
private var _isoCountryCode by mutableStateOf(Locale.current.region ?: "US")
val phoneNumber: String get() = _phoneNumber
val fullPhoneNumber: String get() = _fullPhoneNumber
val isoCountryCode: String get() = _isoCountryCode
val isNumberValid: Boolean get() = _isNumberValid
private val kPhoneNumber = KPhoneNumber()
private val partialFormatter = kPhoneNumber.partialFormatter()
private var _dialCountryCode by mutableStateOf(isoCountryCode ?: "US")
val dialCountryCode: String get() = _dialCountryCode
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CountryCodePhoneInput(
selectedCountryCodeAndPhoneNumber: MutableState<Pair<String, String>>,
onIsoCodeSelected: (String?) -> Unit,
onIsNumberValid: MutableState<Boolean>,
labelText: String = strings.phone_number.get(),
accessText: String = strings.selectOrEnterCountry.get(),
) {
Spacer(M.height(10.dp))
var mobileNumber by remember { mutableStateOf(selectedCountryCodeAndPhoneNumber.value.second) }
val selectedCountryState = remember { mutableStateOf<CountryDetails?>(null) }
val defaultCountryCode = remember { mutableStateOf(Locale.current.region) }
val dialCode =
kPhoneNumber.countryCode(selectedCountryState.value?.countryCode ?: isoCountryCode)
CountryPickerBasicTextField(
// keyboard shows up on physical device, but not on simulator
mobileNumber = mobileNumber,
defaultCountryCode = isoCountryCode,
onMobileNumberChange = { newNumber ->
try {
// Strip spaces and special characters - only numbers
val newDigits = newNumber.replace(Regex("[^0-9]"), "")
if (newDigits.length > _phoneNumber.length) {
// Compare char by char to find the new digit
var newDigit: Char? = null
for (i in _phoneNumber.indices) {
if (_phoneNumber[i] != newDigits[i]) {
newDigit = newDigits[i]
break
}
}
if (newDigit == null) { // If no mismatch found, the new digit is at the end
newDigit = newDigits.last()
}
_phoneNumber += newDigit // Append the new digit
} else if (newDigits.length < _phoneNumber.length) { // Handle deletion
_phoneNumber = _phoneNumber.substring(0, _phoneNumber.length-1)
} else if (newDigits.length == phoneNumber.length) { // Handle deletion of the last digits
_phoneNumber = _phoneNumber.substring(0, _phoneNumber.length - 1)
}
Napier.d { "phone number: $_phoneNumber" }
_isNumberValid = validatePhoneNumber(_phoneNumber, kPhoneNumber, isoCountryCode)
onIsNumberValid.value = _isNumberValid
var formattedString = partialFormatter.formatPartial(_phoneNumber)
if (dialCode != 1) {
// for non US, format with dial code first, then remove dial code index & trim prepending strings
formattedString = partialFormatter.formatPartial("+" + dialCode.toString() + _phoneNumber)
formattedString = formattedString.substring(dialCode.toString().length+1, formattedString.length).trimStart()
}
mobileNumber = formattedString // Update the formatted number for display
if (_isNumberValid) {
Napier.d { "number is valid selectedCS ${selectedCountryState.value?.countryCode} currentLocale $isoCountryCode with dial $dialCode" }
selectedCountryCodeAndPhoneNumber.value = dialCode.toString() to newNumber
onIsoCodeSelected(
selectedCountryState.value?.countryCode?.uppercase() ?: isoCountryCode
)
}
} catch (e: Exception) {
// Handle the exception (e.g., log it or show an error message)
Napier.e("Error parsing phone number: ${e.message}")
_isNumberValid = false
onIsNumberValid.value = false
}
},
//make sure you put the dialCode into selectedCountryCodeAndPhoneNumber and not the isoCode. should work like this.
onCountrySelected = { country ->
// note - country code is lowercased
Napier.d { "country code $country selected" }
_isoCountryCode = country.countryCode.uppercase()
selectedCountryState.value = country
selectedCountryCodeAndPhoneNumber.value = country.countryPhoneNumberCode to mobileNumber
onIsoCodeSelected(_isoCountryCode)
},
modifier = M.fillMaxWidth().height(56.dp),
showCountryFlag = true,
showCountryPhoneCode = true,
showCountryName = false,
showCountryCode = false,
showArrowDropDown = true,
label = { Text(labelText) },
defaultPaddingValues = PaddingValues(6.dp),
colors = TextFieldDefaults.outlinedTextFieldColors(
focusedTextColor = C.OnSecCon,
unfocusedTextColor = C.E,
focusedBorderColor = C.Pri,
unfocusedBorderColor = C.Pri,
errorBorderColor = FyncomRed,
cursorColor = C.Pri,
errorCursorColor = FyncomRed,
disabledPlaceholderColor = C.Ter,
focusedLabelColor = PaleCoral,
unfocusedLabelColor = DarkPurple,
disabledLabelColor = DarkBlue,
errorLabelColor = C.E
),
// shape = RoundedCornerShape(10.dp)
)
}
Thanks for the great library. I started trying to create a "format as you type" PR, but couldn't quite get it to work.
Do you know of a nice way to make this work?
What I would like, is to make it so that as a user is typing in the first few digits of their number in a "enter your phone number" input field, that the number gets formatted automatically. The jump-sdk library has a great one for Android (it actually gets hypens / paraentheses). The PhoneNumberKit does this in iOS (but only with spaces).
so for a US Country Code, with "94970", the formatter would create "949 70" or "(949)-70"
I would love to see that implemented here if possible - but it does not seem like the easiest thing to do on iOS.