mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-07-10 15:58:36 +01:00
Update country selection.
This commit is contained in:
committed by
jeffrey-signal
parent
78940ffc17
commit
7e4736969c
@@ -8,6 +8,7 @@ package org.signal.registration
|
||||
import android.app.backup.BackupManager
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import com.google.i18n.phonenumbers.PhoneNumberUtil
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.withContext
|
||||
@@ -17,6 +18,7 @@ import org.signal.archive.LocalBackupRestoreProgress
|
||||
import org.signal.core.models.AccountEntropyPool
|
||||
import org.signal.core.models.MasterKey
|
||||
import org.signal.core.util.Base64
|
||||
import org.signal.core.util.Util
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.libsignal.net.RequestResult
|
||||
import org.signal.libsignal.protocol.IdentityKeyPair
|
||||
@@ -128,6 +130,17 @@ class RegistrationRepository(val context: Context, val networkController: Networ
|
||||
}
|
||||
}
|
||||
|
||||
fun getDefaultRegionCode(): String {
|
||||
val maybeRegionCode = Util.getNetworkCountryIso(context)
|
||||
val maybeCountryCode = PhoneNumberUtil.getInstance().getCountryCodeForRegion(maybeRegionCode)
|
||||
return if (maybeRegionCode != null && maybeCountryCode != 0) {
|
||||
maybeRegionCode
|
||||
} else {
|
||||
Log.w(TAG, "Invalid region or country code. Defaulting to US.")
|
||||
"US"
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getRestoredSvrCredentials(): List<SvrCredentials> = withContext(Dispatchers.IO) {
|
||||
val data = storageController.readInProgressRegistrationData()
|
||||
data.svrCredentials.map { SvrCredentials(username = it.username, password = it.password) }
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package org.signal.registration.screens.countrycode
|
||||
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
* Utility functions used when working with countries
|
||||
*/
|
||||
object CountryUtils {
|
||||
|
||||
@JvmStatic
|
||||
fun countryToEmoji(countryCode: String): String {
|
||||
return if (countryCode.isNotEmpty()) {
|
||||
countryCode
|
||||
.uppercase(Locale.US)
|
||||
.map { char -> Character.codePointAt("$char", 0) - 0x41 + 0x1F1E6 }
|
||||
.map { codePoint -> Character.toChars(codePoint) }
|
||||
.joinToString(separator = "") { charArray -> String(charArray) }
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
}
|
||||
+20
-6
@@ -38,6 +38,8 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalResources
|
||||
import androidx.compose.ui.platform.testTag
|
||||
@@ -158,6 +160,7 @@ private fun ScreenContent(state: PhoneNumberEntryState, onEvent: (PhoneNumberEnt
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
PhoneNumberInputFields(
|
||||
hasValidCountry = state.countryName.isNotEmpty(),
|
||||
countryCode = state.countryCode,
|
||||
formattedNumber = state.formattedNumber,
|
||||
onCountryCodeChanged = { onEvent(PhoneNumberEntryScreenEvents.CountryCodeChanged(it)) },
|
||||
@@ -221,15 +224,17 @@ private fun CountryPicker(
|
||||
.padding(start = 16.dp, end = 12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = emoji,
|
||||
fontSize = 24.sp
|
||||
)
|
||||
if (emoji.isNotEmpty()) {
|
||||
Text(
|
||||
text = emoji,
|
||||
fontSize = 24.sp
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
}
|
||||
|
||||
Text(
|
||||
text = country,
|
||||
text = country.takeIf { country.isNotEmpty() } ?: stringResource(R.string.RegistrationActivity_select_a_country),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.weight(1f)
|
||||
@@ -250,6 +255,7 @@ private fun CountryPicker(
|
||||
*/
|
||||
@Composable
|
||||
private fun PhoneNumberInputFields(
|
||||
hasValidCountry: Boolean,
|
||||
countryCode: String,
|
||||
formattedNumber: String,
|
||||
onCountryCodeChanged: (String) -> Unit,
|
||||
@@ -258,6 +264,7 @@ private fun PhoneNumberInputFields(
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
var phoneNumberTextFieldValue by remember { mutableStateOf(TextFieldValue(formattedNumber)) }
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
|
||||
LaunchedEffect(formattedNumber) {
|
||||
if (phoneNumberTextFieldValue.text != formattedNumber) {
|
||||
@@ -284,6 +291,12 @@ private fun PhoneNumberInputFields(
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(hasValidCountry) {
|
||||
if (hasValidCountry) {
|
||||
focusRequester.requestFocus()
|
||||
}
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = modifier,
|
||||
horizontalArrangement = Arrangement.Start,
|
||||
@@ -326,6 +339,7 @@ private fun PhoneNumberInputFields(
|
||||
},
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.focusRequester(focusRequester)
|
||||
.testTag(TestTags.PHONE_NUMBER_PHONE_FIELD),
|
||||
label = {
|
||||
Text(stringResource(R.string.RegistrationActivity_phone_number_description))
|
||||
|
||||
+4
-4
@@ -14,10 +14,10 @@ import org.signal.registration.util.DebugLoggableModel
|
||||
import kotlin.time.Duration
|
||||
|
||||
data class PhoneNumberEntryState(
|
||||
val regionCode: String = "US",
|
||||
val countryCode: String = "1",
|
||||
val countryName: String = "United States",
|
||||
val countryEmoji: String = "\uD83C\uDDFA\uD83C\uDDF8",
|
||||
val regionCode: String = "",
|
||||
val countryCode: String = "",
|
||||
val countryName: String = "",
|
||||
val countryEmoji: String = "",
|
||||
val nationalNumber: String = "",
|
||||
val formattedNumber: String = "",
|
||||
val sessionE164: String? = null,
|
||||
|
||||
+19
@@ -17,9 +17,11 @@ import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import org.signal.core.models.AccountEntropyPool
|
||||
import org.signal.core.util.E164Util
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.libsignal.net.RequestResult
|
||||
import org.signal.registration.NetworkController
|
||||
@@ -29,6 +31,7 @@ import org.signal.registration.RegistrationFlowState
|
||||
import org.signal.registration.RegistrationRepository
|
||||
import org.signal.registration.RegistrationRoute
|
||||
import org.signal.registration.screens.EventDrivenViewModel
|
||||
import org.signal.registration.screens.countrycode.CountryUtils
|
||||
import org.signal.registration.screens.localbackuprestore.LocalBackupRestoreResult
|
||||
import org.signal.registration.screens.phonenumber.PhoneNumberEntryState.OneTimeEvent
|
||||
import org.signal.registration.screens.util.navigateTo
|
||||
@@ -58,6 +61,20 @@ class PhoneNumberEntryViewModel(
|
||||
_state.value = state.value.copy(
|
||||
restoredSvrCredentials = repository.getRestoredSvrCredentials()
|
||||
)
|
||||
setDefaultCountry()
|
||||
}
|
||||
}
|
||||
|
||||
fun setDefaultCountry() {
|
||||
val regionCode = repository.getDefaultRegionCode()
|
||||
formatter = phoneNumberUtil.getAsYouTypeFormatter(regionCode)
|
||||
_state.update {
|
||||
it.copy(
|
||||
regionCode = regionCode,
|
||||
countryName = E164Util.getRegionDisplayName(regionCode).orElse(""),
|
||||
countryEmoji = CountryUtils.countryToEmoji(regionCode),
|
||||
countryCode = PhoneNumberUtil.getInstance().getCountryCodeForRegion(regionCode).toString()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,6 +173,8 @@ class PhoneNumberEntryViewModel(
|
||||
val formattedNumber = formatNumber(state.nationalNumber)
|
||||
|
||||
return state.copy(
|
||||
countryName = E164Util.getRegionDisplayName(regionCode).orElse(""),
|
||||
countryEmoji = CountryUtils.countryToEmoji(regionCode).takeIf { regionCode != "ZZ" } ?: "",
|
||||
countryCode = sanitized,
|
||||
regionCode = regionCode,
|
||||
formattedNumber = formattedNumber
|
||||
|
||||
+3
-10
@@ -17,6 +17,7 @@ import assertk.assertions.isTrue
|
||||
import assertk.assertions.prop
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.test.runTest
|
||||
@@ -46,6 +47,8 @@ class PhoneNumberEntryViewModelTest {
|
||||
@Before
|
||||
fun setup() {
|
||||
mockRepository = mockk(relaxed = true)
|
||||
every { mockRepository.getDefaultRegionCode() } returns "US"
|
||||
|
||||
parentState = MutableStateFlow(RegistrationFlowState())
|
||||
emittedStates = mutableListOf()
|
||||
stateEmitter = { state -> emittedStates.add(state) }
|
||||
@@ -54,16 +57,6 @@ class PhoneNumberEntryViewModelTest {
|
||||
viewModel = PhoneNumberEntryViewModel(mockRepository, parentState, parentEventEmitter)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `initial state has default US region and country code`() {
|
||||
val state = PhoneNumberEntryState()
|
||||
|
||||
assertThat(state.regionCode).isEqualTo("US")
|
||||
assertThat(state.countryCode).isEqualTo("1")
|
||||
assertThat(state.nationalNumber).isEqualTo("")
|
||||
assertThat(state.formattedNumber).isEqualTo("")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `PhoneNumberChanged extracts digits and formats number`() = runTest {
|
||||
val initialState = PhoneNumberEntryState()
|
||||
|
||||
+3
-3
@@ -71,7 +71,7 @@ class PhoneNumberScreenTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when Next is clicked, PhoneNumberSubmitted event is emitted`() {
|
||||
fun `when Next is clicked, PhoneNumberEntered event is emitted`() {
|
||||
// Given
|
||||
var emittedEvent: PhoneNumberEntryScreenEvents? = null
|
||||
|
||||
@@ -94,8 +94,8 @@ class PhoneNumberScreenTest {
|
||||
composeTestRule.onNodeWithTag(TestTags.PHONE_NUMBER_NEXT_BUTTON).performClick()
|
||||
|
||||
// Then
|
||||
assert(emittedEvent is PhoneNumberEntryScreenEvents.PhoneNumberSubmitted) {
|
||||
"Expected PhoneNumberSubmitted event but got $emittedEvent"
|
||||
assert(emittedEvent is PhoneNumberEntryScreenEvents.PhoneNumberEntered) {
|
||||
"Expected PhoneNumberEntered event but got $emittedEvent"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user