From 34485d6659f6b4de2bc1ffb001137c0fd80aab15 Mon Sep 17 00:00:00 2001 From: Ademola Fadumo Date: Tue, 14 Oct 2025 23:52:01 +0100 Subject: [PATCH 01/12] feat: added libphonenumber for phone number validation --- auth/build.gradle.kts | 3 + .../validators/PhoneNumberValidator.kt | 67 +++++++++++++++++++ buildSrc/src/main/kotlin/Config.kt | 1 + 3 files changed, 71 insertions(+) create mode 100644 auth/src/main/java/com/firebase/ui/auth/compose/configuration/validators/PhoneNumberValidator.kt diff --git a/auth/build.gradle.kts b/auth/build.gradle.kts index 0fd30ade8..357a846dd 100644 --- a/auth/build.gradle.kts +++ b/auth/build.gradle.kts @@ -106,6 +106,9 @@ dependencies { api(Config.Libs.Firebase.auth) api(Config.Libs.PlayServices.auth) + // Phone number validation + implementation(Config.Libs.Misc.libphonenumber) + compileOnly(Config.Libs.Provider.facebook) implementation(Config.Libs.Androidx.legacySupportv4) // Needed to override deps implementation(Config.Libs.Androidx.cardView) // Needed to override Facebook diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/validators/PhoneNumberValidator.kt b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/validators/PhoneNumberValidator.kt new file mode 100644 index 000000000..692ee0e44 --- /dev/null +++ b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/validators/PhoneNumberValidator.kt @@ -0,0 +1,67 @@ +/* + * Copyright 2025 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.firebase.ui.auth.compose.configuration.validators + +import com.firebase.ui.auth.compose.configuration.string_provider.AuthUIStringProvider +import com.firebase.ui.auth.compose.data.CountryData +import com.google.i18n.phonenumbers.NumberParseException +import com.google.i18n.phonenumbers.PhoneNumberUtil + +internal class PhoneNumberValidator( + override val stringProvider: AuthUIStringProvider, + val selectedCountry: CountryData, +) : + FieldValidator { + private var _validationStatus = FieldValidationStatus(hasError = false, errorMessage = null) + private val phoneNumberUtil = PhoneNumberUtil.getInstance() + + override val hasError: Boolean + get() = _validationStatus.hasError + + override val errorMessage: String + get() = _validationStatus.errorMessage ?: "" + + override fun validate(value: String): Boolean { + if (value.isEmpty()) { + _validationStatus = FieldValidationStatus( + hasError = true, + errorMessage = stringProvider.missingPhoneNumber + ) + return false + } + + try { + val phoneNumber = phoneNumberUtil.parse(value, selectedCountry.countryCode) + val isValid = phoneNumberUtil.isValidNumber(phoneNumber) + + if (!isValid) { + _validationStatus = FieldValidationStatus( + hasError = true, + errorMessage = stringProvider.invalidPhoneNumber + ) + return false + } + } catch (_: NumberParseException) { + _validationStatus = FieldValidationStatus( + hasError = true, + errorMessage = stringProvider.invalidPhoneNumber + ) + return false + } + + _validationStatus = FieldValidationStatus(hasError = false, errorMessage = null) + return true + } +} diff --git a/buildSrc/src/main/kotlin/Config.kt b/buildSrc/src/main/kotlin/Config.kt index 481ee19cd..cad7cd0a8 100644 --- a/buildSrc/src/main/kotlin/Config.kt +++ b/buildSrc/src/main/kotlin/Config.kt @@ -83,6 +83,7 @@ object Config { const val glideCompiler = "com.github.bumptech.glide:compiler:$glideVersion" const val permissions = "pub.devrel:easypermissions:3.0.0" + const val libphonenumber = "com.googlecode.libphonenumber:libphonenumber:9.0.16" } object Test { From c2c4cc6b9b04d38674a8876c4da6ca5059168b98 Mon Sep 17 00:00:00 2001 From: Ademola Fadumo Date: Wed, 15 Oct 2025 00:29:37 +0100 Subject: [PATCH 02/12] feat: PhoneAuthScreen, enter phone number and verification code --- .../string_provider/AuthUIStringProvider.kt | 42 ++ .../DefaultAuthUIStringProvider.kt | 42 ++ .../validators/VerificationCodeValidator.kt | 51 +++ .../ui/auth/compose/data/CountryUtils.kt | 38 ++ .../compose/ui/components/AuthTextField.kt | 4 +- .../compose/ui/components/CountrySelector.kt | 200 +++++++++ .../components/VerificationCodeInputField.kt | 389 ++++++++++++++++++ .../ui/screens/{ => email}/EmailAuthScreen.kt | 2 - .../EmailSignInLinkHandlerActivity.kt | 0 .../ui/screens/{ => email}/ResetPasswordUI.kt | 0 .../ui/screens/{ => email}/SignInUI.kt | 0 .../ui/screens/{ => email}/SignUpUI.kt | 0 .../ui/screens/phone/EnterPhoneNumberUI.kt | 210 ++++++++++ .../screens/phone/EnterVerificationCodeUI.kt | 205 +++++++++ .../ui/screens/phone/PhoneAuthScreen.kt | 327 +++++++++++++++ auth/src/main/res/values-ar/strings.xml | 1 + auth/src/main/res/values-b+es+419/strings.xml | 1 + auth/src/main/res/values-bg/strings.xml | 1 + auth/src/main/res/values-bn/strings.xml | 1 + auth/src/main/res/values-ca/strings.xml | 1 + auth/src/main/res/values-cs/strings.xml | 1 + auth/src/main/res/values-da/strings.xml | 1 + auth/src/main/res/values-de-rAT/strings.xml | 1 + auth/src/main/res/values-de-rCH/strings.xml | 1 + auth/src/main/res/values-de/strings.xml | 1 + auth/src/main/res/values-el/strings.xml | 1 + auth/src/main/res/values-en-rAU/strings.xml | 1 + auth/src/main/res/values-en-rCA/strings.xml | 1 + auth/src/main/res/values-en-rGB/strings.xml | 1 + auth/src/main/res/values-en-rIE/strings.xml | 1 + auth/src/main/res/values-en-rIN/strings.xml | 1 + auth/src/main/res/values-en-rSG/strings.xml | 1 + auth/src/main/res/values-en-rZA/strings.xml | 1 + auth/src/main/res/values-es-rAR/strings.xml | 1 + auth/src/main/res/values-es-rBO/strings.xml | 1 + auth/src/main/res/values-es-rCL/strings.xml | 1 + auth/src/main/res/values-es-rCO/strings.xml | 1 + auth/src/main/res/values-es-rCR/strings.xml | 1 + auth/src/main/res/values-es-rDO/strings.xml | 1 + auth/src/main/res/values-es-rEC/strings.xml | 1 + auth/src/main/res/values-es-rGT/strings.xml | 1 + auth/src/main/res/values-es-rHN/strings.xml | 1 + auth/src/main/res/values-es-rMX/strings.xml | 1 + auth/src/main/res/values-es-rNI/strings.xml | 1 + auth/src/main/res/values-es-rPA/strings.xml | 1 + auth/src/main/res/values-es-rPE/strings.xml | 1 + auth/src/main/res/values-es-rPR/strings.xml | 1 + auth/src/main/res/values-es-rPY/strings.xml | 1 + auth/src/main/res/values-es-rSV/strings.xml | 1 + auth/src/main/res/values-es-rUS/strings.xml | 1 + auth/src/main/res/values-es-rUY/strings.xml | 1 + auth/src/main/res/values-es-rVE/strings.xml | 1 + auth/src/main/res/values-es/strings.xml | 1 + auth/src/main/res/values-fa/strings.xml | 1 + auth/src/main/res/values-fi/strings.xml | 1 + auth/src/main/res/values-fil/strings.xml | 1 + auth/src/main/res/values-fr-rCH/strings.xml | 1 + auth/src/main/res/values-fr/strings.xml | 1 + auth/src/main/res/values-gsw/strings.xml | 1 + auth/src/main/res/values-gu/strings.xml | 1 + auth/src/main/res/values-hi/strings.xml | 1 + auth/src/main/res/values-hr/strings.xml | 1 + auth/src/main/res/values-hu/strings.xml | 1 + auth/src/main/res/values-in/strings.xml | 1 + auth/src/main/res/values-it/strings.xml | 1 + auth/src/main/res/values-iw/strings.xml | 1 + auth/src/main/res/values-ja/strings.xml | 1 + auth/src/main/res/values-kn/strings.xml | 1 + auth/src/main/res/values-ko/strings.xml | 1 + auth/src/main/res/values-ln/strings.xml | 1 + auth/src/main/res/values-lt/strings.xml | 1 + auth/src/main/res/values-lv/strings.xml | 1 + auth/src/main/res/values-mo/strings.xml | 1 + auth/src/main/res/values-mr/strings.xml | 1 + auth/src/main/res/values-ms/strings.xml | 1 + auth/src/main/res/values-nb/strings.xml | 1 + auth/src/main/res/values-nl/strings.xml | 1 + auth/src/main/res/values-no/strings.xml | 1 + auth/src/main/res/values-pl/strings.xml | 1 + auth/src/main/res/values-pt-rBR/strings.xml | 1 + auth/src/main/res/values-pt-rPT/strings.xml | 1 + auth/src/main/res/values-pt/strings.xml | 1 + auth/src/main/res/values-ro/strings.xml | 1 + auth/src/main/res/values-ru/strings.xml | 1 + auth/src/main/res/values-sk/strings.xml | 1 + auth/src/main/res/values-sl/strings.xml | 1 + auth/src/main/res/values-sr/strings.xml | 1 + auth/src/main/res/values-sv/strings.xml | 1 + auth/src/main/res/values-ta/strings.xml | 1 + auth/src/main/res/values-th/strings.xml | 1 + auth/src/main/res/values-tl/strings.xml | 1 + auth/src/main/res/values-tr/strings.xml | 1 + auth/src/main/res/values-uk/strings.xml | 1 + auth/src/main/res/values-ur/strings.xml | 1 + auth/src/main/res/values-vi/strings.xml | 1 + auth/src/main/res/values-zh-rCN/strings.xml | 1 + auth/src/main/res/values-zh-rHK/strings.xml | 1 + auth/src/main/res/values-zh-rTW/strings.xml | 1 + auth/src/main/res/values-zh/strings.xml | 1 + auth/src/main/res/values/strings.xml | 3 +- .../com/firebase/composeapp/MainActivity.kt | 44 +- .../{MainScreen.kt => EmailAuthMain.kt} | 6 +- .../composeapp/ui/screens/PhoneAuthMain.kt | 131 ++++++ 103 files changed, 1766 insertions(+), 12 deletions(-) create mode 100644 auth/src/main/java/com/firebase/ui/auth/compose/configuration/validators/VerificationCodeValidator.kt create mode 100644 auth/src/main/java/com/firebase/ui/auth/compose/ui/components/CountrySelector.kt create mode 100644 auth/src/main/java/com/firebase/ui/auth/compose/ui/components/VerificationCodeInputField.kt rename auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/{ => email}/EmailAuthScreen.kt (98%) rename auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/{ => email}/EmailSignInLinkHandlerActivity.kt (100%) rename auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/{ => email}/ResetPasswordUI.kt (100%) rename auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/{ => email}/SignInUI.kt (100%) rename auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/{ => email}/SignUpUI.kt (100%) create mode 100644 auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/phone/EnterPhoneNumberUI.kt create mode 100644 auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/phone/EnterVerificationCodeUI.kt create mode 100644 auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/phone/PhoneAuthScreen.kt rename composeapp/src/main/java/com/firebase/composeapp/ui/screens/{MainScreen.kt => EmailAuthMain.kt} (97%) create mode 100644 composeapp/src/main/java/com/firebase/composeapp/ui/screens/PhoneAuthMain.kt diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/string_provider/AuthUIStringProvider.kt b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/string_provider/AuthUIStringProvider.kt index 30cc33257..c2fe71fe5 100644 --- a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/string_provider/AuthUIStringProvider.kt +++ b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/string_provider/AuthUIStringProvider.kt @@ -159,6 +159,9 @@ interface AuthUIStringProvider { /** Invalid phone number error */ val invalidPhoneNumber: String + /** Missing phone number error */ + val missingPhoneNumber: String + /** Phone verification code entry form title */ val enterConfirmationCode: String @@ -171,6 +174,9 @@ interface AuthUIStringProvider { /** Resend code link text */ val resendCode: String + /** Resend code with timer */ + fun resendCodeTimer(seconds: Int): String + /** Verifying progress text */ val verifying: String @@ -180,6 +186,42 @@ interface AuthUIStringProvider { /** SMS terms of service warning */ val smsTermsOfService: String + /** Enter phone number title */ + val enterPhoneNumberTitle: String + + /** Phone number hint */ + val phoneNumberHint: String + + /** Send verification code button text */ + val sendVerificationCode: String + + /** Enter verification code title with phone number */ + fun enterVerificationCodeTitle(phoneNumber: String): String + + /** Verification code hint */ + val verificationCodeHint: String + + /** Verify code button text */ + val verifyCode: String + + /** Change phone number link text */ + val changePhoneNumber: String + + /** Missing verification code error */ + val missingVerificationCode: String + + /** Invalid verification code error */ + val invalidVerificationCode: String + + /** Country code label */ + val countryCode: String + + /** Select country dialog title */ + val selectCountry: String + + /** Search countries hint */ + val searchCountries: String + // Provider Picker Strings /** Common button text for sign in */ val signInDefault: String diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/string_provider/DefaultAuthUIStringProvider.kt b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/string_provider/DefaultAuthUIStringProvider.kt index 5016d7482..f6f00d1de 100644 --- a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/string_provider/DefaultAuthUIStringProvider.kt +++ b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/string_provider/DefaultAuthUIStringProvider.kt @@ -159,6 +159,8 @@ class DefaultAuthUIStringProvider( get() = localizedContext.getString(R.string.fui_country_hint) override val invalidPhoneNumber: String get() = localizedContext.getString(R.string.fui_invalid_phone_number) + override val missingPhoneNumber: String + get() = localizedContext.getString(R.string.fui_required_field) override val enterConfirmationCode: String get() = localizedContext.getString(R.string.fui_enter_confirmation_code) override val verifyPhoneNumber: String @@ -167,6 +169,10 @@ class DefaultAuthUIStringProvider( get() = localizedContext.getString(R.string.fui_resend_code_in) override val resendCode: String get() = localizedContext.getString(R.string.fui_resend_code) + + override fun resendCodeTimer(seconds: Int): String = + localizedContext.getString(R.string.fui_resend_code_in, seconds) + override val verifying: String get() = localizedContext.getString(R.string.fui_verifying) override val incorrectCodeDialogBody: String @@ -174,6 +180,42 @@ class DefaultAuthUIStringProvider( override val smsTermsOfService: String get() = localizedContext.getString(R.string.fui_sms_terms_of_service) + override val enterPhoneNumberTitle: String + get() = localizedContext.getString(R.string.fui_verify_phone_number_title) + + override val phoneNumberHint: String + get() = localizedContext.getString(R.string.fui_phone_hint) + + override val sendVerificationCode: String + get() = localizedContext.getString(R.string.fui_next_default) + + override fun enterVerificationCodeTitle(phoneNumber: String): String = + localizedContext.getString(R.string.fui_enter_confirmation_code) + " " + phoneNumber + + override val verificationCodeHint: String + get() = localizedContext.getString(R.string.fui_enter_confirmation_code) + + override val verifyCode: String + get() = localizedContext.getString(R.string.fui_verify_phone_number) + + override val changePhoneNumber: String + get() = localizedContext.getString(R.string.fui_change_phone_number) + + override val missingVerificationCode: String + get() = localizedContext.getString(R.string.fui_required_field) + + override val invalidVerificationCode: String + get() = localizedContext.getString(R.string.fui_incorrect_code_dialog_body) + + override val countryCode: String + get() = localizedContext.getString(R.string.fui_country_hint) + + override val selectCountry: String + get() = localizedContext.getString(R.string.fui_country_hint) + + override val searchCountries: String + get() = localizedContext.getString(R.string.fui_country_hint) + /** * Multi-Factor Authentication Strings */ diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/validators/VerificationCodeValidator.kt b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/validators/VerificationCodeValidator.kt new file mode 100644 index 000000000..e72f100ca --- /dev/null +++ b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/validators/VerificationCodeValidator.kt @@ -0,0 +1,51 @@ +/* + * Copyright 2025 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.firebase.ui.auth.compose.configuration.validators + +import com.firebase.ui.auth.compose.configuration.string_provider.AuthUIStringProvider + +internal class VerificationCodeValidator(override val stringProvider: AuthUIStringProvider) : + FieldValidator { + private var _validationStatus = FieldValidationStatus(hasError = false, errorMessage = null) + + override val hasError: Boolean + get() = _validationStatus.hasError + + override val errorMessage: String + get() = _validationStatus.errorMessage ?: "" + + override fun validate(value: String): Boolean { + if (value.isEmpty()) { + _validationStatus = FieldValidationStatus( + hasError = true, + errorMessage = stringProvider.missingVerificationCode + ) + return false + } + + // Verification codes are typically 6 digits + val digitsOnly = value.replace(Regex("[^0-9]"), "") + if (digitsOnly.length != 6) { + _validationStatus = FieldValidationStatus( + hasError = true, + errorMessage = stringProvider.invalidVerificationCode + ) + return false + } + + _validationStatus = FieldValidationStatus(hasError = false, errorMessage = null) + return true + } +} diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/data/CountryUtils.kt b/auth/src/main/java/com/firebase/ui/auth/compose/data/CountryUtils.kt index e6ef16f06..e71270a33 100644 --- a/auth/src/main/java/com/firebase/ui/auth/compose/data/CountryUtils.kt +++ b/auth/src/main/java/com/firebase/ui/auth/compose/data/CountryUtils.kt @@ -68,6 +68,44 @@ object CountryUtils { } } + /** + * Searches for countries by name, country code, or dial code. + * Supports partial matching and diacritic-insensitive search. + * + * @param query The search query (country name, country code, or dial code). + * @return List of countries matching the query, sorted by relevance. + */ + fun search(query: String): List { + val trimmedQuery = query.trim() + if (trimmedQuery.isEmpty()) return emptyList() + + val normalizedQuery = normalizeString(trimmedQuery) + val uppercaseQuery = trimmedQuery.uppercase() + + return ALL_COUNTRIES.filter { country -> + // Match by country name (partial, case-insensitive, diacritic-insensitive) + normalizeString(country.name).contains(normalizedQuery, ignoreCase = true) || + // Match by country code (partial, case-insensitive) + country.countryCode.uppercase().contains(uppercaseQuery) || + // Match by dial code (partial) + country.dialCode.contains(trimmedQuery) + }.sortedWith( + compareBy( + // Prioritize exact matches first + { country -> + when { + country.countryCode.uppercase() == uppercaseQuery -> 0 + country.dialCode == trimmedQuery -> 1 + normalizeString(country.name) == normalizedQuery -> 2 + else -> 3 + } + }, + // Then sort alphabetically by name + { country -> country.name } + ) + ) + } + /** * Filters countries by allowed country codes. * diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/ui/components/AuthTextField.kt b/auth/src/main/java/com/firebase/ui/auth/compose/ui/components/AuthTextField.kt index 7e0e911d0..7db788397 100644 --- a/auth/src/main/java/com/firebase/ui/auth/compose/ui/components/AuthTextField.kt +++ b/auth/src/main/java/com/firebase/ui/auth/compose/ui/components/AuthTextField.kt @@ -130,7 +130,7 @@ fun AuthTextField( { Icon( imageVector = Icons.Default.Email, - contentDescription = "" + contentDescription = "Email Input Icon" ) } } @@ -139,7 +139,7 @@ fun AuthTextField( { Icon( imageVector = Icons.Default.Lock, - contentDescription = "" + contentDescription = "Password Input Icon" ) } } diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/ui/components/CountrySelector.kt b/auth/src/main/java/com/firebase/ui/auth/compose/ui/components/CountrySelector.kt new file mode 100644 index 000000000..cafabf02f --- /dev/null +++ b/auth/src/main/java/com/firebase/ui/auth/compose/ui/components/CountrySelector.kt @@ -0,0 +1,200 @@ +/* + * Copyright 2025 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.firebase.ui.auth.compose.ui.components + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowDropDown +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import com.firebase.ui.auth.compose.configuration.string_provider.DefaultAuthUIStringProvider +import com.firebase.ui.auth.compose.data.ALL_COUNTRIES +import com.firebase.ui.auth.compose.data.CountryData +import com.firebase.ui.auth.compose.data.CountryUtils +import kotlinx.coroutines.launch + +/** + * A country selector component that displays the selected country's flag and dial code with a dropdown icon. + * Designed to be used as a leadingIcon in a TextField. + * + * @param selectedCountry The currently selected country. + * @param onCountrySelected Callback when a country is selected. + * @param enabled Whether the selector is enabled. + * @param allowedCountries Optional set of allowed country codes to filter the list. + */ +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun CountrySelector( + selectedCountry: CountryData, + onCountrySelected: (CountryData) -> Unit, + enabled: Boolean = true, + allowedCountries: Set? = null +) { + val context = LocalContext.current + val stringProvider = DefaultAuthUIStringProvider(context) + val sheetState = rememberModalBottomSheetState() + val scope = rememberCoroutineScope() + var showBottomSheet by remember { mutableStateOf(false) } + var searchQuery by remember { mutableStateOf("") } + + val countriesList = remember(allowedCountries) { + if (allowedCountries != null) { + CountryUtils.filterByAllowedCountries(allowedCountries) + } else { + ALL_COUNTRIES + } + } + + val filteredCountries = remember(searchQuery, countriesList) { + if (searchQuery.isEmpty()) { + countriesList + } else { + CountryUtils.search(searchQuery).filter { country -> + countriesList.any { it.countryCode == country.countryCode } + } + } + } + + // Clickable row showing flag, dial code and dropdown icon + Row( + modifier = Modifier + .fillMaxHeight() + .clickable(enabled = enabled) { + showBottomSheet = true + } + .padding(start = 8.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(4.dp) + ) { + Text( + text = selectedCountry.flagEmoji, + style = MaterialTheme.typography.bodyLarge + ) + Text( + text = selectedCountry.dialCode, + style = MaterialTheme.typography.bodyLarge + ) + Icon( + imageVector = Icons.Default.ArrowDropDown, + contentDescription = "Select country", + modifier = Modifier.padding(PaddingValues.Zero) + ) + } + + if (showBottomSheet) { + ModalBottomSheet( + onDismissRequest = { + showBottomSheet = false + searchQuery = "" + }, + sheetState = sheetState + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp) + .padding(bottom = 16.dp) + ) { + Text( + text = stringProvider.selectCountry, + style = MaterialTheme.typography.headlineSmall, + modifier = Modifier.padding(bottom = 16.dp) + ) + + OutlinedTextField( + value = searchQuery, + onValueChange = { searchQuery = it }, + label = { Text(stringProvider.searchCountries) }, + modifier = Modifier.fillMaxWidth() + ) + + Spacer(modifier = Modifier.height(16.dp)) + + LazyColumn( + modifier = Modifier + .fillMaxWidth() + .height(500.dp) + ) { + items(filteredCountries) { country -> + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { + onCountrySelected(country) + scope.launch { + sheetState.hide() + }.invokeOnCompletion { + if (!sheetState.isVisible) { + showBottomSheet = false + searchQuery = "" + } + } + } + .padding(vertical = 12.dp, horizontal = 8.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = country.flagEmoji, + style = MaterialTheme.typography.headlineMedium + ) + Spacer(modifier = Modifier.width(12.dp)) + Text( + text = country.name, + style = MaterialTheme.typography.bodyLarge + ) + } + Text( + text = country.dialCode, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + } + } + } + } + } +} diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/ui/components/VerificationCodeInputField.kt b/auth/src/main/java/com/firebase/ui/auth/compose/ui/components/VerificationCodeInputField.kt new file mode 100644 index 000000000..858383b77 --- /dev/null +++ b/auth/src/main/java/com/firebase/ui/auth/compose/ui/components/VerificationCodeInputField.kt @@ -0,0 +1,389 @@ +package com.firebase.ui.auth.compose.ui.components + +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.animation.core.tween +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.consumeWindowInsets +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.ui.draw.clip +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Remove +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +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.focus.onFocusChanged +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.input.key.Key +import androidx.compose.ui.input.key.KeyEventType +import androidx.compose.ui.input.key.key +import androidx.compose.ui.input.key.onPreviewKeyEvent +import androidx.compose.ui.input.key.type +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.text.TextRange +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.core.text.isDigitsOnly +import com.firebase.ui.auth.compose.configuration.theme.AuthUITheme +import com.firebase.ui.auth.compose.configuration.validators.FieldValidator + +@Composable +fun VerificationCodeInputField( + modifier: Modifier = Modifier, + codeLength: Int = 6, + validator: FieldValidator? = null, + isError: Boolean = false, + errorMessage: String? = null, + onCodeComplete: (String) -> Unit = {}, + onCodeChange: (String) -> Unit = {}, +) { + val code = remember { mutableStateOf(List(codeLength) { null }) } + val focusedIndex = remember { mutableStateOf(null) } + val focusRequesters = remember { (1..codeLength).map { FocusRequester() } } + val keyboardManager = LocalSoftwareKeyboardController.current + + // Derive validation state + val currentCodeString = remember { mutableStateOf("") } + val validationError = remember { mutableStateOf(null) } + + // Auto-focus first field on initial composition + LaunchedEffect(Unit) { + focusRequesters.firstOrNull()?.requestFocus() + } + + // Handle focus changes + LaunchedEffect(focusedIndex.value) { + focusedIndex.value?.let { index -> + focusRequesters.getOrNull(index)?.requestFocus() + } + } + + // Handle code completion and validation + LaunchedEffect(code.value) { + val codeString = code.value.mapNotNull { it }.joinToString("") + currentCodeString.value = codeString + onCodeChange(codeString) + + // Run validation if validator is provided + validator?.let { + val isValid = it.validate(codeString) + validationError.value = if (!isValid && codeString.length == codeLength) { + it.errorMessage + } else { + null + } + } + + val allNumbersEntered = code.value.none { it == null } + if (allNumbersEntered) { + keyboardManager?.hide() + onCodeComplete(codeString) + } + } + + // Determine error state: use validator if provided, otherwise use explicit isError + val showError = if (validator != null) { + validationError.value != null + } else { + isError + } + + val displayErrorMessage = if (validator != null) { + validationError.value + } else { + errorMessage + } + + Column( + modifier = modifier, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally) + ) { + code.value.forEachIndexed { index, number -> + SingleDigitField( + modifier = Modifier + .weight(1f) + .aspectRatio(1f), + number = number, + isError = showError, + focusRequester = focusRequesters[index], + onFocusChanged = { isFocused -> + if (isFocused) { + focusedIndex.value = index + } + }, + onNumberChanged = { value -> + val oldValue = code.value[index] + val newCode = code.value.toMutableList() + newCode[index] = value + code.value = newCode + + // Move focus to next field if number was entered (and field was previously empty) + if (value != null && oldValue == null) { + focusedIndex.value = getNextFocusedIndex(newCode, index) + } + }, + onKeyboardBack = { + val previousIndex = getPreviousFocusedIndex(index) + if (previousIndex != null) { + val newCode = code.value.toMutableList() + newCode[previousIndex] = null + code.value = newCode + focusedIndex.value = previousIndex + } + }, + onNumberEntered = { + focusRequesters[index].freeFocus() + } + ) + } + } + + if (showError && displayErrorMessage != null) { + Text( + modifier = Modifier.padding(top = 8.dp), + text = displayErrorMessage, + color = MaterialTheme.colorScheme.error, + style = MaterialTheme.typography.bodySmall, + ) + } + } +} + +@Composable +private fun SingleDigitField( + modifier: Modifier = Modifier, + number: Int?, + isError: Boolean = false, + focusRequester: FocusRequester, + onFocusChanged: (Boolean) -> Unit, + onNumberChanged: (Int?) -> Unit, + onKeyboardBack: () -> Unit, + onNumberEntered: () -> Unit, +) { + val text = remember { mutableStateOf(TextFieldValue()) } + val isFocused = remember { mutableStateOf(false) } + + // Update text field value when number changes externally + LaunchedEffect(number) { + text.value = TextFieldValue( + text = number?.toString().orEmpty(), + selection = TextRange( + index = if (number != null) 1 else 0 + ) + ) + } + + val borderColor = if (isError) { + MaterialTheme.colorScheme.error + } else { + MaterialTheme.colorScheme.primary + } + +// val backgroundColor = if (isError) { +// MaterialTheme.colorScheme.errorContainer +// } else { +// MaterialTheme.colorScheme.primaryContainer +// } + + val textColor = if (isError) { + MaterialTheme.colorScheme.onErrorContainer + } else { + MaterialTheme.colorScheme.primary + } + + val targetBorderWidth = if (isError || isFocused.value || number != null) 2.dp else 1.dp + val animatedBorderWidth by animateDpAsState( + targetValue = targetBorderWidth, + animationSpec = tween(durationMillis = 150), + label = "borderWidth" + ) + + val shape = RoundedCornerShape(8.dp) + + Box( + modifier = modifier + .clip(shape) + .border( + width = animatedBorderWidth, + shape = shape, + color = borderColor, + ), + //.background(backgroundColor), + contentAlignment = Alignment.Center + ) { + BasicTextField( + modifier = Modifier + .fillMaxSize() + .wrapContentSize() + .focusRequester(focusRequester) + .onFocusChanged { + isFocused.value = it.isFocused + onFocusChanged(it.isFocused) + } + .onPreviewKeyEvent { event -> + val isDelete = event.key == Key.Backspace || event.key == Key.Delete + val isInitialDown = event.type == KeyEventType.KeyDown && + event.nativeKeyEvent.repeatCount == 0 + + if (isDelete && isInitialDown && number == null) { + onKeyboardBack() + return@onPreviewKeyEvent true + } + false + }, + value = text.value, + onValueChange = { value -> + val newNumber = value.text + if (newNumber.length <= 1 && newNumber.isDigitsOnly()) { + val digit = newNumber.toIntOrNull() + onNumberChanged(digit) + if (digit != null) { + onNumberEntered() + } + } + }, + cursorBrush = SolidColor(textColor), + singleLine = true, + textStyle = MaterialTheme.typography.bodyMedium.copy( + textAlign = TextAlign.Center, + fontWeight = FontWeight.Normal, + fontSize = 24.sp, + color = textColor, + lineHeight = 24.sp, + ), + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.NumberPassword + ), + decorationBox = { innerTextField -> + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + innerTextField() + } + } + ) + } +} + +private fun getPreviousFocusedIndex(currentIndex: Int): Int? { + return currentIndex.minus(1).takeIf { it >= 0 } +} + +private fun getNextFocusedIndex(code: List, currentIndex: Int): Int? { + if (currentIndex >= code.size - 1) return currentIndex + + for (i in (currentIndex + 1) until code.size) { + if (code[i] == null) { + return i + } + } + return currentIndex +} + +@Preview +@Composable +private fun PreviewVerificationCodeInputFieldExample() { + val completedCode = remember { mutableStateOf(null) } + val currentCode = remember { mutableStateOf("") } + val isError = remember { mutableStateOf(false) } + + AuthUITheme { + Scaffold( + containerColor = MaterialTheme.colorScheme.primaryContainer + ) { innerPadding -> + Column( + modifier = Modifier + .padding(innerPadding) + .consumeWindowInsets(innerPadding) + .fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + VerificationCodeInputField( + modifier = Modifier.padding(16.dp), + isError = isError.value, + errorMessage = if (isError.value) "Invalid verification code" else null, + onCodeComplete = { code -> + completedCode.value = code + // Simulate validation - in real app this would be async + isError.value = code != "123456" + }, + onCodeChange = { code -> + currentCode.value = code + // Clear error on change + if (isError.value) { + isError.value = false + } + } + ) + + if (!isError.value) { + completedCode.value?.let { code -> + Text( + modifier = Modifier.padding(top = 16.dp), + text = "Code entered: $code", + color = MaterialTheme.colorScheme.primary, + fontSize = 16.sp, + ) + } + } + } + } + } +} + +@Preview(showBackground = true) +@Composable +private fun PreviewVerificationCodeInputFieldError() { + AuthUITheme { + VerificationCodeInputField( + modifier = Modifier.padding(16.dp), + isError = true, + errorMessage = "Invalid verification code" + ) + } +} + +@Preview(showBackground = true) +@Composable +private fun PreviewVerificationCodeInputField() { + AuthUITheme { + VerificationCodeInputField( + modifier = Modifier.padding(16.dp) + ) + } +} \ No newline at end of file diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/EmailAuthScreen.kt b/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/email/EmailAuthScreen.kt similarity index 98% rename from auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/EmailAuthScreen.kt rename to auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/email/EmailAuthScreen.kt index 4fbebd2bf..ddfcc6a29 100644 --- a/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/EmailAuthScreen.kt +++ b/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/email/EmailAuthScreen.kt @@ -33,10 +33,8 @@ import com.firebase.ui.auth.compose.configuration.auth_provider.createOrLinkUser import com.firebase.ui.auth.compose.configuration.auth_provider.sendPasswordResetEmail import com.firebase.ui.auth.compose.configuration.auth_provider.sendSignInLinkToEmail import com.firebase.ui.auth.compose.configuration.auth_provider.signInWithEmailAndPassword -import com.firebase.ui.auth.compose.configuration.auth_provider.signInWithEmailLink import com.firebase.ui.auth.compose.configuration.string_provider.DefaultAuthUIStringProvider import com.firebase.ui.auth.compose.ui.components.ErrorRecoveryDialog -import com.firebase.ui.auth.compose.util.EmailLinkPersistenceManager import com.google.firebase.auth.AuthResult import kotlinx.coroutines.launch diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/EmailSignInLinkHandlerActivity.kt b/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/email/EmailSignInLinkHandlerActivity.kt similarity index 100% rename from auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/EmailSignInLinkHandlerActivity.kt rename to auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/email/EmailSignInLinkHandlerActivity.kt diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/ResetPasswordUI.kt b/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/email/ResetPasswordUI.kt similarity index 100% rename from auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/ResetPasswordUI.kt rename to auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/email/ResetPasswordUI.kt diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/SignInUI.kt b/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/email/SignInUI.kt similarity index 100% rename from auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/SignInUI.kt rename to auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/email/SignInUI.kt diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/SignUpUI.kt b/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/email/SignUpUI.kt similarity index 100% rename from auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/SignUpUI.kt rename to auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/email/SignUpUI.kt diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/phone/EnterPhoneNumberUI.kt b/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/phone/EnterPhoneNumberUI.kt new file mode 100644 index 000000000..f5d886483 --- /dev/null +++ b/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/phone/EnterPhoneNumberUI.kt @@ -0,0 +1,210 @@ +/* + * Copyright 2025 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.firebase.ui.auth.compose.ui.screens.phone + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.safeDrawingPadding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.CheckBox +import androidx.compose.material.icons.filled.CheckBoxOutlineBlank +import androidx.compose.material3.Button +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 +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.firebase.ui.auth.compose.configuration.AuthUIConfiguration +import com.firebase.ui.auth.compose.configuration.authUIConfiguration +import com.firebase.ui.auth.compose.configuration.auth_provider.AuthProvider +import com.firebase.ui.auth.compose.configuration.string_provider.DefaultAuthUIStringProvider +import com.firebase.ui.auth.compose.configuration.theme.AuthUITheme +import com.firebase.ui.auth.compose.configuration.validators.PhoneNumberValidator +import com.firebase.ui.auth.compose.data.CountryData +import com.firebase.ui.auth.compose.data.CountryUtils +import com.firebase.ui.auth.compose.ui.components.AuthTextField +import com.firebase.ui.auth.compose.ui.components.CountrySelector +import com.firebase.ui.auth.compose.ui.components.TermsAndPrivacyForm + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun EnterPhoneNumberUI( + modifier: Modifier = Modifier, + configuration: AuthUIConfiguration, + isLoading: Boolean, + phoneNumber: String, + useInstantVerificationEnabled: Boolean, + onUseInstantVerificationChange: (Boolean) -> Unit, + selectedCountry: CountryData, + onPhoneNumberChange: (String) -> Unit, + onCountrySelected: (CountryData) -> Unit, + onSendCodeClick: () -> Unit, +) { + val context = LocalContext.current + val provider = configuration.providers.filterIsInstance().first() + val stringProvider = DefaultAuthUIStringProvider(context) + val phoneNumberValidator = remember(selectedCountry) { + PhoneNumberValidator(stringProvider, selectedCountry) + } + + val isFormValid = remember(selectedCountry, phoneNumber) { + derivedStateOf { + phoneNumberValidator.validate(phoneNumber) + } + } + + Scaffold( + modifier = modifier, + topBar = { + TopAppBar( + title = { + Text(stringProvider.verifyPhoneNumber) + }, + colors = AuthUITheme.topAppBarColors + ) + }, + ) { innerPadding -> + Column( + modifier = Modifier + .padding(innerPadding) + .safeDrawingPadding() + .padding(horizontal = 16.dp) + .verticalScroll(rememberScrollState()), + ) { + Text(stringProvider.enterPhoneNumberTitle) + Spacer(modifier = Modifier.height(16.dp)) + AuthTextField( + value = phoneNumber, + validator = phoneNumberValidator, + enabled = !isLoading, + label = { + Text(stringProvider.phoneNumberHint) + }, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Phone + ), + leadingIcon = { + CountrySelector( + selectedCountry = selectedCountry, + onCountrySelected = onCountrySelected, + enabled = !isLoading, + allowedCountries = provider.allowedCountries?.toSet() + ) + }, + onValueChange = { + onPhoneNumberChange(it) + } + ) + Spacer(modifier = Modifier.height(16.dp)) + if (provider.isInstantVerificationEnabled) { + Row( + modifier = Modifier + .align(Alignment.End) + .clickable { + onUseInstantVerificationChange(!useInstantVerificationEnabled) + }, + verticalAlignment = Alignment.CenterVertically, + ) { + Text("Use instant verification for SMS") + Spacer(modifier = Modifier.width(8.dp)) + Icon( + imageVector = if (useInstantVerificationEnabled) Icons.Default.CheckBox else + Icons.Default.CheckBoxOutlineBlank, + contentDescription = "", + tint = MaterialTheme.colorScheme.primary, + ) + } + Spacer(modifier = Modifier.height(16.dp)) + } + + Row( + modifier = Modifier + .align(Alignment.End), + ) { + Button( + onClick = onSendCodeClick, + enabled = !isLoading && isFormValid.value, + ) { + if (isLoading) { + CircularProgressIndicator( + modifier = Modifier + .size(16.dp) + ) + } else { + Text(stringProvider.sendVerificationCode.uppercase()) + } + } + } + Spacer(modifier = Modifier.height(16.dp)) + TermsAndPrivacyForm( + modifier = Modifier.align(Alignment.End), + tosUrl = configuration.tosUrl, + ppUrl = configuration.privacyPolicyUrl, + ) + } + } +} + +@Preview +@Composable +fun PreviewEnterPhoneNumberUI() { + val applicationContext = LocalContext.current + val provider = AuthProvider.Phone( + defaultNumber = null, + defaultCountryCode = null, + allowedCountries = null, + timeout = 60L, + isInstantVerificationEnabled = true + ) + + AuthUITheme { + EnterPhoneNumberUI( + configuration = authUIConfiguration { + context = applicationContext + providers { provider(provider) } + tosUrl = "" + privacyPolicyUrl = "" + }, + isLoading = false, + phoneNumber = "", + useInstantVerificationEnabled = true, + onUseInstantVerificationChange = { value -> }, + selectedCountry = CountryUtils.getDefaultCountry(), + onPhoneNumberChange = {}, + onCountrySelected = {}, + onSendCodeClick = {}, + ) + } +} diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/phone/EnterVerificationCodeUI.kt b/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/phone/EnterVerificationCodeUI.kt new file mode 100644 index 000000000..62d2e136d --- /dev/null +++ b/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/phone/EnterVerificationCodeUI.kt @@ -0,0 +1,205 @@ +/* + * Copyright 2025 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.firebase.ui.auth.compose.ui.screens.phone + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.safeDrawingPadding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Button +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.firebase.ui.auth.compose.configuration.AuthUIConfiguration +import com.firebase.ui.auth.compose.configuration.authUIConfiguration +import com.firebase.ui.auth.compose.configuration.auth_provider.AuthProvider +import com.firebase.ui.auth.compose.configuration.string_provider.DefaultAuthUIStringProvider +import com.firebase.ui.auth.compose.configuration.theme.AuthUITheme +import com.firebase.ui.auth.compose.configuration.validators.VerificationCodeValidator +import com.firebase.ui.auth.compose.ui.components.TermsAndPrivacyForm +import com.firebase.ui.auth.compose.ui.components.VerificationCodeInputField + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun EnterVerificationCodeUI( + modifier: Modifier = Modifier, + configuration: AuthUIConfiguration, + isLoading: Boolean, + verificationCode: String, + fullPhoneNumber: String, + resendTimer: Int, + onVerificationCodeChange: (String) -> Unit, + onVerifyCodeClick: () -> Unit, + onResendCodeClick: () -> Unit, + onChangeNumberClick: () -> Unit, +) { + val context = LocalContext.current + val stringProvider = DefaultAuthUIStringProvider(context) + val verificationCodeValidator = remember { + VerificationCodeValidator(stringProvider) + } + + val isFormValid = remember(verificationCode) { + derivedStateOf { + verificationCodeValidator.validate(verificationCode) + } + } + + val resendEnabled = resendTimer == 0 && !isLoading + + Scaffold( + modifier = modifier, + topBar = { + TopAppBar( + title = { + Text(stringProvider.verifyPhoneNumber) + }, + colors = AuthUITheme.topAppBarColors + ) + }, + ) { innerPadding -> + Column( + modifier = Modifier + .padding(innerPadding) + .safeDrawingPadding() + .padding(horizontal = 16.dp) + .verticalScroll(rememberScrollState()), + ) { + Text( + text = stringProvider.enterVerificationCodeTitle(fullPhoneNumber), + style = MaterialTheme.typography.bodyLarge, + ) + Spacer(modifier = Modifier.height(8.dp)) + + TextButton( + modifier = Modifier.align(Alignment.Start), + onClick = onChangeNumberClick, + enabled = !isLoading, + contentPadding = PaddingValues.Zero + ) { + Text( + text = stringProvider.changePhoneNumber, + style = MaterialTheme.typography.bodyMedium, + textAlign = TextAlign.Center, + textDecoration = TextDecoration.Underline + ) + } + Spacer(modifier = Modifier.height(16.dp)) + + VerificationCodeInputField( + modifier = Modifier.align(Alignment.CenterHorizontally), + validator = verificationCodeValidator, + onCodeChange = onVerificationCodeChange + ) + Spacer(modifier = Modifier.height(8.dp)) + + TextButton( + modifier = Modifier.align(Alignment.Start), + onClick = onResendCodeClick, + enabled = resendEnabled, + contentPadding = PaddingValues.Zero + ) { + Text( + text = if (resendTimer > 0) { + stringProvider.resendCodeTimer(resendTimer) + } else { + stringProvider.resendCode + }, + style = MaterialTheme.typography.bodyMedium, + textAlign = TextAlign.Center, + textDecoration = if (resendEnabled) TextDecoration.Underline else TextDecoration.None + ) + } + Spacer(modifier = Modifier.height(16.dp)) + + Row( + modifier = Modifier + .align(Alignment.End), + ) { + Button( + onClick = onVerifyCodeClick, + enabled = !isLoading && isFormValid.value, + ) { + if (isLoading) { + CircularProgressIndicator( + modifier = Modifier + .size(16.dp) + ) + } else { + Text(stringProvider.verifyCode.uppercase()) + } + } + } + Spacer(modifier = Modifier.height(16.dp)) + TermsAndPrivacyForm( + modifier = Modifier.align(Alignment.End), + tosUrl = configuration.tosUrl, + ppUrl = configuration.privacyPolicyUrl, + ) + } + } +} + +@Preview +@Composable +fun PreviewEnterVerificationCodeUI() { + val applicationContext = LocalContext.current + val provider = AuthProvider.Phone( + defaultNumber = null, + defaultCountryCode = null, + allowedCountries = null, + timeout = 60L, + isInstantVerificationEnabled = true + ) + + AuthUITheme { + EnterVerificationCodeUI( + configuration = authUIConfiguration { + context = applicationContext + providers { provider(provider) } + tosUrl = "" + privacyPolicyUrl = "" + }, + isLoading = false, + verificationCode = "", + fullPhoneNumber = "+1234567890", + resendTimer = 30, + onVerificationCodeChange = {}, + onVerifyCodeClick = {}, + onResendCodeClick = {}, + onChangeNumberClick = {}, + ) + } +} diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/phone/PhoneAuthScreen.kt b/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/phone/PhoneAuthScreen.kt new file mode 100644 index 000000000..327e19004 --- /dev/null +++ b/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/phone/PhoneAuthScreen.kt @@ -0,0 +1,327 @@ +/* + * Copyright 2025 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.firebase.ui.auth.compose.ui.screens.phone + +import android.content.Context +import android.util.Log +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.Modifier +import com.firebase.ui.auth.compose.AuthException +import com.firebase.ui.auth.compose.AuthState +import com.firebase.ui.auth.compose.FirebaseAuthUI +import com.firebase.ui.auth.compose.configuration.AuthUIConfiguration +import com.firebase.ui.auth.compose.configuration.auth_provider.AuthProvider +import com.firebase.ui.auth.compose.configuration.auth_provider.signInWithPhoneAuthCredential +import com.firebase.ui.auth.compose.configuration.auth_provider.submitVerificationCode +import com.firebase.ui.auth.compose.configuration.auth_provider.verifyPhoneNumber +import com.firebase.ui.auth.compose.configuration.string_provider.DefaultAuthUIStringProvider +import com.firebase.ui.auth.compose.data.CountryData +import com.firebase.ui.auth.compose.data.CountryUtils +import com.firebase.ui.auth.compose.ui.components.ErrorRecoveryDialog +import com.google.firebase.auth.AuthResult +import com.google.firebase.auth.PhoneAuthProvider +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +enum class PhoneAuthStep { + /** + * An enum representing a view requiring a phone number which needs to be entered. + */ + EnterPhoneNumber, + + /** + * An enum representing a view requiring a phone number verification code which needs to + * be entered. + */ + EnterVerificationCode +} + +/** + * A class passed to the content slot, containing all the necessary information to render a custom + * UI for every step of the phone authentication process. + * + * @param step An enum representing the current step in the flow. Use a when expression on this + * to render the correct UI. + * @param isLoading true when an asynchronous operation (like sending or verifying a code) is in + * progress. + * @param error An optional error message to display to the user. + * @param phoneNumber (Step: [PhoneAuthStep.EnterPhoneNumber]) The current value of the phone + * number input field. + * @param useInstantVerificationEnabled + * @param onUseInstantVerificationChange + * @param onPhoneNumberChange (Step: [PhoneAuthStep.EnterPhoneNumber]) A callback to be invoked + * when the phone number input changes. + * @param selectedCountry (Step: [PhoneAuthStep.EnterPhoneNumber]) The currently selected country + * object, containing its name, dial code, and flag. + * @param onCountrySelected (Step: [PhoneAuthStep.EnterPhoneNumber]) A callback to be invoked when + * the user selects a new country. + * @param onSendCodeClick (Step: [PhoneAuthStep.EnterPhoneNumber]) A callback to be invoked to + * send the verification code to the entered number. + * @param verificationCode (Step: [PhoneAuthStep.EnterVerificationCode]) The current value of the + * 6-digit code input field. + * @param onVerificationCodeChange (Step: [PhoneAuthStep.EnterVerificationCode]) A callback to be + * invoked when the verification code input changes. + * @param onVerifyCodeClick (Step: [PhoneAuthStep.EnterVerificationCode]) A callback to be invoked + * to submit the verification code. + * @param fullPhoneNumber (Step: [PhoneAuthStep.EnterVerificationCode]) The formatted full phone + * number to display for user confirmation. + * @param onResendCodeClick (Step: [PhoneAuthStep.EnterVerificationCode]) A callback to be invoked + * when the user clicks "Resend Code". + * @param resendTimer (Step: [PhoneAuthStep.EnterVerificationCode]) The number of seconds remaining + * before the "Resend" action is available. + * @param onChangeNumberClick (Step: [PhoneAuthStep.EnterVerificationCode]) A callback to navigate + * back to the [PhoneAuthStep.EnterPhoneNumber] step. + */ +class PhoneAuthContentState( + val step: PhoneAuthStep, + val isLoading: Boolean = false, + val error: String? = null, + val phoneNumber: String, + val useInstantVerificationEnabled: Boolean, + val onUseInstantVerificationChange: (Boolean) -> Unit, + val onPhoneNumberChange: (String) -> Unit, + val selectedCountry: CountryData, + val onCountrySelected: (CountryData) -> Unit, + val onSendCodeClick: () -> Unit, + val verificationCode: String, + val onVerificationCodeChange: (String) -> Unit, + val onVerifyCodeClick: () -> Unit, + val fullPhoneNumber: String, + val onResendCodeClick: () -> Unit, + val resendTimer: Int = 0, + val onChangeNumberClick: () -> Unit, +) + +/** + * A stateful composable that manages the complete logic for phone number authentication. It handles + * the multi-step flow of sending and verifying an SMS code, exposing the state for each step to a + * custom UI via a trailing lambda (slot). This component renders no UI itself. + * + * @param context The Android context. + * @param configuration The authentication UI configuration containing the phone provider settings. + * @param authUI The FirebaseAuthUI instance used for authentication operations. + * @param onSuccess Callback invoked when authentication succeeds with the [AuthResult]. + * @param onError Callback invoked when an authentication error occurs. + * @param onCancel Callback invoked when the user cancels the authentication flow. + * @param modifier Optional [Modifier] for the composable. + * @param content A composable lambda that receives [PhoneAuthContentState] to render the UI for + * each step. If null, no UI will be rendered. + */ +@Composable +fun PhoneAuthScreen( + context: Context, + configuration: AuthUIConfiguration, + authUI: FirebaseAuthUI, + onSuccess: (AuthResult) -> Unit, + onError: (AuthException) -> Unit, + onCancel: () -> Unit, + modifier: Modifier = Modifier, + content: @Composable ((PhoneAuthContentState) -> Unit)? = null, +) { + val provider = configuration.providers.filterIsInstance().first() + val stringProvider = DefaultAuthUIStringProvider(context) + val coroutineScope = rememberCoroutineScope() + + val step = rememberSaveable { mutableStateOf(PhoneAuthStep.EnterPhoneNumber) } + val phoneNumberValue = rememberSaveable { mutableStateOf(provider.defaultNumber ?: "") } + val useInstantVerificationEnabled = + rememberSaveable { mutableStateOf(provider.isInstantVerificationEnabled) } + val verificationCodeValue = rememberSaveable { mutableStateOf("") } + val selectedCountry = remember { + mutableStateOf( + provider.defaultCountryCode?.let { code -> + CountryUtils.findByCountryCode(code) + } ?: CountryUtils.getDefaultCountry() + ) + } + val fullPhoneNumber = + CountryUtils.formatPhoneNumber(selectedCountry.value.dialCode, phoneNumberValue.value) + val verificationId = rememberSaveable { mutableStateOf(null) } + val forceResendingToken = + rememberSaveable { mutableStateOf(null) } + val resendTimerSeconds = rememberSaveable { mutableIntStateOf(0) } + + val authState by authUI.authStateFlow().collectAsState(AuthState.Idle) + val isLoading = authState is AuthState.Loading + val errorMessage = + if (authState is AuthState.Error) (authState as AuthState.Error).exception.message else null + + val isErrorDialogVisible = + remember(authState) { mutableStateOf(authState is AuthState.Error) } + + // Handle resend timer countdown + LaunchedEffect(resendTimerSeconds.intValue) { + if (resendTimerSeconds.intValue > 0) { + delay(1000) + resendTimerSeconds.intValue-- + } + } + + LaunchedEffect(authState) { + Log.d("PhoneAuthScreen", "Current state: $authState") + when (val state = authState) { + is AuthState.Success -> { + state.result?.let { result -> + onSuccess(result) + } + } + + is AuthState.PhoneNumberVerificationRequired -> { + verificationId.value = state.verificationId + forceResendingToken.value = state.forceResendingToken + step.value = PhoneAuthStep.EnterVerificationCode + resendTimerSeconds.intValue = 60 // Start 60-second countdown + } + + is AuthState.SMSAutoVerified -> { + // Auto-verification succeeded, sign in with the credential + if (useInstantVerificationEnabled.value) { + coroutineScope.launch { + try { + authUI.signInWithPhoneAuthCredential( + config = configuration, + credential = state.credential + ) + } catch (e: Exception) { + // Error will be handled by authState flow + } + } + } + } + + is AuthState.Error -> { + onError(AuthException.from(state.exception)) + } + + is AuthState.Cancelled -> { + onCancel() + } + + else -> Unit + } + } + + val state = PhoneAuthContentState( + step = step.value, + isLoading = isLoading, + error = errorMessage, + phoneNumber = phoneNumberValue.value, + useInstantVerificationEnabled = useInstantVerificationEnabled.value, + onUseInstantVerificationChange = { value -> + useInstantVerificationEnabled.value = value + }, + onPhoneNumberChange = { number -> + phoneNumberValue.value = number + }, + selectedCountry = selectedCountry.value, + onCountrySelected = { country -> + selectedCountry.value = country + }, + onSendCodeClick = { + coroutineScope.launch { + try { + authUI.verifyPhoneNumber( + provider = provider, + phoneNumber = fullPhoneNumber, + ) + } catch (e: Exception) { + // Error will be handled by authState flow + } + } + }, + verificationCode = verificationCodeValue.value, + onVerificationCodeChange = { code -> + verificationCodeValue.value = code + }, + onVerifyCodeClick = { + coroutineScope.launch { + try { + verificationId.value?.let { id -> + authUI.submitVerificationCode( + config = configuration, + verificationId = id, + code = verificationCodeValue.value + ) + } + } catch (e: Exception) { + // Error will be handled by authState flow + } + } + }, + fullPhoneNumber = fullPhoneNumber, + onResendCodeClick = { + if (resendTimerSeconds.intValue == 0) { + coroutineScope.launch { + try { + authUI.verifyPhoneNumber( + provider = provider, + phoneNumber = fullPhoneNumber, + forceResendingToken = forceResendingToken.value, + ) + resendTimerSeconds.intValue = 60 // Restart timer + } catch (e: Exception) { + // Error will be handled by authState flow + } + } + } + }, + resendTimer = resendTimerSeconds.intValue, + onChangeNumberClick = { + step.value = PhoneAuthStep.EnterPhoneNumber + verificationCodeValue.value = "" + verificationId.value = null + forceResendingToken.value = null + resendTimerSeconds.intValue = 0 + } + ) + + if (isErrorDialogVisible.value) { + ErrorRecoveryDialog( + error = when ((authState as AuthState.Error).exception) { + is AuthException -> (authState as AuthState.Error).exception as AuthException + else -> AuthException.from((authState as AuthState.Error).exception) + }, + stringProvider = stringProvider, + onRetry = { exception -> + when (exception) { + is AuthException.InvalidCredentialsException -> { + if (step.value == PhoneAuthStep.EnterVerificationCode) { + state.onVerifyCodeClick() + } else { + state.onSendCodeClick() + } + } + + else -> Unit + } + isErrorDialogVisible.value = false + }, + onDismiss = { + isErrorDialogVisible.value = false + }, + ) + } + + content?.invoke(state) +} + diff --git a/auth/src/main/res/values-ar/strings.xml b/auth/src/main/res/values-ar/strings.xml index 6d91d65bc..c8c79a250 100755 --- a/auth/src/main/res/values-ar/strings.xml +++ b/auth/src/main/res/values-ar/strings.xml @@ -87,6 +87,7 @@ تمّ التحقّق تلقائيًا من رقم الهاتف. إعادة إرسال الرمز تأكيد ملكية رقم الهاتف + Use a different phone number عند النقر على “%1$s”، قد يتمّ إرسال رسالة قصيرة SMS وقد يتمّ تطبيق رسوم الرسائل والبيانات. يشير النقر على "%1$s" إلى موافقتك على %2$s و%3$s. وقد يتمّ إرسال رسالة قصيرة كما قد تنطبق رسوم الرسائل والبيانات. خطأ في المصادقة diff --git a/auth/src/main/res/values-b+es+419/strings.xml b/auth/src/main/res/values-b+es+419/strings.xml index 848171d77..ab6c79322 100755 --- a/auth/src/main/res/values-b+es+419/strings.xml +++ b/auth/src/main/res/values-b+es+419/strings.xml @@ -87,6 +87,7 @@ Se verificó automáticamente el número de teléfono Reenviar código Verificar número de teléfono + Use a different phone number Si presionas “%1$s”, se enviará un SMS. Se aplicarán las tarifas de mensajes y datos. Si presionas "%1$s", indicas que aceptas nuestras %2$s y %3$s. Es posible que se te envíe un SMS. Podrían aplicarse las tarifas de mensajes y datos. Error de autenticación diff --git a/auth/src/main/res/values-bg/strings.xml b/auth/src/main/res/values-bg/strings.xml index 24057a7e2..75feabfb3 100755 --- a/auth/src/main/res/values-bg/strings.xml +++ b/auth/src/main/res/values-bg/strings.xml @@ -87,6 +87,7 @@ Телефонният номер е потвърден автоматично Повторно изпращане на кода Потвърждаване на телефонния номер + Use a different phone number Докосвайки „%1$s“, може да получите SMS съобщение. То може да се таксува по тарифите за данни и SMS. Докосвайки „%1$s", приемате нашите %2$s и %3$s. Възможно е да получите SMS съобщение. То може да се таксува по тарифите за данни и SMS. Грешка при удостоверяване diff --git a/auth/src/main/res/values-bn/strings.xml b/auth/src/main/res/values-bn/strings.xml index bdd03d0a4..b17938ab4 100755 --- a/auth/src/main/res/values-bn/strings.xml +++ b/auth/src/main/res/values-bn/strings.xml @@ -87,6 +87,7 @@ ফোন নম্বরটি নিজে থেকে যাচাই করা হয়েছে কোডটি আবার পাঠান ফোন নম্বর যাচাই করুন + Use a different phone number %1$s এ ট্যাপ করলে আপনি একটি এসএমএস পাঠাতে পারেন। মেসেজ ও ডেটার চার্জ প্রযোজ্য। “%1$s” বোতামে ট্যাপ করার অর্থ, আপনি আমাদের %2$s এবং %3$s-এর সাথে সম্মত। একটি এসএমএস পাঠানো হতে পারে। মেসেজ এবং ডেটার উপরে প্রযোজ্য চার্জ লাগতে পারে। diff --git a/auth/src/main/res/values-ca/strings.xml b/auth/src/main/res/values-ca/strings.xml index 97c4fe56f..74e3e0d86 100755 --- a/auth/src/main/res/values-ca/strings.xml +++ b/auth/src/main/res/values-ca/strings.xml @@ -87,6 +87,7 @@ El número de telèfon s\'ha verificat automàticament Torna a enviar el codi Verifica el número de telèfon + Use a different phone number En tocar %1$s, és possible que s\'enviï un SMS. Es poden aplicar tarifes de dades i missatges. En tocar %1$s, acceptes les nostres %2$s i la nostra %3$s. És possible que s\'enviï un SMS. Es poden aplicar tarifes de dades i missatges. diff --git a/auth/src/main/res/values-cs/strings.xml b/auth/src/main/res/values-cs/strings.xml index 04ea9a9e6..030a7082d 100755 --- a/auth/src/main/res/values-cs/strings.xml +++ b/auth/src/main/res/values-cs/strings.xml @@ -87,6 +87,7 @@ Telefonní číslo bylo automaticky ověřeno Znovu poslat kód Ověřit telefonní číslo + Use a different phone number Po klepnutí na možnost %1$s může být odeslána SMS. Mohou být účtovány poplatky za zprávy a data. Klepnutím na tlačítko %1$s vyjadřujete svůj souhlas s dokumenty %2$s a %3$s. Může být odeslána SMS a mohou být účtovány poplatky za zprávy a data. Chyba ověření diff --git a/auth/src/main/res/values-da/strings.xml b/auth/src/main/res/values-da/strings.xml index 6d79e8a97..871f3b16e 100755 --- a/auth/src/main/res/values-da/strings.xml +++ b/auth/src/main/res/values-da/strings.xml @@ -87,6 +87,7 @@ Telefonnummeret blev bekræftet automatisk Send koden igen Bekræft telefonnummer + Use a different phone number Når du trykker på “%1$s”, sendes der måske en sms. Der opkræves muligvis gebyrer for beskeder og data. Når du trykker på "%1$s", indikerer du, at du accepterer vores %2$s og %3$s. Der sendes måske en sms. Der opkræves muligvis gebyrer for beskeder og data. Godkendelsesfejl diff --git a/auth/src/main/res/values-de-rAT/strings.xml b/auth/src/main/res/values-de-rAT/strings.xml index 64b9ac2ff..0552d5986 100755 --- a/auth/src/main/res/values-de-rAT/strings.xml +++ b/auth/src/main/res/values-de-rAT/strings.xml @@ -87,6 +87,7 @@ Telefonnummer wurde automatisch bestätigt Code erneut senden Telefonnummer bestätigen + Use a different phone number Wenn Sie auf “%1$s” tippen, erhalten Sie möglicherweise eine SMS. Es können Gebühren für SMS und Datenübertragung anfallen. Indem Sie auf “%1$s” tippen, stimmen Sie unseren %2$s und unserer %3$s zu. Sie erhalten möglicherweise eine SMS und es können Gebühren für die Nachricht und die Datenübertragung anfallen. Authentication Error diff --git a/auth/src/main/res/values-de-rCH/strings.xml b/auth/src/main/res/values-de-rCH/strings.xml index 00278a40e..f1158773e 100755 --- a/auth/src/main/res/values-de-rCH/strings.xml +++ b/auth/src/main/res/values-de-rCH/strings.xml @@ -87,6 +87,7 @@ Telefonnummer wurde automatisch bestätigt Code erneut senden Telefonnummer bestätigen + Use a different phone number Wenn Sie auf “%1$s” tippen, erhalten Sie möglicherweise eine SMS. Es können Gebühren für SMS und Datenübertragung anfallen. Indem Sie auf “%1$s” tippen, stimmen Sie unseren %2$s und unserer %3$s zu. Sie erhalten möglicherweise eine SMS und es können Gebühren für die Nachricht und die Datenübertragung anfallen. diff --git a/auth/src/main/res/values-de/strings.xml b/auth/src/main/res/values-de/strings.xml index 90b7458a2..4febb8ee4 100755 --- a/auth/src/main/res/values-de/strings.xml +++ b/auth/src/main/res/values-de/strings.xml @@ -87,6 +87,7 @@ Telefonnummer wurde automatisch bestätigt Code erneut senden Telefonnummer bestätigen + Use a different phone number Wenn Sie auf “%1$s” tippen, erhalten Sie möglicherweise eine SMS. Es können Gebühren für SMS und Datenübertragung anfallen. Indem Sie auf "%1$s" tippen, stimmen Sie unseren %2$s und unserer %3$s zu. Sie erhalten möglicherweise eine SMS und es können Gebühren für die Nachricht und die Datenübertragung anfallen. Authentifizierungsfehler diff --git a/auth/src/main/res/values-el/strings.xml b/auth/src/main/res/values-el/strings.xml index 5853da17c..3375225b1 100755 --- a/auth/src/main/res/values-el/strings.xml +++ b/auth/src/main/res/values-el/strings.xml @@ -87,6 +87,7 @@ Ο αριθμός τηλεφώνου επαληθεύτηκε αυτόματα Επανάληψη αποστολής κωδικού Επαλήθευση αριθμού τηλεφώνου + Use a different phone number Αν πατήσετε “%1$s”, μπορεί να σταλεί ένα SMS. Ενδέχεται να ισχύουν χρεώσεις μηνυμάτων και δεδομένων. Αν πατήσετε “%1$s”, δηλώνετε ότι αποδέχεστε τους %2$s και την %3$s. Μπορεί να σταλεί ένα SMS. Ενδέχεται να ισχύουν χρεώσεις μηνυμάτων και δεδομένων. diff --git a/auth/src/main/res/values-en-rAU/strings.xml b/auth/src/main/res/values-en-rAU/strings.xml index 3d68aaf3a..6e912a145 100755 --- a/auth/src/main/res/values-en-rAU/strings.xml +++ b/auth/src/main/res/values-en-rAU/strings.xml @@ -87,6 +87,7 @@ Phone number automatically verified Resend code Verify Phone Number + Use a different phone number By tapping “%1$s”, an SMS may be sent. Message & data rates may apply. By tapping "%1$s", you are indicating that you accept our %2$s and %3$s. An SMS may be sent. Message & data rates may apply. Authentication Error diff --git a/auth/src/main/res/values-en-rCA/strings.xml b/auth/src/main/res/values-en-rCA/strings.xml index 2cfa96ac2..647bb4df6 100755 --- a/auth/src/main/res/values-en-rCA/strings.xml +++ b/auth/src/main/res/values-en-rCA/strings.xml @@ -87,6 +87,7 @@ Phone number automatically verified Resend code Verify Phone Number + Use a different phone number By tapping “%1$s”, an SMS may be sent. Message & data rates may apply. By tapping "%1$s", you are indicating that you accept our %2$s and %3$s. An SMS may be sent. Message & data rates may apply. Authentication Error diff --git a/auth/src/main/res/values-en-rGB/strings.xml b/auth/src/main/res/values-en-rGB/strings.xml index 2cfa96ac2..647bb4df6 100755 --- a/auth/src/main/res/values-en-rGB/strings.xml +++ b/auth/src/main/res/values-en-rGB/strings.xml @@ -87,6 +87,7 @@ Phone number automatically verified Resend code Verify Phone Number + Use a different phone number By tapping “%1$s”, an SMS may be sent. Message & data rates may apply. By tapping "%1$s", you are indicating that you accept our %2$s and %3$s. An SMS may be sent. Message & data rates may apply. Authentication Error diff --git a/auth/src/main/res/values-en-rIE/strings.xml b/auth/src/main/res/values-en-rIE/strings.xml index 7c76fbdea..08909c65c 100755 --- a/auth/src/main/res/values-en-rIE/strings.xml +++ b/auth/src/main/res/values-en-rIE/strings.xml @@ -87,6 +87,7 @@ Phone number automatically verified Resend code Verify Phone Number + Use a different phone number By tapping “%1$s”, an SMS may be sent. Message & data rates may apply. By tapping "%1$s", you are indicating that you accept our %2$s and %3$s. An SMS may be sent. Message & data rates may apply. Authentication Error diff --git a/auth/src/main/res/values-en-rIN/strings.xml b/auth/src/main/res/values-en-rIN/strings.xml index 7c76fbdea..08909c65c 100755 --- a/auth/src/main/res/values-en-rIN/strings.xml +++ b/auth/src/main/res/values-en-rIN/strings.xml @@ -87,6 +87,7 @@ Phone number automatically verified Resend code Verify Phone Number + Use a different phone number By tapping “%1$s”, an SMS may be sent. Message & data rates may apply. By tapping "%1$s", you are indicating that you accept our %2$s and %3$s. An SMS may be sent. Message & data rates may apply. Authentication Error diff --git a/auth/src/main/res/values-en-rSG/strings.xml b/auth/src/main/res/values-en-rSG/strings.xml index 7c76fbdea..08909c65c 100755 --- a/auth/src/main/res/values-en-rSG/strings.xml +++ b/auth/src/main/res/values-en-rSG/strings.xml @@ -87,6 +87,7 @@ Phone number automatically verified Resend code Verify Phone Number + Use a different phone number By tapping “%1$s”, an SMS may be sent. Message & data rates may apply. By tapping "%1$s", you are indicating that you accept our %2$s and %3$s. An SMS may be sent. Message & data rates may apply. Authentication Error diff --git a/auth/src/main/res/values-en-rZA/strings.xml b/auth/src/main/res/values-en-rZA/strings.xml index 7c76fbdea..08909c65c 100755 --- a/auth/src/main/res/values-en-rZA/strings.xml +++ b/auth/src/main/res/values-en-rZA/strings.xml @@ -87,6 +87,7 @@ Phone number automatically verified Resend code Verify Phone Number + Use a different phone number By tapping “%1$s”, an SMS may be sent. Message & data rates may apply. By tapping "%1$s", you are indicating that you accept our %2$s and %3$s. An SMS may be sent. Message & data rates may apply. Authentication Error diff --git a/auth/src/main/res/values-es-rAR/strings.xml b/auth/src/main/res/values-es-rAR/strings.xml index d731899dd..4c7090e3d 100755 --- a/auth/src/main/res/values-es-rAR/strings.xml +++ b/auth/src/main/res/values-es-rAR/strings.xml @@ -87,6 +87,7 @@ Se verificó automáticamente el número de teléfono Reenviar código Verificar número de teléfono + Use a different phone number Si presionas “%1$s”, se enviará un SMS. Se aplicarán las tarifas de mensajes y datos. Si presionas "%1$s", indicas que aceptas nuestras %2$s y %3$s. Es posible que se te envíe un SMS. Podrían aplicarse las tarifas de mensajes y datos. Error de autenticación diff --git a/auth/src/main/res/values-es-rBO/strings.xml b/auth/src/main/res/values-es-rBO/strings.xml index d731899dd..4c7090e3d 100755 --- a/auth/src/main/res/values-es-rBO/strings.xml +++ b/auth/src/main/res/values-es-rBO/strings.xml @@ -87,6 +87,7 @@ Se verificó automáticamente el número de teléfono Reenviar código Verificar número de teléfono + Use a different phone number Si presionas “%1$s”, se enviará un SMS. Se aplicarán las tarifas de mensajes y datos. Si presionas "%1$s", indicas que aceptas nuestras %2$s y %3$s. Es posible que se te envíe un SMS. Podrían aplicarse las tarifas de mensajes y datos. Error de autenticación diff --git a/auth/src/main/res/values-es-rCL/strings.xml b/auth/src/main/res/values-es-rCL/strings.xml index d731899dd..4c7090e3d 100755 --- a/auth/src/main/res/values-es-rCL/strings.xml +++ b/auth/src/main/res/values-es-rCL/strings.xml @@ -87,6 +87,7 @@ Se verificó automáticamente el número de teléfono Reenviar código Verificar número de teléfono + Use a different phone number Si presionas “%1$s”, se enviará un SMS. Se aplicarán las tarifas de mensajes y datos. Si presionas "%1$s", indicas que aceptas nuestras %2$s y %3$s. Es posible que se te envíe un SMS. Podrían aplicarse las tarifas de mensajes y datos. Error de autenticación diff --git a/auth/src/main/res/values-es-rCO/strings.xml b/auth/src/main/res/values-es-rCO/strings.xml index d731899dd..4c7090e3d 100755 --- a/auth/src/main/res/values-es-rCO/strings.xml +++ b/auth/src/main/res/values-es-rCO/strings.xml @@ -87,6 +87,7 @@ Se verificó automáticamente el número de teléfono Reenviar código Verificar número de teléfono + Use a different phone number Si presionas “%1$s”, se enviará un SMS. Se aplicarán las tarifas de mensajes y datos. Si presionas "%1$s", indicas que aceptas nuestras %2$s y %3$s. Es posible que se te envíe un SMS. Podrían aplicarse las tarifas de mensajes y datos. Error de autenticación diff --git a/auth/src/main/res/values-es-rCR/strings.xml b/auth/src/main/res/values-es-rCR/strings.xml index d731899dd..4c7090e3d 100755 --- a/auth/src/main/res/values-es-rCR/strings.xml +++ b/auth/src/main/res/values-es-rCR/strings.xml @@ -87,6 +87,7 @@ Se verificó automáticamente el número de teléfono Reenviar código Verificar número de teléfono + Use a different phone number Si presionas “%1$s”, se enviará un SMS. Se aplicarán las tarifas de mensajes y datos. Si presionas "%1$s", indicas que aceptas nuestras %2$s y %3$s. Es posible que se te envíe un SMS. Podrían aplicarse las tarifas de mensajes y datos. Error de autenticación diff --git a/auth/src/main/res/values-es-rDO/strings.xml b/auth/src/main/res/values-es-rDO/strings.xml index d731899dd..4c7090e3d 100755 --- a/auth/src/main/res/values-es-rDO/strings.xml +++ b/auth/src/main/res/values-es-rDO/strings.xml @@ -87,6 +87,7 @@ Se verificó automáticamente el número de teléfono Reenviar código Verificar número de teléfono + Use a different phone number Si presionas “%1$s”, se enviará un SMS. Se aplicarán las tarifas de mensajes y datos. Si presionas "%1$s", indicas que aceptas nuestras %2$s y %3$s. Es posible que se te envíe un SMS. Podrían aplicarse las tarifas de mensajes y datos. Error de autenticación diff --git a/auth/src/main/res/values-es-rEC/strings.xml b/auth/src/main/res/values-es-rEC/strings.xml index d731899dd..4c7090e3d 100755 --- a/auth/src/main/res/values-es-rEC/strings.xml +++ b/auth/src/main/res/values-es-rEC/strings.xml @@ -87,6 +87,7 @@ Se verificó automáticamente el número de teléfono Reenviar código Verificar número de teléfono + Use a different phone number Si presionas “%1$s”, se enviará un SMS. Se aplicarán las tarifas de mensajes y datos. Si presionas "%1$s", indicas que aceptas nuestras %2$s y %3$s. Es posible que se te envíe un SMS. Podrían aplicarse las tarifas de mensajes y datos. Error de autenticación diff --git a/auth/src/main/res/values-es-rGT/strings.xml b/auth/src/main/res/values-es-rGT/strings.xml index d731899dd..4c7090e3d 100755 --- a/auth/src/main/res/values-es-rGT/strings.xml +++ b/auth/src/main/res/values-es-rGT/strings.xml @@ -87,6 +87,7 @@ Se verificó automáticamente el número de teléfono Reenviar código Verificar número de teléfono + Use a different phone number Si presionas “%1$s”, se enviará un SMS. Se aplicarán las tarifas de mensajes y datos. Si presionas "%1$s", indicas que aceptas nuestras %2$s y %3$s. Es posible que se te envíe un SMS. Podrían aplicarse las tarifas de mensajes y datos. Error de autenticación diff --git a/auth/src/main/res/values-es-rHN/strings.xml b/auth/src/main/res/values-es-rHN/strings.xml index d731899dd..4c7090e3d 100755 --- a/auth/src/main/res/values-es-rHN/strings.xml +++ b/auth/src/main/res/values-es-rHN/strings.xml @@ -87,6 +87,7 @@ Se verificó automáticamente el número de teléfono Reenviar código Verificar número de teléfono + Use a different phone number Si presionas “%1$s”, se enviará un SMS. Se aplicarán las tarifas de mensajes y datos. Si presionas "%1$s", indicas que aceptas nuestras %2$s y %3$s. Es posible que se te envíe un SMS. Podrían aplicarse las tarifas de mensajes y datos. Error de autenticación diff --git a/auth/src/main/res/values-es-rMX/strings.xml b/auth/src/main/res/values-es-rMX/strings.xml index d731899dd..4c7090e3d 100755 --- a/auth/src/main/res/values-es-rMX/strings.xml +++ b/auth/src/main/res/values-es-rMX/strings.xml @@ -87,6 +87,7 @@ Se verificó automáticamente el número de teléfono Reenviar código Verificar número de teléfono + Use a different phone number Si presionas “%1$s”, se enviará un SMS. Se aplicarán las tarifas de mensajes y datos. Si presionas "%1$s", indicas que aceptas nuestras %2$s y %3$s. Es posible que se te envíe un SMS. Podrían aplicarse las tarifas de mensajes y datos. Error de autenticación diff --git a/auth/src/main/res/values-es-rNI/strings.xml b/auth/src/main/res/values-es-rNI/strings.xml index d731899dd..4c7090e3d 100755 --- a/auth/src/main/res/values-es-rNI/strings.xml +++ b/auth/src/main/res/values-es-rNI/strings.xml @@ -87,6 +87,7 @@ Se verificó automáticamente el número de teléfono Reenviar código Verificar número de teléfono + Use a different phone number Si presionas “%1$s”, se enviará un SMS. Se aplicarán las tarifas de mensajes y datos. Si presionas "%1$s", indicas que aceptas nuestras %2$s y %3$s. Es posible que se te envíe un SMS. Podrían aplicarse las tarifas de mensajes y datos. Error de autenticación diff --git a/auth/src/main/res/values-es-rPA/strings.xml b/auth/src/main/res/values-es-rPA/strings.xml index d731899dd..4c7090e3d 100755 --- a/auth/src/main/res/values-es-rPA/strings.xml +++ b/auth/src/main/res/values-es-rPA/strings.xml @@ -87,6 +87,7 @@ Se verificó automáticamente el número de teléfono Reenviar código Verificar número de teléfono + Use a different phone number Si presionas “%1$s”, se enviará un SMS. Se aplicarán las tarifas de mensajes y datos. Si presionas "%1$s", indicas que aceptas nuestras %2$s y %3$s. Es posible que se te envíe un SMS. Podrían aplicarse las tarifas de mensajes y datos. Error de autenticación diff --git a/auth/src/main/res/values-es-rPE/strings.xml b/auth/src/main/res/values-es-rPE/strings.xml index d731899dd..4c7090e3d 100755 --- a/auth/src/main/res/values-es-rPE/strings.xml +++ b/auth/src/main/res/values-es-rPE/strings.xml @@ -87,6 +87,7 @@ Se verificó automáticamente el número de teléfono Reenviar código Verificar número de teléfono + Use a different phone number Si presionas “%1$s”, se enviará un SMS. Se aplicarán las tarifas de mensajes y datos. Si presionas "%1$s", indicas que aceptas nuestras %2$s y %3$s. Es posible que se te envíe un SMS. Podrían aplicarse las tarifas de mensajes y datos. Error de autenticación diff --git a/auth/src/main/res/values-es-rPR/strings.xml b/auth/src/main/res/values-es-rPR/strings.xml index d731899dd..4c7090e3d 100755 --- a/auth/src/main/res/values-es-rPR/strings.xml +++ b/auth/src/main/res/values-es-rPR/strings.xml @@ -87,6 +87,7 @@ Se verificó automáticamente el número de teléfono Reenviar código Verificar número de teléfono + Use a different phone number Si presionas “%1$s”, se enviará un SMS. Se aplicarán las tarifas de mensajes y datos. Si presionas "%1$s", indicas que aceptas nuestras %2$s y %3$s. Es posible que se te envíe un SMS. Podrían aplicarse las tarifas de mensajes y datos. Error de autenticación diff --git a/auth/src/main/res/values-es-rPY/strings.xml b/auth/src/main/res/values-es-rPY/strings.xml index d731899dd..4c7090e3d 100755 --- a/auth/src/main/res/values-es-rPY/strings.xml +++ b/auth/src/main/res/values-es-rPY/strings.xml @@ -87,6 +87,7 @@ Se verificó automáticamente el número de teléfono Reenviar código Verificar número de teléfono + Use a different phone number Si presionas “%1$s”, se enviará un SMS. Se aplicarán las tarifas de mensajes y datos. Si presionas "%1$s", indicas que aceptas nuestras %2$s y %3$s. Es posible que se te envíe un SMS. Podrían aplicarse las tarifas de mensajes y datos. Error de autenticación diff --git a/auth/src/main/res/values-es-rSV/strings.xml b/auth/src/main/res/values-es-rSV/strings.xml index d731899dd..4c7090e3d 100755 --- a/auth/src/main/res/values-es-rSV/strings.xml +++ b/auth/src/main/res/values-es-rSV/strings.xml @@ -87,6 +87,7 @@ Se verificó automáticamente el número de teléfono Reenviar código Verificar número de teléfono + Use a different phone number Si presionas “%1$s”, se enviará un SMS. Se aplicarán las tarifas de mensajes y datos. Si presionas "%1$s", indicas que aceptas nuestras %2$s y %3$s. Es posible que se te envíe un SMS. Podrían aplicarse las tarifas de mensajes y datos. Error de autenticación diff --git a/auth/src/main/res/values-es-rUS/strings.xml b/auth/src/main/res/values-es-rUS/strings.xml index d731899dd..4c7090e3d 100755 --- a/auth/src/main/res/values-es-rUS/strings.xml +++ b/auth/src/main/res/values-es-rUS/strings.xml @@ -87,6 +87,7 @@ Se verificó automáticamente el número de teléfono Reenviar código Verificar número de teléfono + Use a different phone number Si presionas “%1$s”, se enviará un SMS. Se aplicarán las tarifas de mensajes y datos. Si presionas "%1$s", indicas que aceptas nuestras %2$s y %3$s. Es posible que se te envíe un SMS. Podrían aplicarse las tarifas de mensajes y datos. Error de autenticación diff --git a/auth/src/main/res/values-es-rUY/strings.xml b/auth/src/main/res/values-es-rUY/strings.xml index d731899dd..4c7090e3d 100755 --- a/auth/src/main/res/values-es-rUY/strings.xml +++ b/auth/src/main/res/values-es-rUY/strings.xml @@ -87,6 +87,7 @@ Se verificó automáticamente el número de teléfono Reenviar código Verificar número de teléfono + Use a different phone number Si presionas “%1$s”, se enviará un SMS. Se aplicarán las tarifas de mensajes y datos. Si presionas "%1$s", indicas que aceptas nuestras %2$s y %3$s. Es posible que se te envíe un SMS. Podrían aplicarse las tarifas de mensajes y datos. Error de autenticación diff --git a/auth/src/main/res/values-es-rVE/strings.xml b/auth/src/main/res/values-es-rVE/strings.xml index d731899dd..4c7090e3d 100755 --- a/auth/src/main/res/values-es-rVE/strings.xml +++ b/auth/src/main/res/values-es-rVE/strings.xml @@ -87,6 +87,7 @@ Se verificó automáticamente el número de teléfono Reenviar código Verificar número de teléfono + Use a different phone number Si presionas “%1$s”, se enviará un SMS. Se aplicarán las tarifas de mensajes y datos. Si presionas "%1$s", indicas que aceptas nuestras %2$s y %3$s. Es posible que se te envíe un SMS. Podrían aplicarse las tarifas de mensajes y datos. Error de autenticación diff --git a/auth/src/main/res/values-es/strings.xml b/auth/src/main/res/values-es/strings.xml index 40f100b74..b3ce2fcd0 100755 --- a/auth/src/main/res/values-es/strings.xml +++ b/auth/src/main/res/values-es/strings.xml @@ -87,6 +87,7 @@ Se ha verificado automáticamente el número de teléfono Volver a enviar código Verificar número de teléfono + Use a different phone number Al tocar %1$s, podría enviarse un SMS. Es posible que se apliquen cargos de mensajería y de uso de datos. Si tocas %1$s, confirmas que aceptas nuestras %2$s y nuestra %3$s. Podría enviarse un SMS, por lo que es posible que se apliquen cargos de mensajería y de uso de datos. Error de autenticación diff --git a/auth/src/main/res/values-fa/strings.xml b/auth/src/main/res/values-fa/strings.xml index dcadfa791..331b79fa3 100755 --- a/auth/src/main/res/values-fa/strings.xml +++ b/auth/src/main/res/values-fa/strings.xml @@ -87,6 +87,7 @@ شماره تلفن به‌طور خودکار به‌تأیید رسید ارسال مجدد کد تأیید شماره تلفن + Use a different phone number با ضربه زدن روی «%1$s»، پیامکی برایتان ارسال می‌شود. هزینه پیام و داده اعمال می‌شود. درصورت ضربه‌زدن روی «%1$s»، موافقتتان را با %2$s و %3$s اعلام می‌کنید. پیامکی ارسال می‌شود. ممکن است هزینه داده و «پیام» محاسبه شود. diff --git a/auth/src/main/res/values-fi/strings.xml b/auth/src/main/res/values-fi/strings.xml index 01e38ce7b..ff346063d 100755 --- a/auth/src/main/res/values-fi/strings.xml +++ b/auth/src/main/res/values-fi/strings.xml @@ -87,6 +87,7 @@ Puhelinnumero vahvistettu automaattisesti Lähetä koodi uudelleen Vahvista puhelinnumero + Use a different phone number Kun napautat %1$s, tekstiviesti voidaan lähettää. Datan ja viestien käyttö voi olla maksullista. Napauttamalla %1$s vahvistat hyväksyväsi seuraavat: %2$s ja %3$s. Tekstiviesti voidaan lähettää, ja datan ja viestien käyttö voi olla maksullista. Todennusvirhe diff --git a/auth/src/main/res/values-fil/strings.xml b/auth/src/main/res/values-fil/strings.xml index 066f65c87..0915df67d 100755 --- a/auth/src/main/res/values-fil/strings.xml +++ b/auth/src/main/res/values-fil/strings.xml @@ -87,6 +87,7 @@ Awtomatikong na-verify ang numero ng telepono Ipadala Muli ang Code I-verify ang Numero ng Telepono + Use a different phone number Sa pag-tap sa “%1$s,“ maaaring magpadala ng SMS. Maaaring ipatupad ang mga rate ng pagmemensahe at data. Sa pag-tap sa “%1$s”, ipinababatid mo na tinatanggap mo ang aming %2$s at %3$s. Maaaring magpadala ng SMS. Maaaring ipatupad ang mga rate ng pagmemensahe at data. Error sa Pagpapatotoo diff --git a/auth/src/main/res/values-fr-rCH/strings.xml b/auth/src/main/res/values-fr-rCH/strings.xml index e3a691939..fac959937 100755 --- a/auth/src/main/res/values-fr-rCH/strings.xml +++ b/auth/src/main/res/values-fr-rCH/strings.xml @@ -87,6 +87,7 @@ Numéro de téléphone validé automatiquement Renvoyer le code Valider le numéro de téléphone + Use a different phone number En appuyant sur “%1$s”, vous déclencherez peut-être l\'envoi d\'un SMS. Des frais de messages et de données peuvent être facturés. En appuyant sur “%1$s”, vous acceptez les %2$s et les %3$s. Vous déclencherez peut-être l\'envoi d\'un SMS. Des frais de messages et de données peuvent être facturés. diff --git a/auth/src/main/res/values-fr/strings.xml b/auth/src/main/res/values-fr/strings.xml index 8fd57e38f..8fe2bb073 100755 --- a/auth/src/main/res/values-fr/strings.xml +++ b/auth/src/main/res/values-fr/strings.xml @@ -87,6 +87,7 @@ Numéro de téléphone validé automatiquement Renvoyer le code Valider le numéro de téléphone + Use a different phone number En appuyant sur “%1$s”, vous déclencherez peut-être l\'envoi d\'un SMS. Des frais de messages et de données peuvent être facturés. En appuyant sur "%1$s", vous acceptez les %2$s et les %3$s. Vous déclencherez peut-être l\'envoi d\'un SMS. Des frais de messages et de données peuvent être facturés. Erreur d\'authentification diff --git a/auth/src/main/res/values-gsw/strings.xml b/auth/src/main/res/values-gsw/strings.xml index 3f2ddc6ec..3c8d012ed 100755 --- a/auth/src/main/res/values-gsw/strings.xml +++ b/auth/src/main/res/values-gsw/strings.xml @@ -87,6 +87,7 @@ Telefonnummer wurde automatisch bestätigt Code erneut senden Telefonnummer bestätigen + Use a different phone number Wenn Sie auf “%1$s” tippen, erhalten Sie möglicherweise eine SMS. Es können Gebühren für SMS und Datenübertragung anfallen. Indem Sie auf “%1$s” tippen, stimmen Sie unseren %2$s und unserer %3$s zu. Sie erhalten möglicherweise eine SMS und es können Gebühren für die Nachricht und die Datenübertragung anfallen. Authentifizierungsfehler diff --git a/auth/src/main/res/values-gu/strings.xml b/auth/src/main/res/values-gu/strings.xml index 8bcd09b87..12aa15d72 100755 --- a/auth/src/main/res/values-gu/strings.xml +++ b/auth/src/main/res/values-gu/strings.xml @@ -87,6 +87,7 @@ ફોન નંબર આપમેળે ચકાસવામાં આવ્યો કોડ ફરીથી મોકલો ફોન નંબર ચકાસો + Use a different phone number “%1$s”ને ટૅપ કરવાથી, કદાચ એક SMS મોકલવામાં આવી શકે છે. સંદેશ અને ડેટા શુલ્ક લાગુ થઈ શકે છે. “%1$s” ટૅપ કરીને, તમે સૂચવી રહ્યાં છો કે તમે અમારી %2$s અને %3$sને સ્વીકારો છો. SMS મોકલવામાં આવી શકે છે. સંદેશ અને ડેટા શુલ્ક લાગુ થઈ શકે છે. diff --git a/auth/src/main/res/values-hi/strings.xml b/auth/src/main/res/values-hi/strings.xml index 980e9f4d2..09194051e 100755 --- a/auth/src/main/res/values-hi/strings.xml +++ b/auth/src/main/res/values-hi/strings.xml @@ -87,6 +87,7 @@ फ़ोन नंबर की अपने आप पुष्टि की गई कोड फिर से भेजें फ़ोन नंबर की पुष्टि करें + Use a different phone number “%1$s” पर टैप करने पर, एक मैसेज (एसएमएस) भेजा जा सकता है. मैसेज और डेटा दरें लागू हो सकती हैं. “%1$s” पर टैप करके, आप यह बताते हैं कि आप हमारी %2$s और %3$s को मंज़ूर करते हैं. एक मैसेज (एसएमएस) भेजा जा सकता है. मैसेज और डेटा दरें लागू हो सकती हैं. diff --git a/auth/src/main/res/values-hr/strings.xml b/auth/src/main/res/values-hr/strings.xml index 6e5c9dad9..9f799d3cd 100755 --- a/auth/src/main/res/values-hr/strings.xml +++ b/auth/src/main/res/values-hr/strings.xml @@ -87,6 +87,7 @@ Telefonski je broj automatski potvrđen Ponovo pošalji kôd Potvrda telefonskog broja + Use a different phone number Dodirivanje gumba “%1$s” može dovesti do slanja SMS poruke. Mogu se primijeniti naknade za slanje poruka i podatkovni promet. Ako dodirnete "%1$s", potvrđujete da prihvaćate odredbe koje sadrže %2$s i %3$s. Možda ćemo vam poslati SMS. Moguća je naplata poruke i podatkovnog prometa. Greška provjere identiteta diff --git a/auth/src/main/res/values-hu/strings.xml b/auth/src/main/res/values-hu/strings.xml index 7d342ba1d..879234e03 100755 --- a/auth/src/main/res/values-hu/strings.xml +++ b/auth/src/main/res/values-hu/strings.xml @@ -87,6 +87,7 @@ A telefonszám automatikusan ellenőrizve Kód újraküldése Telefonszám igazolása + Use a different phone number Ha a(z) „%1$s” gombra koppint, a rendszer SMS-t küldhet Önnek. A szolgáltató ezért üzenet- és adatforgalmi díjat számíthat fel. A(z) „%1$s” gombra való koppintással elfogadja a következő dokumentumokat: %2$s és %3$s. A rendszer SMS-t küldhet Önnek. A szolgáltató ezért üzenet- és adatforgalmi díjat számíthat fel. Hitelesítési hiba diff --git a/auth/src/main/res/values-in/strings.xml b/auth/src/main/res/values-in/strings.xml index 124bb90dd..d74dcfcc2 100755 --- a/auth/src/main/res/values-in/strings.xml +++ b/auth/src/main/res/values-in/strings.xml @@ -87,6 +87,7 @@ Nomor telepon terverifikasi secara otomatis Kirim Ulang Kode Verifikasi Nomor Telepon + Use a different phone number Dengan mengetuk “%1$s\", SMS mungkin akan dikirim. Mungkin dikenakan biaya pesan & data. Dengan mengetuk “%1$s”, Anda menyatakan bahwa Anda menyetujui %2$s dan %3$s kami. SMS mungkin akan dikirim. Mungkin dikenakan biaya pesan & data. diff --git a/auth/src/main/res/values-it/strings.xml b/auth/src/main/res/values-it/strings.xml index cfcca546d..bbd79dffd 100755 --- a/auth/src/main/res/values-it/strings.xml +++ b/auth/src/main/res/values-it/strings.xml @@ -87,6 +87,7 @@ Numero di telefono verificato automaticamente Invia di nuovo il codice Verifica numero di telefono + Use a different phone number Se tocchi “%1$s”, è possibile che venga inviato un SMS. Potrebbero essere applicate le tariffe per l\'invio dei messaggi e per il traffico dati. Se tocchi "%1$s", accetti i nostri %2$s e le nostre %3$s. È possibile che venga inviato un SMS. Potrebbero essere applicate le tariffe per l\'invio dei messaggi e per il traffico dati. Errore di autenticazione diff --git a/auth/src/main/res/values-iw/strings.xml b/auth/src/main/res/values-iw/strings.xml index 199692586..76ecde56b 100755 --- a/auth/src/main/res/values-iw/strings.xml +++ b/auth/src/main/res/values-iw/strings.xml @@ -87,6 +87,7 @@ מספר הטלפון אומת באופן אוטומטי שלח קוד חדש אמת את מספר הטלפון + Use a different phone number הקשה על “%1$s” עשויה לגרום לשליחה של הודעת SMS. ייתכן שיחולו תעריפי הודעות והעברת נתונים. הקשה על “%1$s”, תפורש כהסכמתך ל%2$s ול%3$s. ייתכן שתישלח הודעת SMS. ייתכנו חיובים בגין שליחת הודעות ושימוש בנתונים. diff --git a/auth/src/main/res/values-ja/strings.xml b/auth/src/main/res/values-ja/strings.xml index b90a233bc..f2723d81f 100755 --- a/auth/src/main/res/values-ja/strings.xml +++ b/auth/src/main/res/values-ja/strings.xml @@ -87,6 +87,7 @@ 電話番号は自動的に確認されました コードを再送信 電話番号を確認 + Use a different phone number [%1$s] をタップすると、SMS が送信されます。データ通信料がかかることがあります。 [%1$s] をタップすると、%2$s と %3$s に同意したことになり、SMS が送信されます。データ通信料がかかることがあります。 認証エラー diff --git a/auth/src/main/res/values-kn/strings.xml b/auth/src/main/res/values-kn/strings.xml index 27bff3ff6..a087b41a5 100755 --- a/auth/src/main/res/values-kn/strings.xml +++ b/auth/src/main/res/values-kn/strings.xml @@ -87,6 +87,7 @@ ಫೋನ್ ಸಂಖ್ಯೆಯನ್ನು ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಪರಿಶೀಲಿಸಲಾಗಿದೆ ಕೋಡ್ ಪುನಃ ಕಳುಹಿಸಿ ಫೋನ್ ಸಂಖ್ಯೆಯನ್ನು ಪರಿಶೀಲಿಸಿ + Use a different phone number “%1$s” ಅನ್ನು ಟ್ಯಾಪ್ ಮಾಡುವ ಮೂಲಕ, ಎಸ್‌ಎಂಎಸ್‌ ಅನ್ನು ಕಳುಹಿಸಬಹುದಾಗಿದೆ. ಸಂದೇಶ ಮತ್ತು ಡೇಟಾ ದರಗಳು ಅನ್ವಯಿಸಬಹುದು. “%1$s” ಅನ್ನು ಟ್ಯಾಪ್ ಮಾಡುವ ಮೂಲಕ, ನೀವು ನಮ್ಮ %2$s ಮತ್ತು %3$s ಸ್ವೀಕರಿಸುತ್ತೀರಿ ಎಂದು ನೀವು ಸೂಚಿಸುತ್ತಿರುವಿರಿ. ಎಸ್‌ಎಂಎಸ್‌ ಅನ್ನು ಕಳುಹಿಸಬಹುದಾಗಿದೆ. ಸಂದೇಶ ಮತ್ತು ಡೇಟಾ ದರಗಳು ಅನ್ವಯಿಸಬಹುದು. diff --git a/auth/src/main/res/values-ko/strings.xml b/auth/src/main/res/values-ko/strings.xml index 53509f24f..b0a000b37 100755 --- a/auth/src/main/res/values-ko/strings.xml +++ b/auth/src/main/res/values-ko/strings.xml @@ -87,6 +87,7 @@ 전화번호가 자동으로 확인되었습니다. 코드 재전송 전화번호 인증 + Use a different phone number “%1$s” 버튼을 탭하면 SMS가 발송될 수 있으며, 메시지 및 데이터 요금이 부과될 수 있습니다. 인증 오류 다시 시도 diff --git a/auth/src/main/res/values-ln/strings.xml b/auth/src/main/res/values-ln/strings.xml index b2991e5a4..718924292 100755 --- a/auth/src/main/res/values-ln/strings.xml +++ b/auth/src/main/res/values-ln/strings.xml @@ -87,6 +87,7 @@ Numéro de téléphone validé automatiquement Renvoyer le code Valider le numéro de téléphone + Use a different phone number En appuyant sur “%1$s”, vous déclencherez peut-être l\'envoi d\'un SMS. Des frais de messages et de données peuvent être facturés. En appuyant sur “%1$s”, vous acceptez les %2$s et les %3$s. Vous déclencherez peut-être l\'envoi d\'un SMS. Des frais de messages et de données peuvent être facturés. diff --git a/auth/src/main/res/values-lt/strings.xml b/auth/src/main/res/values-lt/strings.xml index c6e0dc59a..2ea648cb6 100755 --- a/auth/src/main/res/values-lt/strings.xml +++ b/auth/src/main/res/values-lt/strings.xml @@ -87,6 +87,7 @@ Telefono numeris patvirtintas automatiškai Siųsti kodą iš naujo Patvirtinti telefono numerį + Use a different phone number Palietus „%1$s“ gali būti išsiųstas SMS pranešimas. Gali būti taikomi pranešimų ir duomenų įkainiai. Paliesdami „%1$s“ nurodote, kad sutinkate su %2$s ir %3$s. Gali būti išsiųstas SMS pranešimas, taip pat – taikomi pranešimų ir duomenų įkainiai. diff --git a/auth/src/main/res/values-lv/strings.xml b/auth/src/main/res/values-lv/strings.xml index dff9a3f78..1449ac2c0 100755 --- a/auth/src/main/res/values-lv/strings.xml +++ b/auth/src/main/res/values-lv/strings.xml @@ -87,6 +87,7 @@ Tālruņa numurs tika automātiski verificēts Vēlreiz nosūtīt kodu Verificēt tālruņa numuru + Use a different phone number Pieskaroties pogai %1$s, var tikt nosūtīta īsziņa. Var tikt piemērota maksa par ziņojumiem un datu pārsūtīšanu. Pieskaroties pogai “%1$s”, jūs norādāt, ka piekrītat šādiem dokumentiem: %2$s un %3$s. Var tikt nosūtīta īsziņa. Var tikt piemērota maksa par ziņojumiem un datu pārsūtīšanu. diff --git a/auth/src/main/res/values-mo/strings.xml b/auth/src/main/res/values-mo/strings.xml index 16ccd10c1..278e0f228 100755 --- a/auth/src/main/res/values-mo/strings.xml +++ b/auth/src/main/res/values-mo/strings.xml @@ -87,6 +87,7 @@ Numărul de telefon este verificat automat Retrimiteți codul Confirmați numărul de telefon + Use a different phone number Dacă atingeți „%1$s”, poate fi trimis un SMS. Se pot aplica tarife pentru mesaje și date. Dacă atingeți „%1$s”, sunteți de acord cu %2$s și cu %3$s. Poate fi trimis un SMS. Se pot aplica tarife pentru mesaje și date. diff --git a/auth/src/main/res/values-mr/strings.xml b/auth/src/main/res/values-mr/strings.xml index 179b854f2..6a3c84374 100755 --- a/auth/src/main/res/values-mr/strings.xml +++ b/auth/src/main/res/values-mr/strings.xml @@ -87,6 +87,7 @@ फोन नंबरची अपोआप पडताळणी केली आहे कोड पुन्हा पाठवा फोन नंबरची पडताळणी करा + Use a different phone number “%1$s“ वर टॅप केल्याने, एक एसएमएस पाठवला जाऊ शकतो. मेसेज आणि डेटा शुल्क लागू होऊ शकते. “%1$s” वर टॅप करून, तुम्ही सूचित करता की तुम्ही आमचे %2$s आणि %3$s स्वीकारता. एसएमएस पाठवला जाऊ शकतो. मेसेज आणि डेटा दर लागू केले जाऊ शकते. diff --git a/auth/src/main/res/values-ms/strings.xml b/auth/src/main/res/values-ms/strings.xml index 2a65ad8fc..03930d48a 100755 --- a/auth/src/main/res/values-ms/strings.xml +++ b/auth/src/main/res/values-ms/strings.xml @@ -87,6 +87,7 @@ Nombor telefon disahkan secara automatik Hantar Semula Kod Sahkan Nombor Telefon + Use a different phone number Dengan mengetik “%1$s”, SMS akan dihantar. Tertakluk pada kadar mesej & data. Dengan mengetik “%1$s”, anda menyatakan bahawa anda menerima %2$s dan %3$s kami. SMS akan dihantar. Tertakluk pada kadar mesej & data. diff --git a/auth/src/main/res/values-nb/strings.xml b/auth/src/main/res/values-nb/strings.xml index 0cb1e2775..9d690a5f8 100755 --- a/auth/src/main/res/values-nb/strings.xml +++ b/auth/src/main/res/values-nb/strings.xml @@ -87,6 +87,7 @@ Telefonnummeret ble bekreftet automatisk Send koden på nytt Bekreft telefonnummeret + Use a different phone number Når du trykker på «%1$s», kan det bli sendt en SMS. Kostnader for meldinger og datatrafikk kan påløpe. Ved å trykke på «%1$s» godtar du %2$s og %3$s våre. Du kan bli tilsendt en SMS. Kostnader for meldinger og datatrafikk kan påløpe. Godkjenningsfeil diff --git a/auth/src/main/res/values-nl/strings.xml b/auth/src/main/res/values-nl/strings.xml index f51181de3..be8d2b21b 100755 --- a/auth/src/main/res/values-nl/strings.xml +++ b/auth/src/main/res/values-nl/strings.xml @@ -87,6 +87,7 @@ Telefoonnummer is automatisch geverifieerd Code opnieuw verzenden Telefoonnummer verifiëren + Use a different phone number Als u op “%1$s” tikt, ontvangt u mogelijk een sms. Er kunnen sms- en datakosten in rekening worden gebracht. Als u op "%1$s" tikt, geeft u aan dat u onze %2$s en ons %3$s accepteert. Mogelijk ontvangt u een sms. Er kunnen sms- en datakosten in rekening worden gebracht. Authenticatiefout diff --git a/auth/src/main/res/values-no/strings.xml b/auth/src/main/res/values-no/strings.xml index 76e979935..b20606d53 100755 --- a/auth/src/main/res/values-no/strings.xml +++ b/auth/src/main/res/values-no/strings.xml @@ -87,6 +87,7 @@ Telefonnummeret ble bekreftet automatisk Send koden på nytt Bekreft telefonnummeret + Use a different phone number Når du trykker på «%1$s», kan det bli sendt en SMS. Kostnader for meldinger og datatrafikk kan påløpe. Ved å trykke på «%1$s» godtar du %2$s og %3$s våre. Du kan bli tilsendt en SMS. Kostnader for meldinger og datatrafikk kan påløpe. diff --git a/auth/src/main/res/values-pl/strings.xml b/auth/src/main/res/values-pl/strings.xml index 4dd79cc1a..664c11127 100755 --- a/auth/src/main/res/values-pl/strings.xml +++ b/auth/src/main/res/values-pl/strings.xml @@ -87,6 +87,7 @@ Numer telefonu został automatycznie zweryfikowany Wyślij kod ponownie Zweryfikuj numer telefonu + Use a different phone number Gdy klikniesz „%1$s”, może zostać wysłany SMS. Może to skutkować pobraniem opłaty za przesłanie wiadomości i danych. Klikając „%1$s", potwierdzasz, że akceptujesz te dokumenty: %2$s i %3$s. Może zostać wysłany SMS. Może to skutkować pobraniem opłat za przesłanie wiadomości i danych. Błąd uwierzytelniania diff --git a/auth/src/main/res/values-pt-rBR/strings.xml b/auth/src/main/res/values-pt-rBR/strings.xml index 70709794f..eee868828 100755 --- a/auth/src/main/res/values-pt-rBR/strings.xml +++ b/auth/src/main/res/values-pt-rBR/strings.xml @@ -87,6 +87,7 @@ O número de telefone foi verificado automaticamente Reenviar código Confirmar número de telefone + Use a different phone number Se você tocar em “%1$s”, um SMS poderá ser enviado e tarifas de mensagens e de dados serão cobradas. Ao tocar em “%1$s”, você concorda com nossos %2$s e a %3$s. Um SMS poderá ser enviado e tarifas de mensagens e de dados poderão ser cobradas. diff --git a/auth/src/main/res/values-pt-rPT/strings.xml b/auth/src/main/res/values-pt-rPT/strings.xml index b8592da53..c3da239ea 100755 --- a/auth/src/main/res/values-pt-rPT/strings.xml +++ b/auth/src/main/res/values-pt-rPT/strings.xml @@ -87,6 +87,7 @@ Número de telefone verificado automaticamente Reenviar código Validar número de telefone + Use a different phone number Ao tocar em “%1$s”, pode gerar o envio de uma SMS. Podem aplicar-se tarifas de mensagens e dados. Ao tocar em “%1$s”, indica que aceita os %2$s e a %3$s. Pode gerar o envio de uma SMS. Podem aplicar-se tarifas de dados e de mensagens. diff --git a/auth/src/main/res/values-pt/strings.xml b/auth/src/main/res/values-pt/strings.xml index 9d312c557..7febe8d88 100755 --- a/auth/src/main/res/values-pt/strings.xml +++ b/auth/src/main/res/values-pt/strings.xml @@ -87,6 +87,7 @@ O número de telefone foi verificado automaticamente Reenviar código Confirmar número de telefone + Use a different phone number Se você tocar em “%1$s”, um SMS poderá ser enviado e tarifas de mensagens e de dados serão cobradas. Ao tocar em "%1$s", você concorda com nossos %2$s e a %3$s. Um SMS poderá ser enviado e tarifas de mensagens e de dados poderão ser cobradas. Erro de autenticação diff --git a/auth/src/main/res/values-ro/strings.xml b/auth/src/main/res/values-ro/strings.xml index a956eb95a..99dd4f10d 100755 --- a/auth/src/main/res/values-ro/strings.xml +++ b/auth/src/main/res/values-ro/strings.xml @@ -87,6 +87,7 @@ Numărul de telefon este verificat automat Retrimiteți codul Confirmați numărul de telefon + Use a different phone number Dacă atingeți „%1$s”, poate fi trimis un SMS. Se pot aplica tarife pentru mesaje și date. Dacă atingeți „%1$s", sunteți de acord cu %2$s și cu %3$s. Poate fi trimis un SMS. Se pot aplica tarife pentru mesaje și date. Eroare de autentificare diff --git a/auth/src/main/res/values-ru/strings.xml b/auth/src/main/res/values-ru/strings.xml index 26576e9d8..5e38eb081 100755 --- a/auth/src/main/res/values-ru/strings.xml +++ b/auth/src/main/res/values-ru/strings.xml @@ -87,6 +87,7 @@ Номер телефона был подтвержден автоматически Отправить код ещё раз Подтвердить номер телефона + Use a different phone number Нажимая кнопку “%1$s”, вы соглашаетесь получить SMS. За его отправку и обмен данными может взиматься плата. Нажимая кнопку "%1$s", вы принимаете %2$s и %3$s, а также соглашаетесь получить SMS. За его отправку и обмен данными может взиматься плата. Ошибка аутентификации diff --git a/auth/src/main/res/values-sk/strings.xml b/auth/src/main/res/values-sk/strings.xml index ec5c4ecab..14ec23c21 100755 --- a/auth/src/main/res/values-sk/strings.xml +++ b/auth/src/main/res/values-sk/strings.xml @@ -87,6 +87,7 @@ Telefónne číslo bolo automaticky overené Znova odoslať kód Overiť telefónne číslo + Use a different phone number Klepnutím na tlačidlo %1$s možno odoslať SMS. Môžu sa účtovať poplatky za správy a dáta. Klepnutím na tlačidlo %1$s vyjadrujete súhlas s dokumentmi %2$s a %3$s. Môže byť odoslaná SMS a môžu sa účtovať poplatky za správy a dáta. Chyba overenia diff --git a/auth/src/main/res/values-sl/strings.xml b/auth/src/main/res/values-sl/strings.xml index d8773274e..ef84b5ae6 100755 --- a/auth/src/main/res/values-sl/strings.xml +++ b/auth/src/main/res/values-sl/strings.xml @@ -87,6 +87,7 @@ Telefonska številka je bila samodejno preverjena Ponovno pošlji kodo Preverjanje telefonske številke + Use a different phone number Če se dotaknete možnosti »%1$s«, bo morda poslano sporočilo SMS. Pošiljanje sporočila in prenos podatkov boste morda morali plačati. Če se dotaknete možnosti »%1$s«, potrjujete, da se strinjate z dokumentoma %2$s in %3$s. Morda bo poslano sporočilo SMS. Pošiljanje sporočila in prenos podatkov boste morda morali plačati. diff --git a/auth/src/main/res/values-sr/strings.xml b/auth/src/main/res/values-sr/strings.xml index db29b6630..fd87c0f6c 100755 --- a/auth/src/main/res/values-sr/strings.xml +++ b/auth/src/main/res/values-sr/strings.xml @@ -87,6 +87,7 @@ Број телефона је аутоматски верификован Поново пошаљи кôд Верификуј број телефона + Use a different phone number Ако додирнете „%1$s“, можда ћете послати SMS. Могу да вам буду наплаћени трошкови слања поруке и преноса података. Ако додирнете „%1$s“, потврђујете да прихватате документе %2$s и %3$s. Можда ћете послати SMS. Могу да вам буду наплаћени трошкови слања поруке и преноса података. diff --git a/auth/src/main/res/values-sv/strings.xml b/auth/src/main/res/values-sv/strings.xml index 57a8ec314..d95ebe57d 100755 --- a/auth/src/main/res/values-sv/strings.xml +++ b/auth/src/main/res/values-sv/strings.xml @@ -87,6 +87,7 @@ Telefonnumret verifierades automatiskt Skicka koden igen Verifiera telefonnummer + Use a different phone number Genom att trycka på %1$s skickas ett sms. Meddelande- och dataavgifter kan tillkomma. Genom att trycka på %1$s godkänner du våra %2$s och vår %3$s. Ett sms kan skickas. Meddelande- och dataavgifter kan tillkomma. Autentiseringsfel diff --git a/auth/src/main/res/values-ta/strings.xml b/auth/src/main/res/values-ta/strings.xml index 02d7f382e..ca01da64d 100755 --- a/auth/src/main/res/values-ta/strings.xml +++ b/auth/src/main/res/values-ta/strings.xml @@ -87,6 +87,7 @@ ஃபோன் எண் தானாகவே சரிபார்க்கப்பட்டது குறியீட்டை மீண்டும் அனுப்பு ஃபோன் எண்ணைச் சரிபார் + Use a different phone number “%1$s” என்பதைத் தட்டுவதன் மூலம், SMS அனுப்பப்படலாம். செய்தி மற்றும் தரவுக் கட்டணங்கள் விதிக்கப்படலாம். “%1$s” என்பதைத் தட்டுவதன் மூலம், எங்கள் %2$s மற்றும் %3$sஐ ஏற்பதாகக் குறிப்பிடுகிறீர்கள். SMS அனுப்பப்படலாம். செய்தி மற்றும் தரவுக் கட்டணங்கள் விதிக்கப்படலாம். diff --git a/auth/src/main/res/values-th/strings.xml b/auth/src/main/res/values-th/strings.xml index 611a3bc22..650ecb602 100755 --- a/auth/src/main/res/values-th/strings.xml +++ b/auth/src/main/res/values-th/strings.xml @@ -87,6 +87,7 @@ ยืนยันหมายเลขโทรศัพท์โดยอัตโนมัติแล้ว ส่งรหัสอีกครั้ง ยืนยันหมายเลขโทรศัพท์ + Use a different phone number เมื่อคุณแตะ “%1$s” ระบบจะส่ง SMS ให้คุณ อาจมีค่าบริการรับส่งข้อความและค่าบริการอินเทอร์เน็ต การแตะ “%1$s” แสดงว่าคุณยอมรับ %2$s และ %3$s ระบบจะส่ง SMS ให้คุณ อาจมีค่าบริการรับส่งข้อความและค่าบริการอินเทอร์เน็ต diff --git a/auth/src/main/res/values-tl/strings.xml b/auth/src/main/res/values-tl/strings.xml index f83fbe549..d57c8cedf 100755 --- a/auth/src/main/res/values-tl/strings.xml +++ b/auth/src/main/res/values-tl/strings.xml @@ -87,6 +87,7 @@ Awtomatikong na-verify ang numero ng telepono Ipadala Muli ang Code I-verify ang Numero ng Telepono + Use a different phone number Sa pag-tap sa “%1$s,“ maaaring magpadala ng SMS. Maaaring ipatupad ang mga rate ng pagmemensahe at data. Sa pag-tap sa “%1$s”, ipinababatid mo na tinatanggap mo ang aming %2$s at %3$s. Maaaring magpadala ng SMS. Maaaring ipatupad ang mga rate ng pagmemensahe at data. Error sa Pagpapatotoo diff --git a/auth/src/main/res/values-tr/strings.xml b/auth/src/main/res/values-tr/strings.xml index 50feaa6d0..71d261569 100755 --- a/auth/src/main/res/values-tr/strings.xml +++ b/auth/src/main/res/values-tr/strings.xml @@ -87,6 +87,7 @@ Telefon numarası otomatik olarak doğrulandı Kodu Yeniden Gönder Telefon Numarasını Doğrula + Use a different phone number “%1$s” öğesine dokunarak SMS gönderilebilir. Mesaj ve veri ücretleri uygulanabilir. “%1$s” öğesine dokunarak %2$s ve %3$s hükümlerimizi kabul ettiğinizi bildirirsiniz. SMS gönderilebilir. Mesaj ve veri ücretleri uygulanabilir. diff --git a/auth/src/main/res/values-uk/strings.xml b/auth/src/main/res/values-uk/strings.xml index e7d4cb54d..120d09fac 100755 --- a/auth/src/main/res/values-uk/strings.xml +++ b/auth/src/main/res/values-uk/strings.xml @@ -87,6 +87,7 @@ Номер телефону підтверджено автоматично Повторно надіслати код Підтвердити номер телефону + Use a different phone number Коли ви торкнетесь опції “%1$s”, вам може надійти SMS-повідомлення. За SMS і використання трафіку може стягуватися плата. Торкаючись кнопки “%1$s”, ви приймаєте такі документи: %2$s і %3$s. Вам може надійти SMS-повідомлення. За SMS і використання трафіку може стягуватися плата. diff --git a/auth/src/main/res/values-ur/strings.xml b/auth/src/main/res/values-ur/strings.xml index 35f8e7c61..c82d67f82 100755 --- a/auth/src/main/res/values-ur/strings.xml +++ b/auth/src/main/res/values-ur/strings.xml @@ -87,6 +87,7 @@ فون نمبر کی خودکار طور پر توثیق ہو گئی کوڈ دوبارہ بھیجیں فون نمبر کی توثیق کریں + Use a different phone number %1$s پر تھپتھپانے سے، ایک SMS بھیجا جا سکتا ہے۔ پیغام اور ڈیٹا کی شرحوں کا اطلاق ہو سکتا ہے۔ “%1$s” کو تھپتھپا کر، آپ نشاندہی کر رہے ہیں کہ آپ ہماری %2$s اور %3$s کو قبول کرتے ہیں۔ ایک SMS بھیجا جا سکتا ہے۔ پیغام اور ڈیٹا نرخ لاگو ہو سکتے ہیں۔ diff --git a/auth/src/main/res/values-vi/strings.xml b/auth/src/main/res/values-vi/strings.xml index 24df395c6..50738036f 100755 --- a/auth/src/main/res/values-vi/strings.xml +++ b/auth/src/main/res/values-vi/strings.xml @@ -87,6 +87,7 @@ Đã tự động xác minh số điện thoại Gửi lại mã Xác minh số điện thoại + Use a different phone number Bằng cách nhấn vào “%1$s”, bạn có thể nhận được một tin nhắn SMS. Cước tin nhắn và dữ liệu có thể áp dụng. Bằng cách nhấn vào “%1$s”, bạn cho biết rằng bạn chấp nhận %2$s và %3$s của chúng tôi. Bạn có thể nhận được một tin nhắn SMS. Cước tin nhắn và dữ liệu có thể áp dụng. diff --git a/auth/src/main/res/values-zh-rCN/strings.xml b/auth/src/main/res/values-zh-rCN/strings.xml index bb727ea18..5a62bc5de 100755 --- a/auth/src/main/res/values-zh-rCN/strings.xml +++ b/auth/src/main/res/values-zh-rCN/strings.xml @@ -87,6 +87,7 @@ 电话号码已自动验证 重新发送验证码 验证电话号码 + Use a different phone number 您点按“%1$s”后,系统会向您发送一条短信。这可能会产生短信费用和上网流量费。 点按“%1$s”即表示您接受我们的%2$s和%3$s。系统会向您发送一条短信。这可能会产生短信费用和上网流量费。 diff --git a/auth/src/main/res/values-zh-rHK/strings.xml b/auth/src/main/res/values-zh-rHK/strings.xml index 6f9a2474a..ec81ac859 100755 --- a/auth/src/main/res/values-zh-rHK/strings.xml +++ b/auth/src/main/res/values-zh-rHK/strings.xml @@ -87,6 +87,7 @@ 已自動驗證電話號碼 重新傳送驗證碼 驗證電話號碼 + Use a different phone number 輕觸 [%1$s] 後,系統將會傳送一封簡訊。您可能需支付簡訊和數據傳輸費用。 輕觸 [%1$s] 即表示您同意接受我們的《%2$s》和《%3$s》。系統將會傳送簡訊給您,不過您可能需要支付簡訊和數據傳輸費用。 diff --git a/auth/src/main/res/values-zh-rTW/strings.xml b/auth/src/main/res/values-zh-rTW/strings.xml index d9425dbde..6e9efae10 100755 --- a/auth/src/main/res/values-zh-rTW/strings.xml +++ b/auth/src/main/res/values-zh-rTW/strings.xml @@ -87,6 +87,7 @@ 已自動驗證電話號碼 重新傳送驗證碼 驗證電話號碼 + Use a different phone number 輕觸 [%1$s] 後,系統將會傳送一封簡訊。您可能需支付簡訊和數據傳輸費用。 輕觸 [%1$s] 即表示您同意接受我們的《%2$s》和《%3$s》。系統將會傳送簡訊給您,不過您可能需要支付簡訊和數據傳輸費用。 diff --git a/auth/src/main/res/values-zh/strings.xml b/auth/src/main/res/values-zh/strings.xml index 69b678aa7..0be8a8910 100755 --- a/auth/src/main/res/values-zh/strings.xml +++ b/auth/src/main/res/values-zh/strings.xml @@ -87,6 +87,7 @@ 电话号码已自动验证 重新发送验证码 验证电话号码 + Use a different phone number 您点按“%1$s”后,系统会向您发送一条短信。这可能会产生短信费用和上网流量费。 点按"%1$s"即表示您接受我们的%2$s和%3$s。系统会向您发送一条短信。这可能会产生短信费用和上网流量费。 身份验证错误 diff --git a/auth/src/main/res/values/strings.xml b/auth/src/main/res/values/strings.xml index 511a8d7a8..28740ba10 100644 --- a/auth/src/main/res/values/strings.xml +++ b/auth/src/main/res/values/strings.xml @@ -140,7 +140,8 @@ Phone number automatically verified Resend Code Verify Phone Number - By tapping “%1$s”, an SMS may be sent. Message & data rates may apply. + Use a different phone number + By tapping "%1$s", an SMS may be sent. Message & data rates may apply. By tapping "%1$s", you are indicating that you accept our %2$s and %3$s. An SMS may be sent. Message & data rates may apply. diff --git a/composeapp/src/main/java/com/firebase/composeapp/MainActivity.kt b/composeapp/src/main/java/com/firebase/composeapp/MainActivity.kt index a4afb5fdf..506340bb1 100644 --- a/composeapp/src/main/java/com/firebase/composeapp/MainActivity.kt +++ b/composeapp/src/main/java/com/firebase/composeapp/MainActivity.kt @@ -8,7 +8,8 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.ui.Modifier import androidx.lifecycle.lifecycleScope -import com.firebase.composeapp.ui.screens.MainScreen +import com.firebase.composeapp.ui.screens.EmailAuthMain +import com.firebase.composeapp.ui.screens.PhoneAuthMain import com.firebase.ui.auth.compose.FirebaseAuthUI import com.firebase.ui.auth.compose.configuration.PasswordRule import com.firebase.ui.auth.compose.configuration.authUIConfiguration @@ -29,6 +30,45 @@ class MainActivity : ComponentActivity() { val authUI = FirebaseAuthUI.getInstance() authUI.auth.useEmulator("10.0.2.2", 9099) + //initializeEmailAuth(authUI) + initializePhoneAuth(authUI) + } + + fun initializePhoneAuth(authUI: FirebaseAuthUI) { + val provider = AuthProvider.Phone( + defaultNumber = null, + defaultCountryCode = null, + allowedCountries = emptyList(), + smsCodeLength = 6, + timeout = 60L, + isInstantVerificationEnabled = true + ) + + val configuration = authUIConfiguration { + context = applicationContext + providers { provider(provider) } + tosUrl = "https://policies.google.com/terms?hl=en-NG&fg=1" + privacyPolicyUrl = "https://policies.google.com/privacy?hl=en-NG&fg=1" + } + + setContent { + AuthUITheme { + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background + ) { + PhoneAuthMain( + context = applicationContext, + configuration = configuration, + authUI = authUI, + provider = provider + ) + } + } + } + } + + fun initializeEmailAuth(authUI: FirebaseAuthUI) { // Check if this is an email link sign-in flow val emailLink = intent.getStringExtra( EmailSignInLinkHandlerActivity.EXTRA_EMAIL_LINK @@ -90,7 +130,7 @@ class MainActivity : ComponentActivity() { modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background ) { - MainScreen( + EmailAuthMain( context = applicationContext, configuration = configuration, authUI = authUI, diff --git a/composeapp/src/main/java/com/firebase/composeapp/ui/screens/MainScreen.kt b/composeapp/src/main/java/com/firebase/composeapp/ui/screens/EmailAuthMain.kt similarity index 97% rename from composeapp/src/main/java/com/firebase/composeapp/ui/screens/MainScreen.kt rename to composeapp/src/main/java/com/firebase/composeapp/ui/screens/EmailAuthMain.kt index 4e1aeaf66..299f1052b 100644 --- a/composeapp/src/main/java/com/firebase/composeapp/ui/screens/MainScreen.kt +++ b/composeapp/src/main/java/com/firebase/composeapp/ui/screens/EmailAuthMain.kt @@ -1,7 +1,6 @@ package com.firebase.composeapp.ui.screens import android.content.Context -import android.util.Log import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer @@ -10,11 +9,8 @@ import androidx.compose.foundation.layout.height import androidx.compose.material3.Button import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -32,7 +28,7 @@ import com.firebase.ui.auth.compose.ui.screens.SignUpUI import kotlinx.coroutines.launch @Composable -fun MainScreen( +fun EmailAuthMain( context: Context, configuration: AuthUIConfiguration, authUI: FirebaseAuthUI, diff --git a/composeapp/src/main/java/com/firebase/composeapp/ui/screens/PhoneAuthMain.kt b/composeapp/src/main/java/com/firebase/composeapp/ui/screens/PhoneAuthMain.kt new file mode 100644 index 000000000..06f2371da --- /dev/null +++ b/composeapp/src/main/java/com/firebase/composeapp/ui/screens/PhoneAuthMain.kt @@ -0,0 +1,131 @@ +package com.firebase.composeapp.ui.screens + +import android.content.Context +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.material3.Button +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import com.firebase.ui.auth.compose.AuthState +import com.firebase.ui.auth.compose.FirebaseAuthUI +import com.firebase.ui.auth.compose.configuration.AuthUIConfiguration +import com.firebase.ui.auth.compose.configuration.auth_provider.AuthProvider +import com.firebase.ui.auth.compose.ui.screens.phone.EnterPhoneNumberUI +import com.firebase.ui.auth.compose.ui.screens.phone.EnterVerificationCodeUI +import com.firebase.ui.auth.compose.ui.screens.phone.PhoneAuthScreen +import com.firebase.ui.auth.compose.ui.screens.phone.PhoneAuthStep +import kotlinx.coroutines.launch + +@Composable +fun PhoneAuthMain( + context: Context, + configuration: AuthUIConfiguration, + authUI: FirebaseAuthUI, + provider: AuthProvider.Phone, +) { + val coroutineScope = rememberCoroutineScope() + val authState by authUI.authStateFlow().collectAsState(AuthState.Idle) + + when (authState) { + is AuthState.Success -> { + Column( + modifier = Modifier + .fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text( + "Authenticated User - (Success): ${authUI.getCurrentUser()?.phoneNumber}", + textAlign = TextAlign.Center + ) + Spacer(modifier = Modifier.height(8.dp)) + Button( + onClick = { + coroutineScope.launch { + authUI.signOut(context) + } + } + ) { + Text("Sign Out") + } + } + } + + is AuthState.RequiresEmailVerification -> { + Column( + modifier = Modifier + .fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text( + "Authenticated User - " + + "(RequiresEmailVerification): " + + "${(authState as AuthState.RequiresEmailVerification).user.email}", + textAlign = TextAlign.Center + ) + Spacer(modifier = Modifier.height(8.dp)) + Button( + onClick = { + coroutineScope.launch { + authUI.signOut(context) + } + } + ) { + Text("Sign Out") + } + } + } + + else -> { + PhoneAuthScreen( + context = context, + configuration = configuration, + authUI = authUI, + onSuccess = { result -> }, + onError = { exception -> }, + onCancel = { }, + ) { state -> + when (state.step) { + PhoneAuthStep.EnterPhoneNumber -> { + EnterPhoneNumberUI( + configuration = configuration, + isLoading = state.isLoading, + phoneNumber = state.phoneNumber, + useInstantVerificationEnabled = state.useInstantVerificationEnabled, + onUseInstantVerificationChange = state.onUseInstantVerificationChange, + selectedCountry = state.selectedCountry, + onPhoneNumberChange = state.onPhoneNumberChange, + onCountrySelected = state.onCountrySelected, + onSendCodeClick = state.onSendCodeClick, + ) + } + + PhoneAuthStep.EnterVerificationCode -> { + EnterVerificationCodeUI( + configuration = configuration, + isLoading = state.isLoading, + verificationCode = state.verificationCode, + fullPhoneNumber = state.fullPhoneNumber, + resendTimer = state.resendTimer, + onVerificationCodeChange = state.onVerificationCodeChange, + onVerifyCodeClick = state.onVerifyCodeClick, + onResendCodeClick = state.onResendCodeClick, + onChangeNumberClick = state.onChangeNumberClick, + ) + } + } + } + } + } +} \ No newline at end of file From 8d8320cc47d82e6bb226167bdae8642f0dd19f08 Mon Sep 17 00:00:00 2001 From: Ademola Fadumo Date: Wed, 15 Oct 2025 11:42:34 +0100 Subject: [PATCH 03/12] wip: e2e tests for PhoneAuthScreen --- auth/build.gradle.kts | 10 +- .../auth_provider/AuthProvider.kt | 29 +- .../PhoneAuthProvider+FirebaseAuthUI.kt | 3 + .../string_provider/AuthUIStringProvider.kt | 2 +- .../DefaultAuthUIStringProvider.kt | 4 +- .../screens/phone/EnterVerificationCodeUI.kt | 5 +- .../ui/screens/phone/PhoneAuthScreen.kt | 7 + auth/src/main/res/values/strings.xml | 2 +- buildSrc/src/main/kotlin/Config.kt | 17 +- composeapp/build.gradle.kts | 7 + .../com/firebase/composeapp/MainActivity.kt | 212 +++++--- .../composeapp/ui/screens/EmailAuthMain.kt | 1 - .../composeapp/ui/screens/PhoneAuthMain.kt | 1 - .../ui/auth/compose/testutil/EmulatorApi.kt | 52 ++ .../ui/auth/compose/testutil/HttpClient.kt | 40 ++ .../compose/ui/screens/EmailAuthScreenTest.kt | 84 +-- .../compose/ui/screens/PhoneAuthScreenTest.kt | 501 ++++++++++++++++++ 17 files changed, 798 insertions(+), 179 deletions(-) create mode 100644 e2eTest/src/test/java/com/firebase/ui/auth/compose/testutil/EmulatorApi.kt create mode 100644 e2eTest/src/test/java/com/firebase/ui/auth/compose/testutil/HttpClient.kt create mode 100644 e2eTest/src/test/java/com/firebase/ui/auth/compose/ui/screens/PhoneAuthScreenTest.kt diff --git a/auth/build.gradle.kts b/auth/build.gradle.kts index 357a846dd..77e86eb5f 100644 --- a/auth/build.gradle.kts +++ b/auth/build.gradle.kts @@ -1,5 +1,3 @@ -import com.android.build.gradle.internal.dsl.TestOptions - plugins { id("com.android.library") id("com.vanniktech.maven.publish") @@ -13,7 +11,7 @@ android { defaultConfig { minSdk = Config.SdkVersions.min - targetSdk =Config.SdkVersions.target + targetSdk = Config.SdkVersions.target buildConfigField("String", "VERSION_NAME", "\"${Config.version}\"") @@ -27,8 +25,8 @@ android { consumerProguardFiles("auth-proguard.pro") } } - - compileOptions { + + compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } @@ -82,8 +80,8 @@ dependencies { implementation(Config.Libs.Androidx.Compose.tooling) implementation(Config.Libs.Androidx.Compose.toolingPreview) implementation(Config.Libs.Androidx.Compose.activityCompose) - implementation(Config.Libs.Androidx.materialDesign) implementation(Config.Libs.Androidx.activity) + implementation(Config.Libs.Androidx.materialDesign) implementation(Config.Libs.Androidx.Compose.materialIconsExtended) implementation(Config.Libs.Androidx.datastorePreferences) // The new activity result APIs force us to include Fragment 1.3.0 diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/auth_provider/AuthProvider.kt b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/auth_provider/AuthProvider.kt index d0cc3be98..3a612e9bb 100644 --- a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/auth_provider/AuthProvider.kt +++ b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/auth_provider/AuthProvider.kt @@ -14,6 +14,7 @@ package com.firebase.ui.auth.compose.configuration.auth_provider +import android.app.Activity import android.content.Context import android.net.Uri import android.util.Log @@ -44,6 +45,7 @@ import com.google.firebase.auth.TwitterAuthProvider import com.google.firebase.auth.UserProfileChangeRequest import com.google.firebase.auth.actionCodeSettings import kotlinx.coroutines.tasks.await +import kotlinx.serialization.Serializable import java.util.concurrent.TimeUnit import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException @@ -404,6 +406,7 @@ abstract class AuthProvider(open val providerId: String) { */ internal suspend fun verifyPhoneNumberAwait( auth: FirebaseAuth, + activity: Activity?, phoneNumber: String, multiFactorSession: MultiFactorSession? = null, forceResendingToken: PhoneAuthProvider.ForceResendingToken?, @@ -411,6 +414,7 @@ abstract class AuthProvider(open val providerId: String) { ): VerifyPhoneNumberResult { return verifier.verifyPhoneNumber( auth, + activity, phoneNumber, timeout, forceResendingToken, @@ -425,11 +429,12 @@ abstract class AuthProvider(open val providerId: String) { internal interface Verifier { suspend fun verifyPhoneNumber( auth: FirebaseAuth, + activity: Activity?, phoneNumber: String, timeout: Long, forceResendingToken: PhoneAuthProvider.ForceResendingToken?, multiFactorSession: MultiFactorSession?, - isInstantVerificationEnabled: Boolean + isInstantVerificationEnabled: Boolean, ): VerifyPhoneNumberResult } @@ -439,18 +444,20 @@ abstract class AuthProvider(open val providerId: String) { internal class DefaultVerifier : Verifier { override suspend fun verifyPhoneNumber( auth: FirebaseAuth, + activity: Activity?, phoneNumber: String, timeout: Long, forceResendingToken: PhoneAuthProvider.ForceResendingToken?, multiFactorSession: MultiFactorSession?, - isInstantVerificationEnabled: Boolean + isInstantVerificationEnabled: Boolean, ): VerifyPhoneNumberResult { return suspendCoroutine { continuation -> val options = PhoneAuthOptions.newBuilder(auth) .setPhoneNumber(phoneNumber) .requireSmsValidation(!isInstantVerificationEnabled) .setTimeout(timeout, TimeUnit.SECONDS) - .setCallbacks(object : PhoneAuthProvider.OnVerificationStateChangedCallbacks() { + .setCallbacks(object : + PhoneAuthProvider.OnVerificationStateChangedCallbacks() { override fun onVerificationCompleted(credential: PhoneAuthCredential) { continuation.resume(VerifyPhoneNumberResult.AutoVerified(credential)) } @@ -471,11 +478,14 @@ abstract class AuthProvider(open val providerId: String) { ) } }) - if (forceResendingToken != null) { - options.setForceResendingToken(forceResendingToken) + activity?.let { + options.setActivity(it) } - if (multiFactorSession != null) { - options.setMultiFactorSession(multiFactorSession) + forceResendingToken?.let { + options.setForceResendingToken(it) + } + multiFactorSession?.let { + options.setMultiFactorSession(it) } PhoneAuthProvider.verifyPhoneNumber(options.build()) } @@ -495,7 +505,10 @@ abstract class AuthProvider(open val providerId: String) { * @suppress */ internal class DefaultCredentialProvider : CredentialProvider { - override fun getCredential(verificationId: String, smsCode: String): PhoneAuthCredential { + override fun getCredential( + verificationId: String, + smsCode: String, + ): PhoneAuthCredential { return PhoneAuthProvider.getCredential(verificationId, smsCode) } } diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/auth_provider/PhoneAuthProvider+FirebaseAuthUI.kt b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/auth_provider/PhoneAuthProvider+FirebaseAuthUI.kt index 0eadb2189..3c17d113e 100644 --- a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/auth_provider/PhoneAuthProvider+FirebaseAuthUI.kt +++ b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/auth_provider/PhoneAuthProvider+FirebaseAuthUI.kt @@ -1,5 +1,6 @@ package com.firebase.ui.auth.compose.configuration.auth_provider +import android.app.Activity import com.firebase.ui.auth.compose.AuthException import com.firebase.ui.auth.compose.AuthState import com.firebase.ui.auth.compose.FirebaseAuthUI @@ -101,6 +102,7 @@ import kotlinx.coroutines.CancellationException */ internal suspend fun FirebaseAuthUI.verifyPhoneNumber( provider: AuthProvider.Phone, + activity: Activity?, phoneNumber: String, multiFactorSession: MultiFactorSession? = null, forceResendingToken: PhoneAuthProvider.ForceResendingToken? = null, @@ -110,6 +112,7 @@ internal suspend fun FirebaseAuthUI.verifyPhoneNumber( updateAuthState(AuthState.Loading("Verifying phone number...")) val result = provider.verifyPhoneNumberAwait( auth = auth, + activity = activity, phoneNumber = phoneNumber, multiFactorSession = multiFactorSession, forceResendingToken = forceResendingToken, diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/string_provider/AuthUIStringProvider.kt b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/string_provider/AuthUIStringProvider.kt index c2fe71fe5..29d13c2de 100644 --- a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/string_provider/AuthUIStringProvider.kt +++ b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/string_provider/AuthUIStringProvider.kt @@ -175,7 +175,7 @@ interface AuthUIStringProvider { val resendCode: String /** Resend code with timer */ - fun resendCodeTimer(seconds: Int): String + fun resendCodeTimer(timeFormatted: String): String /** Verifying progress text */ val verifying: String diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/string_provider/DefaultAuthUIStringProvider.kt b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/string_provider/DefaultAuthUIStringProvider.kt index f6f00d1de..b3a065444 100644 --- a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/string_provider/DefaultAuthUIStringProvider.kt +++ b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/string_provider/DefaultAuthUIStringProvider.kt @@ -170,8 +170,8 @@ class DefaultAuthUIStringProvider( override val resendCode: String get() = localizedContext.getString(R.string.fui_resend_code) - override fun resendCodeTimer(seconds: Int): String = - localizedContext.getString(R.string.fui_resend_code_in, seconds) + override fun resendCodeTimer(timeFormatted: String): String = + localizedContext.getString(R.string.fui_resend_code_in, timeFormatted) override val verifying: String get() = localizedContext.getString(R.string.fui_verifying) diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/phone/EnterVerificationCodeUI.kt b/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/phone/EnterVerificationCodeUI.kt index 62d2e136d..ddbe66a6e 100644 --- a/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/phone/EnterVerificationCodeUI.kt +++ b/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/phone/EnterVerificationCodeUI.kt @@ -133,7 +133,10 @@ fun EnterVerificationCodeUI( ) { Text( text = if (resendTimer > 0) { - stringProvider.resendCodeTimer(resendTimer) + val minutes = resendTimer / 60 + val seconds = resendTimer % 60 + val timeFormatted = "$minutes:${String.format("%02d", seconds)}" + stringProvider.resendCodeTimer(timeFormatted) } else { stringProvider.resendCode }, diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/phone/PhoneAuthScreen.kt b/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/phone/PhoneAuthScreen.kt index 327e19004..f7928b49c 100644 --- a/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/phone/PhoneAuthScreen.kt +++ b/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/phone/PhoneAuthScreen.kt @@ -14,6 +14,7 @@ package com.firebase.ui.auth.compose.ui.screens.phone +import android.app.Activity import android.content.Context import android.util.Log import androidx.compose.runtime.Composable @@ -26,6 +27,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import com.firebase.ui.auth.compose.AuthException import com.firebase.ui.auth.compose.AuthState import com.firebase.ui.auth.compose.FirebaseAuthUI @@ -42,6 +44,7 @@ import com.google.firebase.auth.AuthResult import com.google.firebase.auth.PhoneAuthProvider import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import androidx.activity.compose.LocalActivity enum class PhoneAuthStep { /** @@ -138,6 +141,7 @@ fun PhoneAuthScreen( modifier: Modifier = Modifier, content: @Composable ((PhoneAuthContentState) -> Unit)? = null, ) { + val activity = LocalActivity.current val provider = configuration.providers.filterIsInstance().first() val stringProvider = DefaultAuthUIStringProvider(context) val coroutineScope = rememberCoroutineScope() @@ -242,6 +246,7 @@ fun PhoneAuthScreen( try { authUI.verifyPhoneNumber( provider = provider, + activity = activity, phoneNumber = fullPhoneNumber, ) } catch (e: Exception) { @@ -274,6 +279,7 @@ fun PhoneAuthScreen( coroutineScope.launch { try { authUI.verifyPhoneNumber( + activity = activity, provider = provider, phoneNumber = fullPhoneNumber, forceResendingToken = forceResendingToken.value, @@ -288,6 +294,7 @@ fun PhoneAuthScreen( resendTimer = resendTimerSeconds.intValue, onChangeNumberClick = { step.value = PhoneAuthStep.EnterPhoneNumber + //phoneNumberValue.value = "" verificationCodeValue.value = "" verificationId.value = null forceResendingToken.value = null diff --git a/auth/src/main/res/values/strings.xml b/auth/src/main/res/values/strings.xml index 28740ba10..8f074b868 100644 --- a/auth/src/main/res/values/strings.xml +++ b/auth/src/main/res/values/strings.xml @@ -129,7 +129,7 @@ Enter your phone number Enter a valid phone number Enter the 6-digit code we sent to - Resend code in 0:%02d + Resend code in %s Verify your phone number Verifying… Wrong code. Try again. diff --git a/buildSrc/src/main/kotlin/Config.kt b/buildSrc/src/main/kotlin/Config.kt index cad7cd0a8..424a9fb0c 100644 --- a/buildSrc/src/main/kotlin/Config.kt +++ b/buildSrc/src/main/kotlin/Config.kt @@ -3,10 +3,11 @@ object Config { val submodules = listOf("auth", "common", "firestore", "database", "storage") const val kotlinVersion = "2.2.0" + const val kotlinSerializationVersion = "1.9.0" object SdkVersions { - const val compile = 35 - const val target = 35 + const val compile = 36 + const val target = 36 const val min = 23 } @@ -45,7 +46,7 @@ object Config { const val datastorePreferences = "androidx.datastore:datastore-preferences:1.1.1" const val credentials = "androidx.credentials:credentials:1.3.0" object Compose { - const val bom = "androidx.compose:compose-bom:2025.08.00" + const val bom = "androidx.compose:compose-bom:2025.10.00" const val ui = "androidx.compose.ui:ui" const val uiGraphics = "androidx.compose.ui:ui-graphics" const val toolingPreview = "androidx.compose.ui:ui-tooling-preview" @@ -53,8 +54,16 @@ object Config { const val foundation = "androidx.compose.foundation:foundation" const val material3 = "androidx.compose.material3:material3" const val materialIconsExtended = "androidx.compose.material:material-icons-extended" - const val activityCompose = "androidx.activity:activity-compose:1.9.0" + const val activityCompose = "androidx.activity:activity-compose:1.11.0" } + + object Navigation { + const val nav3Runtime = "androidx.navigation3:navigation3-runtime:1.0.0-alpha08" + const val nav3UI = "androidx.navigation3:navigation3-ui:1.0.0-alpha08" + const val lifecycleViewmodelNav3 = "androidx.lifecycle:lifecycle-viewmodel-compose:1.0.0-alpha04" + } + + const val kotlinxSerialization = "org.jetbrains.kotlinx:kotlinx-serialization-core:$kotlinSerializationVersion" } object Firebase { diff --git a/composeapp/build.gradle.kts b/composeapp/build.gradle.kts index 92f555ad7..fd3193475 100644 --- a/composeapp/build.gradle.kts +++ b/composeapp/build.gradle.kts @@ -2,6 +2,7 @@ plugins { id("com.android.application") id("org.jetbrains.kotlin.android") id("org.jetbrains.kotlin.plugin.compose") + id("org.jetbrains.kotlin.plugin.serialization") version Config.kotlinVersion id("com.google.gms.google-services") apply false } @@ -52,6 +53,12 @@ dependencies { implementation(Config.Libs.Androidx.Compose.toolingPreview) implementation(Config.Libs.Androidx.Compose.material3) + // Navigation 3 + implementation(Config.Libs.Androidx.Navigation.nav3Runtime) + implementation(Config.Libs.Androidx.Navigation.nav3UI) + implementation(Config.Libs.Androidx.Navigation.lifecycleViewmodelNav3) + implementation(Config.Libs.Androidx.kotlinxSerialization) + testImplementation(Config.Libs.Test.junit) androidTestImplementation(Config.Libs.Test.junitExt) androidTestImplementation(platform(Config.Libs.Androidx.Compose.bom)) diff --git a/composeapp/src/main/java/com/firebase/composeapp/MainActivity.kt b/composeapp/src/main/java/com/firebase/composeapp/MainActivity.kt index 506340bb1..640ad1389 100644 --- a/composeapp/src/main/java/com/firebase/composeapp/MainActivity.kt +++ b/composeapp/src/main/java/com/firebase/composeapp/MainActivity.kt @@ -1,26 +1,49 @@ package com.firebase.composeapp import android.os.Bundle +import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Modifier -import androidx.lifecycle.lifecycleScope +import androidx.navigation3.runtime.NavEntry +import androidx.navigation3.runtime.NavKey +import androidx.navigation3.runtime.rememberNavBackStack +import androidx.navigation3.ui.NavDisplay import com.firebase.composeapp.ui.screens.EmailAuthMain import com.firebase.composeapp.ui.screens.PhoneAuthMain import com.firebase.ui.auth.compose.FirebaseAuthUI +import com.firebase.ui.auth.compose.configuration.AuthUIConfiguration import com.firebase.ui.auth.compose.configuration.PasswordRule import com.firebase.ui.auth.compose.configuration.authUIConfiguration import com.firebase.ui.auth.compose.configuration.auth_provider.AuthProvider import com.firebase.ui.auth.compose.configuration.auth_provider.signInWithEmailLink +import com.firebase.ui.auth.compose.configuration.theme.AuthUIAsset import com.firebase.ui.auth.compose.configuration.theme.AuthUITheme +import com.firebase.ui.auth.compose.ui.method_picker.AuthMethodPicker import com.firebase.ui.auth.compose.ui.screens.EmailSignInLinkHandlerActivity import com.firebase.ui.auth.compose.util.EmailLinkPersistenceManager import com.google.firebase.FirebaseApp import com.google.firebase.auth.actionCodeSettings -import kotlinx.coroutines.launch +import kotlinx.serialization.Serializable + +@Serializable +sealed class Route : NavKey { + @Serializable + object MethodPicker : Route() + + @Serializable + object EmailAuth : Route() + + @Serializable + object PhoneAuth : Route() +} class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -30,90 +53,134 @@ class MainActivity : ComponentActivity() { val authUI = FirebaseAuthUI.getInstance() authUI.auth.useEmulator("10.0.2.2", 9099) - //initializeEmailAuth(authUI) - initializePhoneAuth(authUI) - } - - fun initializePhoneAuth(authUI: FirebaseAuthUI) { - val provider = AuthProvider.Phone( - defaultNumber = null, - defaultCountryCode = null, - allowedCountries = emptyList(), - smsCodeLength = 6, - timeout = 60L, - isInstantVerificationEnabled = true - ) - val configuration = authUIConfiguration { context = applicationContext - providers { provider(provider) } + providers { + provider( + AuthProvider.Email( + isDisplayNameRequired = true, + isEmailLinkForceSameDeviceEnabled = true, + emailLinkActionCodeSettings = actionCodeSettings { + // The continue URL - where to redirect after email link is clicked + url = "https://temp-test-aa342.firebaseapp.com" + handleCodeInApp = true + setAndroidPackageName( + "com.firebase.composeapp", + true, + null + ) + }, + isNewAccountsAllowed = true, + minimumPasswordLength = 8, + passwordValidationRules = listOf( + PasswordRule.MinimumLength(8), + PasswordRule.RequireLowercase, + PasswordRule.RequireUppercase, + ) + ) + ) + provider( + AuthProvider.Phone( + defaultNumber = null, + defaultCountryCode = null, + allowedCountries = emptyList(), + smsCodeLength = 6, + timeout = 60L, + isInstantVerificationEnabled = true + ) + ) + } tosUrl = "https://policies.google.com/terms?hl=en-NG&fg=1" privacyPolicyUrl = "https://policies.google.com/privacy?hl=en-NG&fg=1" } setContent { + val backStack = rememberNavBackStack(Route.MethodPicker) + AuthUITheme { Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background ) { - PhoneAuthMain( - context = applicationContext, - configuration = configuration, - authUI = authUI, - provider = provider + NavDisplay( + backStack = backStack, + onBack = { + if (backStack.size > 1) { + backStack.removeLastOrNull() + } + }, + entryProvider = { entry -> + val route = entry as Route + when (route) { + is Route.MethodPicker -> NavEntry(entry) { + Scaffold { innerPadding -> + AuthMethodPicker( + modifier = Modifier.padding(innerPadding), + providers = configuration.providers, + logo = AuthUIAsset.Resource(R.drawable.fui_ic_googleg_color_24dp), + termsOfServiceUrl = configuration.tosUrl, + privacyPolicyUrl = configuration.privacyPolicyUrl, + onProviderSelected = { provider -> + Log.d( + "MainActivity", + "Selected Provider: $provider" + ) + when (provider) { + is AuthProvider.Email -> backStack.add(Route.EmailAuth) + is AuthProvider.Phone -> backStack.add(Route.PhoneAuth) + } + }, + ) + } + } + + is Route.EmailAuth -> NavEntry(entry) { + val emailProvider = configuration.providers + .filterIsInstance() + .first() + LaunchEmailAuth(authUI, configuration, emailProvider) + } + + is Route.PhoneAuth -> NavEntry(entry) { + val phoneProvider = configuration.providers + .filterIsInstance() + .first() + LaunchPhoneAuth(authUI, configuration, phoneProvider) + } + } + } ) } } } } - fun initializeEmailAuth(authUI: FirebaseAuthUI) { + @Composable + private fun LaunchEmailAuth( + authUI: FirebaseAuthUI, + configuration: AuthUIConfiguration, + selectedProvider: AuthProvider.Email, + ) { // Check if this is an email link sign-in flow val emailLink = intent.getStringExtra( EmailSignInLinkHandlerActivity.EXTRA_EMAIL_LINK ) - val provider = AuthProvider.Email( - isDisplayNameRequired = true, - isEmailLinkForceSameDeviceEnabled = true, - emailLinkActionCodeSettings = actionCodeSettings { - // The continue URL - where to redirect after email link is clicked - url = "https://temp-test-aa342.firebaseapp.com" - handleCodeInApp = true - setAndroidPackageName( - "com.firebase.composeapp", - true, - null - ) - }, - isNewAccountsAllowed = true, - minimumPasswordLength = 8, - passwordValidationRules = listOf( - PasswordRule.MinimumLength(8), - PasswordRule.RequireLowercase, - PasswordRule.RequireUppercase, - ) - ) - - val configuration = authUIConfiguration { - context = applicationContext - providers { provider(provider) } - tosUrl = "https://policies.google.com/terms?hl=en-NG&fg=1" - privacyPolicyUrl = "https://policies.google.com/privacy?hl=en-NG&fg=1" - } - if (emailLink != null) { - lifecycleScope.launch { + LaunchedEffect(emailLink) { + try { - val emailFromSession = EmailLinkPersistenceManager - .retrieveSessionRecord(applicationContext)?.email + val emailFromSession = + EmailLinkPersistenceManager + .retrieveSessionRecord( + applicationContext + )?.email if (emailFromSession != null) { authUI.signInWithEmailLink( context = applicationContext, config = configuration, - provider = provider, + provider = selectedProvider, email = emailFromSession, emailLink = emailLink, ) @@ -124,20 +191,23 @@ class MainActivity : ComponentActivity() { } } - setContent { - AuthUITheme { - Surface( - modifier = Modifier.fillMaxSize(), - color = MaterialTheme.colorScheme.background - ) { - EmailAuthMain( - context = applicationContext, - configuration = configuration, - authUI = authUI, - provider = provider - ) - } - } - } + EmailAuthMain( + context = applicationContext, + configuration = configuration, + authUI = authUI, + ) + } + + @Composable + private fun LaunchPhoneAuth( + authUI: FirebaseAuthUI, + configuration: AuthUIConfiguration, + selectedProvider: AuthProvider.Phone, + ) { + PhoneAuthMain( + context = applicationContext, + configuration = configuration, + authUI = authUI, + ) } } \ No newline at end of file diff --git a/composeapp/src/main/java/com/firebase/composeapp/ui/screens/EmailAuthMain.kt b/composeapp/src/main/java/com/firebase/composeapp/ui/screens/EmailAuthMain.kt index 299f1052b..33938a0ff 100644 --- a/composeapp/src/main/java/com/firebase/composeapp/ui/screens/EmailAuthMain.kt +++ b/composeapp/src/main/java/com/firebase/composeapp/ui/screens/EmailAuthMain.kt @@ -32,7 +32,6 @@ fun EmailAuthMain( context: Context, configuration: AuthUIConfiguration, authUI: FirebaseAuthUI, - provider: AuthProvider.Email ) { val coroutineScope = rememberCoroutineScope() val authState by authUI.authStateFlow().collectAsState(AuthState.Idle) diff --git a/composeapp/src/main/java/com/firebase/composeapp/ui/screens/PhoneAuthMain.kt b/composeapp/src/main/java/com/firebase/composeapp/ui/screens/PhoneAuthMain.kt index 06f2371da..cf4cf456a 100644 --- a/composeapp/src/main/java/com/firebase/composeapp/ui/screens/PhoneAuthMain.kt +++ b/composeapp/src/main/java/com/firebase/composeapp/ui/screens/PhoneAuthMain.kt @@ -31,7 +31,6 @@ fun PhoneAuthMain( context: Context, configuration: AuthUIConfiguration, authUI: FirebaseAuthUI, - provider: AuthProvider.Phone, ) { val coroutineScope = rememberCoroutineScope() val authState by authUI.authStateFlow().collectAsState(AuthState.Idle) diff --git a/e2eTest/src/test/java/com/firebase/ui/auth/compose/testutil/EmulatorApi.kt b/e2eTest/src/test/java/com/firebase/ui/auth/compose/testutil/EmulatorApi.kt new file mode 100644 index 000000000..731770a67 --- /dev/null +++ b/e2eTest/src/test/java/com/firebase/ui/auth/compose/testutil/EmulatorApi.kt @@ -0,0 +1,52 @@ +package com.firebase.ui.auth.compose.testutil + +import org.json.JSONArray +import org.json.JSONObject +import java.net.HttpURLConnection + +internal class EmulatorAuthApi( + private val projectId: String, + emulatorHost: String, + emulatorPort: Int +) { + + private val httpClient = HttpClient(host = emulatorHost, port = emulatorPort) + + fun clearAccounts() { + httpClient.delete("/emulator/v1/projects/$projectId/accounts") { connection -> + val responseCode = connection.responseCode + if (responseCode !in 200..299) { + println("WARNING: Failed to clear emulator data: HTTP $responseCode") + } else { + println("TEST: Cleared emulator data") + } + } + } + + fun fetchVerifyEmailCode(email: String): String { + val oobCodes = fetchOobCodes() + return (0 until oobCodes.length()) + .asSequence() + .mapNotNull { index -> oobCodes.optJSONObject(index) } + .firstOrNull { json -> + json.optString("email") == email && + json.optString("requestType") == "VERIFY_EMAIL" + } + ?.optString("oobCode") + ?.takeIf { it.isNotBlank() } + ?: throw Exception("No VERIFY_EMAIL OOB code found for user email: $email") + } + + private fun fetchOobCodes(): JSONArray { + val payload = httpClient.get("/emulator/v1/projects/$projectId/oobCodes") { connection -> + val responseCode = connection.responseCode + if (responseCode != HttpURLConnection.HTTP_OK) { + throw Exception("Failed to get OOB codes: HTTP $responseCode") + } + + connection.inputStream.bufferedReader().use { it.readText() } + } + + return JSONObject(payload).optJSONArray("oobCodes") ?: JSONArray() + } +} diff --git a/e2eTest/src/test/java/com/firebase/ui/auth/compose/testutil/HttpClient.kt b/e2eTest/src/test/java/com/firebase/ui/auth/compose/testutil/HttpClient.kt new file mode 100644 index 000000000..035b15572 --- /dev/null +++ b/e2eTest/src/test/java/com/firebase/ui/auth/compose/testutil/HttpClient.kt @@ -0,0 +1,40 @@ +package com.firebase.ui.auth.compose.testutil + +import java.net.HttpURLConnection +import java.net.URL + +internal class HttpClient( + private val host: String, + private val port: Int, + private val timeoutMs: Int = DEFAULT_TIMEOUT_MS +) { + + companion object { + const val DEFAULT_TIMEOUT_MS = 5_000 + } + + fun delete(path: String, block: (HttpURLConnection) -> Unit) { + execute(path = path, method = "DELETE", block = block) + } + + fun get(path: String, block: (HttpURLConnection) -> T): T { + return execute(path = path, method = "GET", block = block) + } + + private fun execute(path: String, method: String, block: (HttpURLConnection) -> T): T { + val connection = buildUrl(path).openConnection() as HttpURLConnection + connection.requestMethod = method + connection.connectTimeout = timeoutMs + connection.readTimeout = timeoutMs + + return try { + block(connection) + } finally { + connection.disconnect() + } + } + + private fun buildUrl(path: String): URL { + return URL("http://$host:$port$path") + } +} diff --git a/e2eTest/src/test/java/com/firebase/ui/auth/compose/ui/screens/EmailAuthScreenTest.kt b/e2eTest/src/test/java/com/firebase/ui/auth/compose/ui/screens/EmailAuthScreenTest.kt index 57e0e125e..773ea1981 100644 --- a/e2eTest/src/test/java/com/firebase/ui/auth/compose/ui/screens/EmailAuthScreenTest.kt +++ b/e2eTest/src/test/java/com/firebase/ui/auth/compose/ui/screens/EmailAuthScreenTest.kt @@ -22,6 +22,7 @@ import com.firebase.ui.auth.compose.configuration.authUIConfiguration import com.firebase.ui.auth.compose.configuration.auth_provider.AuthProvider import com.firebase.ui.auth.compose.configuration.string_provider.AuthUIStringProvider import com.firebase.ui.auth.compose.configuration.string_provider.DefaultAuthUIStringProvider +import com.firebase.ui.auth.compose.testutil.EmulatorAuthApi import com.firebase.ui.auth.compose.testutil.awaitWithLooper import com.google.common.truth.Truth.assertThat import com.google.firebase.FirebaseApp @@ -671,86 +672,3 @@ class EmailAuthScreenTest { } } - -private class EmulatorAuthApi( - private val projectId: String, - emulatorHost: String, - emulatorPort: Int -) { - - private val httpClient = HttpClient(host = emulatorHost, port = emulatorPort) - - fun clearAccounts() { - httpClient.delete("/emulator/v1/projects/$projectId/accounts") { connection -> - val responseCode = connection.responseCode - if (responseCode !in 200..299) { - println("WARNING: Failed to clear emulator data: HTTP $responseCode") - } else { - println("TEST: Cleared emulator data") - } - } - } - - fun fetchVerifyEmailCode(email: String): String { - val oobCodes = fetchOobCodes() - return (0 until oobCodes.length()) - .asSequence() - .mapNotNull { index -> oobCodes.optJSONObject(index) } - .firstOrNull { json -> - json.optString("email") == email && - json.optString("requestType") == "VERIFY_EMAIL" - } - ?.optString("oobCode") - ?.takeIf { it.isNotBlank() } - ?: throw Exception("No VERIFY_EMAIL OOB code found for user email: $email") - } - - private fun fetchOobCodes(): JSONArray { - val payload = httpClient.get("/emulator/v1/projects/$projectId/oobCodes") { connection -> - val responseCode = connection.responseCode - if (responseCode != HttpURLConnection.HTTP_OK) { - throw Exception("Failed to get OOB codes: HTTP $responseCode") - } - - connection.inputStream.bufferedReader().use { it.readText() } - } - - return JSONObject(payload).optJSONArray("oobCodes") ?: JSONArray() - } -} - -private class HttpClient( - private val host: String, - private val port: Int, - private val timeoutMs: Int = DEFAULT_TIMEOUT_MS -) { - - companion object { - const val DEFAULT_TIMEOUT_MS = 5_000 - } - - fun delete(path: String, block: (HttpURLConnection) -> Unit) { - execute(path = path, method = "DELETE", block = block) - } - - fun get(path: String, block: (HttpURLConnection) -> T): T { - return execute(path = path, method = "GET", block = block) - } - - private fun execute(path: String, method: String, block: (HttpURLConnection) -> T): T { - val connection = buildUrl(path).openConnection() as HttpURLConnection - connection.requestMethod = method - connection.connectTimeout = timeoutMs - connection.readTimeout = timeoutMs - - return try { - block(connection) - } finally { - connection.disconnect() - } - } - - private fun buildUrl(path: String): URL { - return URL("http://$host:$port$path") - } -} diff --git a/e2eTest/src/test/java/com/firebase/ui/auth/compose/ui/screens/PhoneAuthScreenTest.kt b/e2eTest/src/test/java/com/firebase/ui/auth/compose/ui/screens/PhoneAuthScreenTest.kt new file mode 100644 index 000000000..7680303c0 --- /dev/null +++ b/e2eTest/src/test/java/com/firebase/ui/auth/compose/ui/screens/PhoneAuthScreenTest.kt @@ -0,0 +1,501 @@ +package com.firebase.ui.auth.compose.ui.screens + +import android.content.Context +import android.os.Looper +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.assertIsEnabled +import androidx.compose.ui.test.assertIsNotEnabled +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.performScrollTo +import androidx.compose.ui.test.performTextInput +import androidx.test.core.app.ApplicationProvider +import com.firebase.ui.auth.compose.AuthException +import com.firebase.ui.auth.compose.AuthState +import com.firebase.ui.auth.compose.FirebaseAuthUI +import com.firebase.ui.auth.compose.configuration.AuthUIConfiguration +import com.firebase.ui.auth.compose.configuration.authUIConfiguration +import com.firebase.ui.auth.compose.configuration.auth_provider.AuthProvider +import com.firebase.ui.auth.compose.configuration.string_provider.AuthUIStringProvider +import com.firebase.ui.auth.compose.configuration.string_provider.DefaultAuthUIStringProvider +import com.firebase.ui.auth.compose.testutil.EmulatorAuthApi +import com.firebase.ui.auth.compose.ui.screens.phone.EnterPhoneNumberUI +import com.firebase.ui.auth.compose.ui.screens.phone.EnterVerificationCodeUI +import com.firebase.ui.auth.compose.ui.screens.phone.PhoneAuthScreen +import com.firebase.ui.auth.compose.ui.screens.phone.PhoneAuthStep +import com.google.common.truth.Truth.assertThat +import com.google.firebase.FirebaseApp +import com.google.firebase.FirebaseOptions +import com.google.firebase.auth.AuthResult +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.MockitoAnnotations +import org.robolectric.RobolectricTestRunner +import org.robolectric.Shadows.shadowOf +import org.robolectric.annotation.Config + +private const val AUTH_STATE_WAIT_TIMEOUT_MS = 5_000L +private const val TEST_PHONE_NUMBER = "5551234567" +private const val TEST_VERIFICATION_CODE = "123456" + +@Config(sdk = [34]) +@RunWith(RobolectricTestRunner::class) +class PhoneAuthScreenTest { + @get:Rule + val composeTestRule = createComposeRule() + + private lateinit var applicationContext: Context + private lateinit var stringProvider: AuthUIStringProvider + private lateinit var authUI: FirebaseAuthUI + private lateinit var emulatorApi: EmulatorAuthApi + + @Before + fun setUp() { + MockitoAnnotations.openMocks(this) + + applicationContext = ApplicationProvider.getApplicationContext() + stringProvider = DefaultAuthUIStringProvider(applicationContext) + + // Clear any existing Firebase apps + FirebaseApp.getApps(applicationContext).forEach { app -> + app.delete() + } + + // Initialize default FirebaseApp + val firebaseApp = FirebaseApp.initializeApp( + applicationContext, + FirebaseOptions.Builder() + .setApiKey("fake-api-key") + .setApplicationId("fake-app-id") + .setProjectId("fake-project-id") + .build() + ) + + authUI = FirebaseAuthUI.getInstance() + authUI.auth.useEmulator("127.0.0.1", 9099) + + emulatorApi = EmulatorAuthApi( + projectId = firebaseApp.options.projectId + ?: throw IllegalStateException("Project ID is required for emulator interactions"), + emulatorHost = "127.0.0.1", + emulatorPort = 9099 + ) + + // Clear emulator data + clearEmulatorData() + } + + @After + fun tearDown() { + // Clean up after each test to prevent test pollution + FirebaseAuthUI.clearInstanceCache() + + // Clear emulator data + clearEmulatorData() + } + + @Test + fun `initial PhoneAuthStep is EnterPhoneNumber`() { + val configuration = authUIConfiguration { + context = applicationContext + providers { + provider( + AuthProvider.Phone( + defaultNumber = null, + defaultCountryCode = null, + allowedCountries = null, + timeout = 60L, + isInstantVerificationEnabled = true + ) + ) + } + } + + composeTestRule.setContent { + FirebaseAuthScreen(configuration = configuration) + } + + composeTestRule.onNodeWithText(stringProvider.enterPhoneNumberTitle) + .assertIsDisplayed() + } + + @Test + fun `phone number input enables send code button when valid`() { + val configuration = authUIConfiguration { + context = applicationContext + providers { + provider( + AuthProvider.Phone( + defaultNumber = null, + defaultCountryCode = "US", + allowedCountries = null, + timeout = 60L, + isInstantVerificationEnabled = true + ) + ) + } + } + + composeTestRule.setContent { + FirebaseAuthScreen(configuration = configuration) + } + + // Initially button should be disabled (no phone number) + composeTestRule.onNodeWithText(stringProvider.sendVerificationCode.uppercase()) + .performScrollTo() + .assertIsNotEnabled() + + // Enter phone number + composeTestRule.onNodeWithText(stringProvider.phoneNumberHint) + .performScrollTo() + .performTextInput(TEST_PHONE_NUMBER) + + // Button should now be enabled + composeTestRule.onNodeWithText(stringProvider.sendVerificationCode.uppercase()) + .performScrollTo() + .assertIsEnabled() + } + + @Test + fun `sends verification code and transitions to EnterVerificationCode step`() { + val configuration = authUIConfiguration { + context = applicationContext + providers { + provider( + AuthProvider.Phone( + defaultNumber = null, + defaultCountryCode = "US", + allowedCountries = null, + timeout = 60L, + isInstantVerificationEnabled = false // Disable instant verification for this test + ) + ) + } + } + + var currentAuthState: AuthState = AuthState.Idle + var currentStep: PhoneAuthStep? = null + + composeTestRule.setContent { + FirebaseAuthScreen( + configuration = configuration, + onStepChange = { step -> currentStep = step } + ) + val authState by authUI.authStateFlow().collectAsState(AuthState.Idle) + currentAuthState = authState + } + + // Enter phone number + composeTestRule.onNodeWithText(stringProvider.phoneNumberHint) + .performScrollTo() + .performTextInput(TEST_PHONE_NUMBER) + + // Click send code + composeTestRule.onNodeWithText(stringProvider.sendVerificationCode.uppercase()) + .performScrollTo() + .performClick() + + println("TEST: Pumping looper after click...") + shadowOf(Looper.getMainLooper()).idle() + + // Wait for transition to verification code step + println("TEST: Waiting for step change... Current state: $currentAuthState") + composeTestRule.waitUntil(timeoutMillis = AUTH_STATE_WAIT_TIMEOUT_MS) { + shadowOf(Looper.getMainLooper()).idle() + println("TEST: Auth state during wait: $currentAuthState, Step: $currentStep") + currentStep == PhoneAuthStep.EnterVerificationCode + } + + // Ensure final recomposition is complete + shadowOf(Looper.getMainLooper()).idle() + + // Verify we're on the verification code screen + println("TEST: Verifying verification code screen is displayed") + composeTestRule.onNodeWithText(stringProvider.verifyPhoneNumber) + .assertIsDisplayed() + } + + @Test + fun `verification code input enables verify button when complete`() { + val configuration = authUIConfiguration { + context = applicationContext + providers { + provider( + AuthProvider.Phone( + defaultNumber = TEST_PHONE_NUMBER, + defaultCountryCode = "US", + allowedCountries = null, + timeout = 60L, + isInstantVerificationEnabled = false + ) + ) + } + } + + var currentStep: PhoneAuthStep? = null + + composeTestRule.setContent { + FirebaseAuthScreen( + configuration = configuration, + onStepChange = { step -> currentStep = step } + ) + } + + // Send verification code + composeTestRule.onNodeWithText(stringProvider.sendVerificationCode.uppercase()) + .performScrollTo() + .performClick() + + shadowOf(Looper.getMainLooper()).idle() + + // Wait for transition to verification code step + composeTestRule.waitUntil(timeoutMillis = AUTH_STATE_WAIT_TIMEOUT_MS) { + shadowOf(Looper.getMainLooper()).idle() + currentStep == PhoneAuthStep.EnterVerificationCode + } + + shadowOf(Looper.getMainLooper()).idle() + + // Verify button should be disabled initially + composeTestRule.onNodeWithText(stringProvider.verifyCode.uppercase()) + .performScrollTo() + .assertIsNotEnabled() + + // Note: VerificationCodeInputField uses a custom implementation + // For testing, we'd need to interact with the actual input mechanism + // This is a simplified test - in practice you'd need to properly interact + // with the VerificationCodeInputField component + } + + @Test + fun `resend code timer starts at configured timeout`() { + val timeout = 60L + val configuration = authUIConfiguration { + context = applicationContext + providers { + provider( + AuthProvider.Phone( + defaultNumber = TEST_PHONE_NUMBER, + defaultCountryCode = "US", + allowedCountries = null, + timeout = timeout, + isInstantVerificationEnabled = false + ) + ) + } + } + + var currentStep: PhoneAuthStep? = null + + composeTestRule.setContent { + FirebaseAuthScreen( + configuration = configuration, + onStepChange = { step -> currentStep = step } + ) + } + + // Send verification code + composeTestRule.onNodeWithText(stringProvider.sendVerificationCode.uppercase()) + .performScrollTo() + .performClick() + + shadowOf(Looper.getMainLooper()).idle() + + // Wait for transition to verification code step + composeTestRule.waitUntil(timeoutMillis = AUTH_STATE_WAIT_TIMEOUT_MS) { + shadowOf(Looper.getMainLooper()).idle() + currentStep == PhoneAuthStep.EnterVerificationCode + } + + shadowOf(Looper.getMainLooper()).idle() + + // Check that timer text is displayed (should show 1:00 for 60 seconds) + val expectedTimerText = stringProvider.resendCodeTimer("1:00") + composeTestRule.onNodeWithText(expectedTimerText, substring = true) + .assertIsDisplayed() + } + + @Test + fun `change phone number navigates back to EnterPhoneNumber step`() { + val configuration = authUIConfiguration { + context = applicationContext + providers { + provider( + AuthProvider.Phone( + defaultNumber = TEST_PHONE_NUMBER, + defaultCountryCode = "US", + allowedCountries = null, + timeout = 60L, + isInstantVerificationEnabled = false + ) + ) + } + } + + var currentStep: PhoneAuthStep? = null + + composeTestRule.setContent { + FirebaseAuthScreen( + configuration = configuration, + onStepChange = { step -> currentStep = step } + ) + } + + // Send verification code to get to verification screen + composeTestRule.onNodeWithText(stringProvider.sendVerificationCode.uppercase()) + .performScrollTo() + .performClick() + + shadowOf(Looper.getMainLooper()).idle() + + // Wait for transition to verification code step + composeTestRule.waitUntil(timeoutMillis = AUTH_STATE_WAIT_TIMEOUT_MS) { + shadowOf(Looper.getMainLooper()).idle() + currentStep == PhoneAuthStep.EnterVerificationCode + } + + shadowOf(Looper.getMainLooper()).idle() + + // Click change phone number + composeTestRule.onNodeWithText(stringProvider.changePhoneNumber) + .performScrollTo() + .performClick() + + shadowOf(Looper.getMainLooper()).idle() + + // Verify we're back on the phone number entry screen + assertThat(currentStep).isEqualTo(PhoneAuthStep.EnterPhoneNumber) + composeTestRule.onNodeWithText(stringProvider.enterPhoneNumberTitle) + .assertIsDisplayed() + } + + @Test + fun `default country code is applied when configured`() { + val defaultCountryCode = "GB" + val configuration = authUIConfiguration { + context = applicationContext + providers { + provider( + AuthProvider.Phone( + defaultNumber = null, + defaultCountryCode = defaultCountryCode, + allowedCountries = null, + timeout = 60L, + isInstantVerificationEnabled = true + ) + ) + } + } + + composeTestRule.setContent { + FirebaseAuthScreen(configuration = configuration) + } + + // The country selector should show the default country (GB = United Kingdom) + // Note: The exact text depends on how the country is displayed in the UI + composeTestRule.onNodeWithText("United Kingdom", substring = true) + .assertIsDisplayed() + } + + @Test + fun `default phone number is pre-filled when configured`() { + val defaultNumber = "1234567890" + val configuration = authUIConfiguration { + context = applicationContext + providers { + provider( + AuthProvider.Phone( + defaultNumber = defaultNumber, + defaultCountryCode = "US", + allowedCountries = null, + timeout = 60L, + isInstantVerificationEnabled = true + ) + ) + } + } + + composeTestRule.setContent { + FirebaseAuthScreen(configuration = configuration) + } + + // The send verification code button should be enabled since phone number is pre-filled + composeTestRule.onNodeWithText(stringProvider.sendVerificationCode.uppercase()) + .performScrollTo() + .assertIsEnabled() + } + + @Composable + private fun FirebaseAuthScreen( + configuration: AuthUIConfiguration, + onSuccess: ((AuthResult) -> Unit) = {}, + onError: ((AuthException) -> Unit) = {}, + onCancel: (() -> Unit) = {}, + onStepChange: ((PhoneAuthStep) -> Unit) = {}, + ) { + PhoneAuthScreen( + context = applicationContext, + configuration = configuration, + authUI = authUI, + onSuccess = onSuccess, + onError = onError, + onCancel = onCancel, + ) { state -> + onStepChange(state.step) + + when (state.step) { + PhoneAuthStep.EnterPhoneNumber -> { + EnterPhoneNumberUI( + configuration = configuration, + isLoading = state.isLoading, + phoneNumber = state.phoneNumber, + selectedCountry = state.selectedCountry, + useInstantVerificationEnabled = state.useInstantVerificationEnabled, + onPhoneNumberChange = state.onPhoneNumberChange, + onCountrySelected = state.onCountrySelected, + onSendCodeClick = state.onSendCodeClick, + onUseInstantVerificationChange = state.onUseInstantVerificationChange, + ) + } + + PhoneAuthStep.EnterVerificationCode -> { + EnterVerificationCodeUI( + configuration = configuration, + isLoading = state.isLoading, + verificationCode = state.verificationCode, + fullPhoneNumber = state.fullPhoneNumber, + resendTimer = state.resendTimer, + onVerificationCodeChange = state.onVerificationCodeChange, + onVerifyCodeClick = state.onVerifyCodeClick, + onResendCodeClick = state.onResendCodeClick, + onChangeNumberClick = state.onChangeNumberClick, + ) + } + } + } + } + + /** + * Clears all data from the Firebase Auth Emulator. + * + * This function calls the emulator's clear data endpoint to remove all accounts, + * OOB codes, and other authentication data. This ensures test isolation by providing + * a clean slate for each test. + */ + private fun clearEmulatorData() { + if (::emulatorApi.isInitialized) { + try { + emulatorApi.clearAccounts() + } catch (e: Exception) { + println("WARNING: Exception while clearing emulator data: ${e.message}") + } + } + } +} From 632fc1dc295307dfca749aed4b1b0de2c34b907a Mon Sep 17 00:00:00 2001 From: Ademola Fadumo Date: Wed, 15 Oct 2025 15:36:41 +0100 Subject: [PATCH 04/12] fix failing tests --- .../auth/compose/mfa/SmsEnrollmentHandler.kt | 6 +- .../PhoneAuthProviderFirebaseAuthUITest.kt | 60 ++- .../compose/mfa/SmsEnrollmentHandlerTest.kt | 7 +- .../compose/ui/screens/PhoneAuthScreenTest.kt | 501 ------------------ 4 files changed, 45 insertions(+), 529 deletions(-) delete mode 100644 e2eTest/src/test/java/com/firebase/ui/auth/compose/ui/screens/PhoneAuthScreenTest.kt diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/mfa/SmsEnrollmentHandler.kt b/auth/src/main/java/com/firebase/ui/auth/compose/mfa/SmsEnrollmentHandler.kt index 3bf46c50a..02037c4c9 100644 --- a/auth/src/main/java/com/firebase/ui/auth/compose/mfa/SmsEnrollmentHandler.kt +++ b/auth/src/main/java/com/firebase/ui/auth/compose/mfa/SmsEnrollmentHandler.kt @@ -14,10 +14,11 @@ package com.firebase.ui.auth.compose.mfa +import android.app.Activity import com.firebase.ui.auth.compose.configuration.auth_provider.AuthProvider +import com.firebase.ui.auth.compose.mfa.SmsEnrollmentHandler.Companion.RESEND_DELAY_SECONDS import com.google.firebase.auth.FirebaseAuth import com.google.firebase.auth.FirebaseUser -import com.google.firebase.auth.MultiFactorAssertion import com.google.firebase.auth.PhoneAuthCredential import com.google.firebase.auth.PhoneAuthProvider import com.google.firebase.auth.PhoneMultiFactorGenerator @@ -61,6 +62,7 @@ import kotlinx.coroutines.tasks.await * @see AuthProvider.Phone.verifyPhoneNumberAwait */ class SmsEnrollmentHandler( + private val activity: Activity, private val auth: FirebaseAuth, private val user: FirebaseUser ) { @@ -98,6 +100,7 @@ class SmsEnrollmentHandler( val multiFactorSession = user.multiFactor.session.await() val result = phoneProvider.verifyPhoneNumberAwait( auth = auth, + activity = activity, phoneNumber = phoneNumber, multiFactorSession = multiFactorSession, forceResendingToken = null @@ -145,6 +148,7 @@ class SmsEnrollmentHandler( val multiFactorSession = user.multiFactor.session.await() val result = phoneProvider.verifyPhoneNumberAwait( auth = auth, + activity = activity, phoneNumber = session.phoneNumber, multiFactorSession = multiFactorSession, forceResendingToken = session.forceResendingToken diff --git a/auth/src/test/java/com/firebase/ui/auth/compose/configuration/auth_provider/PhoneAuthProviderFirebaseAuthUITest.kt b/auth/src/test/java/com/firebase/ui/auth/compose/configuration/auth_provider/PhoneAuthProviderFirebaseAuthUITest.kt index b01b10ab8..ad39e96cf 100644 --- a/auth/src/test/java/com/firebase/ui/auth/compose/configuration/auth_provider/PhoneAuthProviderFirebaseAuthUITest.kt +++ b/auth/src/test/java/com/firebase/ui/auth/compose/configuration/auth_provider/PhoneAuthProviderFirebaseAuthUITest.kt @@ -42,6 +42,7 @@ import org.mockito.MockitoAnnotations import org.mockito.kotlin.any import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.eq +import org.mockito.kotlin.timeout import org.robolectric.RobolectricTestRunner import org.robolectric.annotation.Config @@ -121,17 +122,19 @@ class PhoneAuthProviderFirebaseAuthUITest { `when`( mockPhoneAuthVerifier.verifyPhoneNumber( - any(), - any(), - any(), - anyOrNull(), - anyOrNull(), - eq(true) + auth = any(), + activity = anyOrNull(), + phoneNumber = any(), + timeout = eq(60L), + forceResendingToken = anyOrNull(), + multiFactorSession = anyOrNull(), + isInstantVerificationEnabled = eq(true) ) ).thenReturn(AuthProvider.Phone.VerifyPhoneNumberResult.AutoVerified(mockCredential)) instance.verifyPhoneNumber( provider = phoneProvider, + activity = null, phoneNumber = "+1234567890", verifier = mockPhoneAuthVerifier ) @@ -157,12 +160,13 @@ class PhoneAuthProviderFirebaseAuthUITest { `when`( mockPhoneAuthVerifier.verifyPhoneNumber( - any(), - any(), - any(), - anyOrNull(), - anyOrNull(), - eq(true) + auth = any(), + activity = anyOrNull(), + phoneNumber = any(), + timeout = eq(60L), + forceResendingToken = anyOrNull(), + multiFactorSession = anyOrNull(), + isInstantVerificationEnabled = eq(true) ) ).thenReturn( AuthProvider.Phone.VerifyPhoneNumberResult.NeedsManualVerification( @@ -173,6 +177,7 @@ class PhoneAuthProviderFirebaseAuthUITest { instance.verifyPhoneNumber( provider = phoneProvider, + activity = null, phoneNumber = "+1234567890", verifier = mockPhoneAuthVerifier ) @@ -200,12 +205,13 @@ class PhoneAuthProviderFirebaseAuthUITest { `when`( mockPhoneAuthVerifier.verifyPhoneNumber( - any(), - any(), - any(), - eq(mockToken), - anyOrNull(), - eq(true) + auth = any(), + activity = anyOrNull(), + phoneNumber = any(), + timeout = eq(60L), + forceResendingToken = eq(mockToken), + multiFactorSession = anyOrNull(), + isInstantVerificationEnabled = eq(true) ) ).thenReturn( AuthProvider.Phone.VerifyPhoneNumberResult.NeedsManualVerification( @@ -216,6 +222,7 @@ class PhoneAuthProviderFirebaseAuthUITest { instance.verifyPhoneNumber( provider = phoneProvider, + activity = null, phoneNumber = "+1234567890", forceResendingToken = mockToken, verifier = mockPhoneAuthVerifier @@ -242,12 +249,13 @@ class PhoneAuthProviderFirebaseAuthUITest { `when`( mockPhoneAuthVerifier.verifyPhoneNumber( - any(), - any(), - any(), - anyOrNull(), - anyOrNull(), - eq(false) + auth = any(), + activity = anyOrNull(), + phoneNumber = any(), + timeout = eq(60L), + forceResendingToken = anyOrNull(), + multiFactorSession = anyOrNull(), + isInstantVerificationEnabled = eq(false) ) ).thenReturn( AuthProvider.Phone.VerifyPhoneNumberResult.NeedsManualVerification( @@ -258,14 +266,16 @@ class PhoneAuthProviderFirebaseAuthUITest { instance.verifyPhoneNumber( provider = phoneProvider, + activity = null, phoneNumber = "+1234567890", verifier = mockPhoneAuthVerifier ) verify(mockPhoneAuthVerifier).verifyPhoneNumber( any(), + anyOrNull(), any(), - any(), + eq(60L), anyOrNull(), anyOrNull(), eq(false) diff --git a/auth/src/test/java/com/firebase/ui/auth/compose/mfa/SmsEnrollmentHandlerTest.kt b/auth/src/test/java/com/firebase/ui/auth/compose/mfa/SmsEnrollmentHandlerTest.kt index 579b6f0f6..014a33f2e 100644 --- a/auth/src/test/java/com/firebase/ui/auth/compose/mfa/SmsEnrollmentHandlerTest.kt +++ b/auth/src/test/java/com/firebase/ui/auth/compose/mfa/SmsEnrollmentHandlerTest.kt @@ -14,6 +14,7 @@ package com.firebase.ui.auth.compose.mfa +import android.app.Activity import com.google.firebase.auth.FirebaseAuth import com.google.firebase.auth.FirebaseUser import com.google.firebase.auth.MultiFactor @@ -42,6 +43,8 @@ import org.robolectric.annotation.Config @Config(manifest = Config.NONE) class SmsEnrollmentHandlerTest { + @Mock + private lateinit var mockActivity: Activity @Mock private lateinit var mockAuth: FirebaseAuth @Mock @@ -54,7 +57,7 @@ class SmsEnrollmentHandlerTest { fun setUp() { MockitoAnnotations.openMocks(this) `when`(mockUser.multiFactor).thenReturn(mockMultiFactor) - handler = SmsEnrollmentHandler(mockAuth, mockUser) + handler = SmsEnrollmentHandler(mockActivity, mockAuth, mockUser) } // isValidCodeFormat tests @@ -153,7 +156,7 @@ class SmsEnrollmentHandlerTest { @Test fun `handler is created with correct auth and user references`() { // Verify handler can be instantiated - val newHandler = SmsEnrollmentHandler(mockAuth, mockUser) + val newHandler = SmsEnrollmentHandler(mockActivity, mockAuth, mockUser) // Basic smoke test - if we get here, construction succeeded assertTrue(newHandler.isValidCodeFormat("123456")) } diff --git a/e2eTest/src/test/java/com/firebase/ui/auth/compose/ui/screens/PhoneAuthScreenTest.kt b/e2eTest/src/test/java/com/firebase/ui/auth/compose/ui/screens/PhoneAuthScreenTest.kt deleted file mode 100644 index 7680303c0..000000000 --- a/e2eTest/src/test/java/com/firebase/ui/auth/compose/ui/screens/PhoneAuthScreenTest.kt +++ /dev/null @@ -1,501 +0,0 @@ -package com.firebase.ui.auth.compose.ui.screens - -import android.content.Context -import android.os.Looper -import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.ui.test.assertIsDisplayed -import androidx.compose.ui.test.assertIsEnabled -import androidx.compose.ui.test.assertIsNotEnabled -import androidx.compose.ui.test.junit4.createComposeRule -import androidx.compose.ui.test.onNodeWithText -import androidx.compose.ui.test.performClick -import androidx.compose.ui.test.performScrollTo -import androidx.compose.ui.test.performTextInput -import androidx.test.core.app.ApplicationProvider -import com.firebase.ui.auth.compose.AuthException -import com.firebase.ui.auth.compose.AuthState -import com.firebase.ui.auth.compose.FirebaseAuthUI -import com.firebase.ui.auth.compose.configuration.AuthUIConfiguration -import com.firebase.ui.auth.compose.configuration.authUIConfiguration -import com.firebase.ui.auth.compose.configuration.auth_provider.AuthProvider -import com.firebase.ui.auth.compose.configuration.string_provider.AuthUIStringProvider -import com.firebase.ui.auth.compose.configuration.string_provider.DefaultAuthUIStringProvider -import com.firebase.ui.auth.compose.testutil.EmulatorAuthApi -import com.firebase.ui.auth.compose.ui.screens.phone.EnterPhoneNumberUI -import com.firebase.ui.auth.compose.ui.screens.phone.EnterVerificationCodeUI -import com.firebase.ui.auth.compose.ui.screens.phone.PhoneAuthScreen -import com.firebase.ui.auth.compose.ui.screens.phone.PhoneAuthStep -import com.google.common.truth.Truth.assertThat -import com.google.firebase.FirebaseApp -import com.google.firebase.FirebaseOptions -import com.google.firebase.auth.AuthResult -import org.junit.After -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.MockitoAnnotations -import org.robolectric.RobolectricTestRunner -import org.robolectric.Shadows.shadowOf -import org.robolectric.annotation.Config - -private const val AUTH_STATE_WAIT_TIMEOUT_MS = 5_000L -private const val TEST_PHONE_NUMBER = "5551234567" -private const val TEST_VERIFICATION_CODE = "123456" - -@Config(sdk = [34]) -@RunWith(RobolectricTestRunner::class) -class PhoneAuthScreenTest { - @get:Rule - val composeTestRule = createComposeRule() - - private lateinit var applicationContext: Context - private lateinit var stringProvider: AuthUIStringProvider - private lateinit var authUI: FirebaseAuthUI - private lateinit var emulatorApi: EmulatorAuthApi - - @Before - fun setUp() { - MockitoAnnotations.openMocks(this) - - applicationContext = ApplicationProvider.getApplicationContext() - stringProvider = DefaultAuthUIStringProvider(applicationContext) - - // Clear any existing Firebase apps - FirebaseApp.getApps(applicationContext).forEach { app -> - app.delete() - } - - // Initialize default FirebaseApp - val firebaseApp = FirebaseApp.initializeApp( - applicationContext, - FirebaseOptions.Builder() - .setApiKey("fake-api-key") - .setApplicationId("fake-app-id") - .setProjectId("fake-project-id") - .build() - ) - - authUI = FirebaseAuthUI.getInstance() - authUI.auth.useEmulator("127.0.0.1", 9099) - - emulatorApi = EmulatorAuthApi( - projectId = firebaseApp.options.projectId - ?: throw IllegalStateException("Project ID is required for emulator interactions"), - emulatorHost = "127.0.0.1", - emulatorPort = 9099 - ) - - // Clear emulator data - clearEmulatorData() - } - - @After - fun tearDown() { - // Clean up after each test to prevent test pollution - FirebaseAuthUI.clearInstanceCache() - - // Clear emulator data - clearEmulatorData() - } - - @Test - fun `initial PhoneAuthStep is EnterPhoneNumber`() { - val configuration = authUIConfiguration { - context = applicationContext - providers { - provider( - AuthProvider.Phone( - defaultNumber = null, - defaultCountryCode = null, - allowedCountries = null, - timeout = 60L, - isInstantVerificationEnabled = true - ) - ) - } - } - - composeTestRule.setContent { - FirebaseAuthScreen(configuration = configuration) - } - - composeTestRule.onNodeWithText(stringProvider.enterPhoneNumberTitle) - .assertIsDisplayed() - } - - @Test - fun `phone number input enables send code button when valid`() { - val configuration = authUIConfiguration { - context = applicationContext - providers { - provider( - AuthProvider.Phone( - defaultNumber = null, - defaultCountryCode = "US", - allowedCountries = null, - timeout = 60L, - isInstantVerificationEnabled = true - ) - ) - } - } - - composeTestRule.setContent { - FirebaseAuthScreen(configuration = configuration) - } - - // Initially button should be disabled (no phone number) - composeTestRule.onNodeWithText(stringProvider.sendVerificationCode.uppercase()) - .performScrollTo() - .assertIsNotEnabled() - - // Enter phone number - composeTestRule.onNodeWithText(stringProvider.phoneNumberHint) - .performScrollTo() - .performTextInput(TEST_PHONE_NUMBER) - - // Button should now be enabled - composeTestRule.onNodeWithText(stringProvider.sendVerificationCode.uppercase()) - .performScrollTo() - .assertIsEnabled() - } - - @Test - fun `sends verification code and transitions to EnterVerificationCode step`() { - val configuration = authUIConfiguration { - context = applicationContext - providers { - provider( - AuthProvider.Phone( - defaultNumber = null, - defaultCountryCode = "US", - allowedCountries = null, - timeout = 60L, - isInstantVerificationEnabled = false // Disable instant verification for this test - ) - ) - } - } - - var currentAuthState: AuthState = AuthState.Idle - var currentStep: PhoneAuthStep? = null - - composeTestRule.setContent { - FirebaseAuthScreen( - configuration = configuration, - onStepChange = { step -> currentStep = step } - ) - val authState by authUI.authStateFlow().collectAsState(AuthState.Idle) - currentAuthState = authState - } - - // Enter phone number - composeTestRule.onNodeWithText(stringProvider.phoneNumberHint) - .performScrollTo() - .performTextInput(TEST_PHONE_NUMBER) - - // Click send code - composeTestRule.onNodeWithText(stringProvider.sendVerificationCode.uppercase()) - .performScrollTo() - .performClick() - - println("TEST: Pumping looper after click...") - shadowOf(Looper.getMainLooper()).idle() - - // Wait for transition to verification code step - println("TEST: Waiting for step change... Current state: $currentAuthState") - composeTestRule.waitUntil(timeoutMillis = AUTH_STATE_WAIT_TIMEOUT_MS) { - shadowOf(Looper.getMainLooper()).idle() - println("TEST: Auth state during wait: $currentAuthState, Step: $currentStep") - currentStep == PhoneAuthStep.EnterVerificationCode - } - - // Ensure final recomposition is complete - shadowOf(Looper.getMainLooper()).idle() - - // Verify we're on the verification code screen - println("TEST: Verifying verification code screen is displayed") - composeTestRule.onNodeWithText(stringProvider.verifyPhoneNumber) - .assertIsDisplayed() - } - - @Test - fun `verification code input enables verify button when complete`() { - val configuration = authUIConfiguration { - context = applicationContext - providers { - provider( - AuthProvider.Phone( - defaultNumber = TEST_PHONE_NUMBER, - defaultCountryCode = "US", - allowedCountries = null, - timeout = 60L, - isInstantVerificationEnabled = false - ) - ) - } - } - - var currentStep: PhoneAuthStep? = null - - composeTestRule.setContent { - FirebaseAuthScreen( - configuration = configuration, - onStepChange = { step -> currentStep = step } - ) - } - - // Send verification code - composeTestRule.onNodeWithText(stringProvider.sendVerificationCode.uppercase()) - .performScrollTo() - .performClick() - - shadowOf(Looper.getMainLooper()).idle() - - // Wait for transition to verification code step - composeTestRule.waitUntil(timeoutMillis = AUTH_STATE_WAIT_TIMEOUT_MS) { - shadowOf(Looper.getMainLooper()).idle() - currentStep == PhoneAuthStep.EnterVerificationCode - } - - shadowOf(Looper.getMainLooper()).idle() - - // Verify button should be disabled initially - composeTestRule.onNodeWithText(stringProvider.verifyCode.uppercase()) - .performScrollTo() - .assertIsNotEnabled() - - // Note: VerificationCodeInputField uses a custom implementation - // For testing, we'd need to interact with the actual input mechanism - // This is a simplified test - in practice you'd need to properly interact - // with the VerificationCodeInputField component - } - - @Test - fun `resend code timer starts at configured timeout`() { - val timeout = 60L - val configuration = authUIConfiguration { - context = applicationContext - providers { - provider( - AuthProvider.Phone( - defaultNumber = TEST_PHONE_NUMBER, - defaultCountryCode = "US", - allowedCountries = null, - timeout = timeout, - isInstantVerificationEnabled = false - ) - ) - } - } - - var currentStep: PhoneAuthStep? = null - - composeTestRule.setContent { - FirebaseAuthScreen( - configuration = configuration, - onStepChange = { step -> currentStep = step } - ) - } - - // Send verification code - composeTestRule.onNodeWithText(stringProvider.sendVerificationCode.uppercase()) - .performScrollTo() - .performClick() - - shadowOf(Looper.getMainLooper()).idle() - - // Wait for transition to verification code step - composeTestRule.waitUntil(timeoutMillis = AUTH_STATE_WAIT_TIMEOUT_MS) { - shadowOf(Looper.getMainLooper()).idle() - currentStep == PhoneAuthStep.EnterVerificationCode - } - - shadowOf(Looper.getMainLooper()).idle() - - // Check that timer text is displayed (should show 1:00 for 60 seconds) - val expectedTimerText = stringProvider.resendCodeTimer("1:00") - composeTestRule.onNodeWithText(expectedTimerText, substring = true) - .assertIsDisplayed() - } - - @Test - fun `change phone number navigates back to EnterPhoneNumber step`() { - val configuration = authUIConfiguration { - context = applicationContext - providers { - provider( - AuthProvider.Phone( - defaultNumber = TEST_PHONE_NUMBER, - defaultCountryCode = "US", - allowedCountries = null, - timeout = 60L, - isInstantVerificationEnabled = false - ) - ) - } - } - - var currentStep: PhoneAuthStep? = null - - composeTestRule.setContent { - FirebaseAuthScreen( - configuration = configuration, - onStepChange = { step -> currentStep = step } - ) - } - - // Send verification code to get to verification screen - composeTestRule.onNodeWithText(stringProvider.sendVerificationCode.uppercase()) - .performScrollTo() - .performClick() - - shadowOf(Looper.getMainLooper()).idle() - - // Wait for transition to verification code step - composeTestRule.waitUntil(timeoutMillis = AUTH_STATE_WAIT_TIMEOUT_MS) { - shadowOf(Looper.getMainLooper()).idle() - currentStep == PhoneAuthStep.EnterVerificationCode - } - - shadowOf(Looper.getMainLooper()).idle() - - // Click change phone number - composeTestRule.onNodeWithText(stringProvider.changePhoneNumber) - .performScrollTo() - .performClick() - - shadowOf(Looper.getMainLooper()).idle() - - // Verify we're back on the phone number entry screen - assertThat(currentStep).isEqualTo(PhoneAuthStep.EnterPhoneNumber) - composeTestRule.onNodeWithText(stringProvider.enterPhoneNumberTitle) - .assertIsDisplayed() - } - - @Test - fun `default country code is applied when configured`() { - val defaultCountryCode = "GB" - val configuration = authUIConfiguration { - context = applicationContext - providers { - provider( - AuthProvider.Phone( - defaultNumber = null, - defaultCountryCode = defaultCountryCode, - allowedCountries = null, - timeout = 60L, - isInstantVerificationEnabled = true - ) - ) - } - } - - composeTestRule.setContent { - FirebaseAuthScreen(configuration = configuration) - } - - // The country selector should show the default country (GB = United Kingdom) - // Note: The exact text depends on how the country is displayed in the UI - composeTestRule.onNodeWithText("United Kingdom", substring = true) - .assertIsDisplayed() - } - - @Test - fun `default phone number is pre-filled when configured`() { - val defaultNumber = "1234567890" - val configuration = authUIConfiguration { - context = applicationContext - providers { - provider( - AuthProvider.Phone( - defaultNumber = defaultNumber, - defaultCountryCode = "US", - allowedCountries = null, - timeout = 60L, - isInstantVerificationEnabled = true - ) - ) - } - } - - composeTestRule.setContent { - FirebaseAuthScreen(configuration = configuration) - } - - // The send verification code button should be enabled since phone number is pre-filled - composeTestRule.onNodeWithText(stringProvider.sendVerificationCode.uppercase()) - .performScrollTo() - .assertIsEnabled() - } - - @Composable - private fun FirebaseAuthScreen( - configuration: AuthUIConfiguration, - onSuccess: ((AuthResult) -> Unit) = {}, - onError: ((AuthException) -> Unit) = {}, - onCancel: (() -> Unit) = {}, - onStepChange: ((PhoneAuthStep) -> Unit) = {}, - ) { - PhoneAuthScreen( - context = applicationContext, - configuration = configuration, - authUI = authUI, - onSuccess = onSuccess, - onError = onError, - onCancel = onCancel, - ) { state -> - onStepChange(state.step) - - when (state.step) { - PhoneAuthStep.EnterPhoneNumber -> { - EnterPhoneNumberUI( - configuration = configuration, - isLoading = state.isLoading, - phoneNumber = state.phoneNumber, - selectedCountry = state.selectedCountry, - useInstantVerificationEnabled = state.useInstantVerificationEnabled, - onPhoneNumberChange = state.onPhoneNumberChange, - onCountrySelected = state.onCountrySelected, - onSendCodeClick = state.onSendCodeClick, - onUseInstantVerificationChange = state.onUseInstantVerificationChange, - ) - } - - PhoneAuthStep.EnterVerificationCode -> { - EnterVerificationCodeUI( - configuration = configuration, - isLoading = state.isLoading, - verificationCode = state.verificationCode, - fullPhoneNumber = state.fullPhoneNumber, - resendTimer = state.resendTimer, - onVerificationCodeChange = state.onVerificationCodeChange, - onVerifyCodeClick = state.onVerifyCodeClick, - onResendCodeClick = state.onResendCodeClick, - onChangeNumberClick = state.onChangeNumberClick, - ) - } - } - } - } - - /** - * Clears all data from the Firebase Auth Emulator. - * - * This function calls the emulator's clear data endpoint to remove all accounts, - * OOB codes, and other authentication data. This ensures test isolation by providing - * a clean slate for each test. - */ - private fun clearEmulatorData() { - if (::emulatorApi.isInitialized) { - try { - emulatorApi.clearAccounts() - } catch (e: Exception) { - println("WARNING: Exception while clearing emulator data: ${e.message}") - } - } - } -} From caa6fff07fd719bd4986cb89407afd55ffccd6dd Mon Sep 17 00:00:00 2001 From: Ademola Fadumo Date: Wed, 15 Oct 2025 16:09:04 +0100 Subject: [PATCH 05/12] chore: localize strings --- .../configuration/string_provider/AuthUIStringProvider.kt | 3 --- .../string_provider/DefaultAuthUIStringProvider.kt | 3 --- .../ui/auth/compose/ui/screens/phone/EnterPhoneNumberUI.kt | 2 +- .../compose/ui/screens/phone/EnterVerificationCodeUI.kt | 6 ++++-- auth/src/main/res/values-ar/strings.xml | 2 +- auth/src/main/res/values-b+es+419/strings.xml | 2 +- auth/src/main/res/values-bg/strings.xml | 2 +- auth/src/main/res/values-bn/strings.xml | 2 +- auth/src/main/res/values-ca/strings.xml | 2 +- auth/src/main/res/values-cs/strings.xml | 2 +- auth/src/main/res/values-da/strings.xml | 2 +- auth/src/main/res/values-de-rAT/strings.xml | 2 +- auth/src/main/res/values-de-rCH/strings.xml | 2 +- auth/src/main/res/values-de/strings.xml | 2 +- auth/src/main/res/values-el/strings.xml | 2 +- auth/src/main/res/values-en-rAU/strings.xml | 2 +- auth/src/main/res/values-en-rCA/strings.xml | 2 +- auth/src/main/res/values-en-rGB/strings.xml | 2 +- auth/src/main/res/values-en-rIE/strings.xml | 2 +- auth/src/main/res/values-en-rIN/strings.xml | 2 +- auth/src/main/res/values-en-rSG/strings.xml | 2 +- auth/src/main/res/values-en-rZA/strings.xml | 2 +- auth/src/main/res/values-es-rAR/strings.xml | 2 +- auth/src/main/res/values-es-rBO/strings.xml | 2 +- auth/src/main/res/values-es-rCL/strings.xml | 2 +- auth/src/main/res/values-es-rCO/strings.xml | 2 +- auth/src/main/res/values-es-rCR/strings.xml | 2 +- auth/src/main/res/values-es-rDO/strings.xml | 2 +- auth/src/main/res/values-es-rEC/strings.xml | 2 +- auth/src/main/res/values-es-rGT/strings.xml | 2 +- auth/src/main/res/values-es-rHN/strings.xml | 2 +- auth/src/main/res/values-es-rMX/strings.xml | 2 +- auth/src/main/res/values-es-rNI/strings.xml | 2 +- auth/src/main/res/values-es-rPA/strings.xml | 2 +- auth/src/main/res/values-es-rPE/strings.xml | 2 +- auth/src/main/res/values-es-rPR/strings.xml | 2 +- auth/src/main/res/values-es-rPY/strings.xml | 2 +- auth/src/main/res/values-es-rSV/strings.xml | 2 +- auth/src/main/res/values-es-rUS/strings.xml | 2 +- auth/src/main/res/values-es-rUY/strings.xml | 2 +- auth/src/main/res/values-es-rVE/strings.xml | 2 +- auth/src/main/res/values-es/strings.xml | 2 +- auth/src/main/res/values-fa/strings.xml | 2 +- auth/src/main/res/values-fi/strings.xml | 2 +- auth/src/main/res/values-fil/strings.xml | 2 +- auth/src/main/res/values-fr-rCH/strings.xml | 2 +- auth/src/main/res/values-fr/strings.xml | 2 +- auth/src/main/res/values-gsw/strings.xml | 2 +- auth/src/main/res/values-gu/strings.xml | 2 +- auth/src/main/res/values-hi/strings.xml | 2 +- auth/src/main/res/values-hr/strings.xml | 2 +- auth/src/main/res/values-hu/strings.xml | 2 +- auth/src/main/res/values-in/strings.xml | 2 +- auth/src/main/res/values-it/strings.xml | 2 +- auth/src/main/res/values-iw/strings.xml | 2 +- auth/src/main/res/values-ja/strings.xml | 2 +- auth/src/main/res/values-kn/strings.xml | 2 +- auth/src/main/res/values-ko/strings.xml | 2 +- auth/src/main/res/values-ln/strings.xml | 2 +- auth/src/main/res/values-lt/strings.xml | 2 +- auth/src/main/res/values-lv/strings.xml | 2 +- auth/src/main/res/values-mo/strings.xml | 2 +- auth/src/main/res/values-mr/strings.xml | 2 +- auth/src/main/res/values-ms/strings.xml | 2 +- auth/src/main/res/values-nb/strings.xml | 2 +- auth/src/main/res/values-nl/strings.xml | 2 +- auth/src/main/res/values-no/strings.xml | 2 +- auth/src/main/res/values-pl/strings.xml | 2 +- auth/src/main/res/values-pt-rBR/strings.xml | 2 +- auth/src/main/res/values-pt-rPT/strings.xml | 2 +- auth/src/main/res/values-pt/strings.xml | 2 +- auth/src/main/res/values-ro/strings.xml | 2 +- auth/src/main/res/values-ru/strings.xml | 2 +- auth/src/main/res/values-sk/strings.xml | 2 +- auth/src/main/res/values-sl/strings.xml | 2 +- auth/src/main/res/values-sr/strings.xml | 2 +- auth/src/main/res/values-sv/strings.xml | 2 +- auth/src/main/res/values-ta/strings.xml | 2 +- auth/src/main/res/values-th/strings.xml | 2 +- auth/src/main/res/values-tl/strings.xml | 2 +- auth/src/main/res/values-tr/strings.xml | 2 +- auth/src/main/res/values-uk/strings.xml | 2 +- auth/src/main/res/values-ur/strings.xml | 2 +- auth/src/main/res/values-vi/strings.xml | 2 +- auth/src/main/res/values-zh-rCN/strings.xml | 2 +- auth/src/main/res/values-zh-rHK/strings.xml | 2 +- auth/src/main/res/values-zh-rTW/strings.xml | 2 +- auth/src/main/res/values-zh/strings.xml | 2 +- auth/src/main/res/values/strings.xml | 2 +- 89 files changed, 90 insertions(+), 94 deletions(-) diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/string_provider/AuthUIStringProvider.kt b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/string_provider/AuthUIStringProvider.kt index 29d13c2de..cede1890f 100644 --- a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/string_provider/AuthUIStringProvider.kt +++ b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/string_provider/AuthUIStringProvider.kt @@ -201,9 +201,6 @@ interface AuthUIStringProvider { /** Verification code hint */ val verificationCodeHint: String - /** Verify code button text */ - val verifyCode: String - /** Change phone number link text */ val changePhoneNumber: String diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/string_provider/DefaultAuthUIStringProvider.kt b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/string_provider/DefaultAuthUIStringProvider.kt index b3a065444..e944c4823 100644 --- a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/string_provider/DefaultAuthUIStringProvider.kt +++ b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/string_provider/DefaultAuthUIStringProvider.kt @@ -195,9 +195,6 @@ class DefaultAuthUIStringProvider( override val verificationCodeHint: String get() = localizedContext.getString(R.string.fui_enter_confirmation_code) - override val verifyCode: String - get() = localizedContext.getString(R.string.fui_verify_phone_number) - override val changePhoneNumber: String get() = localizedContext.getString(R.string.fui_change_phone_number) diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/phone/EnterPhoneNumberUI.kt b/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/phone/EnterPhoneNumberUI.kt index f5d886483..9bb888873 100644 --- a/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/phone/EnterPhoneNumberUI.kt +++ b/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/phone/EnterPhoneNumberUI.kt @@ -90,7 +90,7 @@ fun EnterPhoneNumberUI( topBar = { TopAppBar( title = { - Text(stringProvider.verifyPhoneNumber) + Text(stringProvider.signInWithPhone) }, colors = AuthUITheme.topAppBarColors ) diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/phone/EnterVerificationCodeUI.kt b/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/phone/EnterVerificationCodeUI.kt index ddbe66a6e..00c64d614 100644 --- a/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/phone/EnterVerificationCodeUI.kt +++ b/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/phone/EnterVerificationCodeUI.kt @@ -50,6 +50,7 @@ import com.firebase.ui.auth.compose.configuration.theme.AuthUITheme import com.firebase.ui.auth.compose.configuration.validators.VerificationCodeValidator import com.firebase.ui.auth.compose.ui.components.TermsAndPrivacyForm import com.firebase.ui.auth.compose.ui.components.VerificationCodeInputField +import java.util.Locale @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -135,7 +136,8 @@ fun EnterVerificationCodeUI( text = if (resendTimer > 0) { val minutes = resendTimer / 60 val seconds = resendTimer % 60 - val timeFormatted = "$minutes:${String.format("%02d", seconds)}" + val timeFormatted = + "$minutes:${String.format(Locale.ROOT, "%02d", seconds)}" stringProvider.resendCodeTimer(timeFormatted) } else { stringProvider.resendCode @@ -161,7 +163,7 @@ fun EnterVerificationCodeUI( .size(16.dp) ) } else { - Text(stringProvider.verifyCode.uppercase()) + Text(stringProvider.verifyPhoneNumber.uppercase()) } } } diff --git a/auth/src/main/res/values-ar/strings.xml b/auth/src/main/res/values-ar/strings.xml index c8c79a250..a40372feb 100755 --- a/auth/src/main/res/values-ar/strings.xml +++ b/auth/src/main/res/values-ar/strings.xml @@ -76,7 +76,7 @@ أدخل رقم هاتفك يُرجى إدخال رقم هاتف صالح أدخل الرمز المكوّن من 6 أرقام الذي أرسلناه إلى - إعادة إرسال الرمز بعد 0:%02d + إعادة إرسال الرمز بعد %1$s إثبات ملكية رقم هاتفك جارٍ التحقق… الرمز غير صحيح. يُرجى المحاولة مجددًا. diff --git a/auth/src/main/res/values-b+es+419/strings.xml b/auth/src/main/res/values-b+es+419/strings.xml index ab6c79322..3edd82e8a 100755 --- a/auth/src/main/res/values-b+es+419/strings.xml +++ b/auth/src/main/res/values-b+es+419/strings.xml @@ -76,7 +76,7 @@ Ingresa tu número de teléfono Ingresa un número de teléfono válido Ingresa el código de 6 dígitos que enviamos al número - Se reenviará el código en 0:%02d + Se reenviará el código en %1$s Verifica tu número de teléfono Verificando… Código incorrecto. Vuelve a intentarlo. diff --git a/auth/src/main/res/values-bg/strings.xml b/auth/src/main/res/values-bg/strings.xml index 75feabfb3..8b0588a5d 100755 --- a/auth/src/main/res/values-bg/strings.xml +++ b/auth/src/main/res/values-bg/strings.xml @@ -76,7 +76,7 @@ Въвеждане на телефонния ви номер Въведете валиден телефонен номер Въведете 6-цифрения код, който изпратихме до - Повторно изпращане на кода след 0:%02d + Повторно изпращане на кода след %1$s Потвърждаване на телефонния ви номер Потвърждава се… Неправилен код. Опитайте отново. diff --git a/auth/src/main/res/values-bn/strings.xml b/auth/src/main/res/values-bn/strings.xml index b17938ab4..0cd3dc173 100755 --- a/auth/src/main/res/values-bn/strings.xml +++ b/auth/src/main/res/values-bn/strings.xml @@ -76,7 +76,7 @@ আপনার ফোন নম্বর লিখুন একটি সঠিক ফোন নম্বর লিখুন আমাদের পাঠানো ৬-সংখ্যার কোডটি লিখুন - 0:%02d এ কোডটি আবার পাঠান + %1$s এ কোডটি আবার পাঠান আপনার ফোন নম্বর যাচাই করুন যাচাই করা হচ্ছে… কোডটি ভুল। আবার চেষ্টা করুন। diff --git a/auth/src/main/res/values-ca/strings.xml b/auth/src/main/res/values-ca/strings.xml index 74e3e0d86..504bbbd32 100755 --- a/auth/src/main/res/values-ca/strings.xml +++ b/auth/src/main/res/values-ca/strings.xml @@ -76,7 +76,7 @@ Introdueix el número de telèfon Introdueix un número de telèfon vàlid Introdueix el codi de 6 dígits que s\'ha enviat al número - Torna a enviar el codi d\'aquí a 0:%02d + Torna a enviar el codi d\'aquí a %1$s Verifica el número de telèfon S\'està verificant… El codi no és correcte. Torna-ho a provar. diff --git a/auth/src/main/res/values-cs/strings.xml b/auth/src/main/res/values-cs/strings.xml index 030a7082d..491bf8024 100755 --- a/auth/src/main/res/values-cs/strings.xml +++ b/auth/src/main/res/values-cs/strings.xml @@ -76,7 +76,7 @@ Zadejte své telefonní číslo Zadejte platné telefonní číslo Zadejte šestimístný kód, který jsme vám zaslali - Znovu zaslat kód za 0:%02d + Znovu zaslat kód za %1$s Ověřit telefonní číslo Ověřování… Špatný kód. Zkuste to znovu. diff --git a/auth/src/main/res/values-da/strings.xml b/auth/src/main/res/values-da/strings.xml index 871f3b16e..849ce6796 100755 --- a/auth/src/main/res/values-da/strings.xml +++ b/auth/src/main/res/values-da/strings.xml @@ -76,7 +76,7 @@ Angiv dit telefonnummer Angiv et gyldigt telefonnummer Angiv den 6-cifrede kode, vi sendte til - Send koden igen om 0:%02d + Send koden igen om %1$s Bekræft dit telefonnummer Bekræfter… Koden er forkert. Prøv igen. diff --git a/auth/src/main/res/values-de-rAT/strings.xml b/auth/src/main/res/values-de-rAT/strings.xml index 0552d5986..62b6e1c22 100755 --- a/auth/src/main/res/values-de-rAT/strings.xml +++ b/auth/src/main/res/values-de-rAT/strings.xml @@ -76,7 +76,7 @@ Telefonnummer eingeben Geben Sie eine gültige Telefonnummer ein Geben Sie den 6-stelligen Code ein, der gesendet wurde an - Code in 0:%02d erneut senden + Code in %1$s erneut senden Telefonnummer bestätigen Wird verifiziert… Falscher Code. Versuchen Sie es noch einmal. diff --git a/auth/src/main/res/values-de-rCH/strings.xml b/auth/src/main/res/values-de-rCH/strings.xml index f1158773e..9d8bcf3fa 100755 --- a/auth/src/main/res/values-de-rCH/strings.xml +++ b/auth/src/main/res/values-de-rCH/strings.xml @@ -76,7 +76,7 @@ Telefonnummer eingeben Geben Sie eine gültige Telefonnummer ein Geben Sie den 6-stelligen Code ein, der gesendet wurde an - Code in 0:%02d erneut senden + Code in %1$s erneut senden Telefonnummer bestätigen Wird verifiziert… Falscher Code. Versuchen Sie es noch einmal. diff --git a/auth/src/main/res/values-de/strings.xml b/auth/src/main/res/values-de/strings.xml index 4febb8ee4..9b00b4994 100755 --- a/auth/src/main/res/values-de/strings.xml +++ b/auth/src/main/res/values-de/strings.xml @@ -76,7 +76,7 @@ Telefonnummer eingeben Geben Sie eine gültige Telefonnummer ein Geben Sie den 6-stelligen Code ein, der gesendet wurde an - Code in 0:%02d erneut senden + Code in %1$s erneut senden Telefonnummer bestätigen Wird verifiziert… Falscher Code. Versuchen Sie es noch einmal. diff --git a/auth/src/main/res/values-el/strings.xml b/auth/src/main/res/values-el/strings.xml index 3375225b1..2cff189ed 100755 --- a/auth/src/main/res/values-el/strings.xml +++ b/auth/src/main/res/values-el/strings.xml @@ -76,7 +76,7 @@ Εισαγάγετε τον αριθμό τηλεφώνου σας Καταχωρίστε έναν έγκυρο αριθμό τηλεφώνου Εισαγάγετε τον 6ψήφιο κωδικό που σας στείλαμε στο - Επανάληψη αποστολής κωδικού σε 0:%02d + Επανάληψη αποστολής κωδικού σε %1$s Επαλήθευση του τηλεφώνου σας Επαλήθευση… Εσφαλμένος κωδικός. Δοκιμάστε ξανά. diff --git a/auth/src/main/res/values-en-rAU/strings.xml b/auth/src/main/res/values-en-rAU/strings.xml index 6e912a145..999dae092 100755 --- a/auth/src/main/res/values-en-rAU/strings.xml +++ b/auth/src/main/res/values-en-rAU/strings.xml @@ -76,7 +76,7 @@ Enter your phone number Enter a valid phone number Enter the 6-digit code that we sent to - Resend code in 0:%02d + Resend code in %1$s Verify your phone number Verifying… Wrong code. Try again. diff --git a/auth/src/main/res/values-en-rCA/strings.xml b/auth/src/main/res/values-en-rCA/strings.xml index 647bb4df6..82a7adb95 100755 --- a/auth/src/main/res/values-en-rCA/strings.xml +++ b/auth/src/main/res/values-en-rCA/strings.xml @@ -76,7 +76,7 @@ Enter your phone number Enter a valid phone number Enter the 6-digit code that we sent to - Resend code in 0:%02d + Resend code in %1$s Verify your phone number Verifying… Wrong code. Try again. diff --git a/auth/src/main/res/values-en-rGB/strings.xml b/auth/src/main/res/values-en-rGB/strings.xml index 647bb4df6..82a7adb95 100755 --- a/auth/src/main/res/values-en-rGB/strings.xml +++ b/auth/src/main/res/values-en-rGB/strings.xml @@ -76,7 +76,7 @@ Enter your phone number Enter a valid phone number Enter the 6-digit code that we sent to - Resend code in 0:%02d + Resend code in %1$s Verify your phone number Verifying… Wrong code. Try again. diff --git a/auth/src/main/res/values-en-rIE/strings.xml b/auth/src/main/res/values-en-rIE/strings.xml index 08909c65c..7d28f96c1 100755 --- a/auth/src/main/res/values-en-rIE/strings.xml +++ b/auth/src/main/res/values-en-rIE/strings.xml @@ -76,7 +76,7 @@ Enter your phone number Enter a valid phone number Enter the 6-digit code that we sent to - Resend code in 0:%02d + Resend code in %1$s Verify your phone number Verifying… Wrong code. Try again. diff --git a/auth/src/main/res/values-en-rIN/strings.xml b/auth/src/main/res/values-en-rIN/strings.xml index 08909c65c..7d28f96c1 100755 --- a/auth/src/main/res/values-en-rIN/strings.xml +++ b/auth/src/main/res/values-en-rIN/strings.xml @@ -76,7 +76,7 @@ Enter your phone number Enter a valid phone number Enter the 6-digit code that we sent to - Resend code in 0:%02d + Resend code in %1$s Verify your phone number Verifying… Wrong code. Try again. diff --git a/auth/src/main/res/values-en-rSG/strings.xml b/auth/src/main/res/values-en-rSG/strings.xml index 08909c65c..7d28f96c1 100755 --- a/auth/src/main/res/values-en-rSG/strings.xml +++ b/auth/src/main/res/values-en-rSG/strings.xml @@ -76,7 +76,7 @@ Enter your phone number Enter a valid phone number Enter the 6-digit code that we sent to - Resend code in 0:%02d + Resend code in %1$s Verify your phone number Verifying… Wrong code. Try again. diff --git a/auth/src/main/res/values-en-rZA/strings.xml b/auth/src/main/res/values-en-rZA/strings.xml index 08909c65c..7d28f96c1 100755 --- a/auth/src/main/res/values-en-rZA/strings.xml +++ b/auth/src/main/res/values-en-rZA/strings.xml @@ -76,7 +76,7 @@ Enter your phone number Enter a valid phone number Enter the 6-digit code that we sent to - Resend code in 0:%02d + Resend code in %1$s Verify your phone number Verifying… Wrong code. Try again. diff --git a/auth/src/main/res/values-es-rAR/strings.xml b/auth/src/main/res/values-es-rAR/strings.xml index 4c7090e3d..4846839cb 100755 --- a/auth/src/main/res/values-es-rAR/strings.xml +++ b/auth/src/main/res/values-es-rAR/strings.xml @@ -76,7 +76,7 @@ Ingresa tu número de teléfono Ingresa un número de teléfono válido Ingresa el código de 6 dígitos que enviamos al número - Se reenviará el código en 0:%02d + Se reenviará el código en %1$s Verifica tu número de teléfono Verificando… Código incorrecto. Vuelve a intentarlo. diff --git a/auth/src/main/res/values-es-rBO/strings.xml b/auth/src/main/res/values-es-rBO/strings.xml index 4c7090e3d..4846839cb 100755 --- a/auth/src/main/res/values-es-rBO/strings.xml +++ b/auth/src/main/res/values-es-rBO/strings.xml @@ -76,7 +76,7 @@ Ingresa tu número de teléfono Ingresa un número de teléfono válido Ingresa el código de 6 dígitos que enviamos al número - Se reenviará el código en 0:%02d + Se reenviará el código en %1$s Verifica tu número de teléfono Verificando… Código incorrecto. Vuelve a intentarlo. diff --git a/auth/src/main/res/values-es-rCL/strings.xml b/auth/src/main/res/values-es-rCL/strings.xml index 4c7090e3d..4846839cb 100755 --- a/auth/src/main/res/values-es-rCL/strings.xml +++ b/auth/src/main/res/values-es-rCL/strings.xml @@ -76,7 +76,7 @@ Ingresa tu número de teléfono Ingresa un número de teléfono válido Ingresa el código de 6 dígitos que enviamos al número - Se reenviará el código en 0:%02d + Se reenviará el código en %1$s Verifica tu número de teléfono Verificando… Código incorrecto. Vuelve a intentarlo. diff --git a/auth/src/main/res/values-es-rCO/strings.xml b/auth/src/main/res/values-es-rCO/strings.xml index 4c7090e3d..4846839cb 100755 --- a/auth/src/main/res/values-es-rCO/strings.xml +++ b/auth/src/main/res/values-es-rCO/strings.xml @@ -76,7 +76,7 @@ Ingresa tu número de teléfono Ingresa un número de teléfono válido Ingresa el código de 6 dígitos que enviamos al número - Se reenviará el código en 0:%02d + Se reenviará el código en %1$s Verifica tu número de teléfono Verificando… Código incorrecto. Vuelve a intentarlo. diff --git a/auth/src/main/res/values-es-rCR/strings.xml b/auth/src/main/res/values-es-rCR/strings.xml index 4c7090e3d..4846839cb 100755 --- a/auth/src/main/res/values-es-rCR/strings.xml +++ b/auth/src/main/res/values-es-rCR/strings.xml @@ -76,7 +76,7 @@ Ingresa tu número de teléfono Ingresa un número de teléfono válido Ingresa el código de 6 dígitos que enviamos al número - Se reenviará el código en 0:%02d + Se reenviará el código en %1$s Verifica tu número de teléfono Verificando… Código incorrecto. Vuelve a intentarlo. diff --git a/auth/src/main/res/values-es-rDO/strings.xml b/auth/src/main/res/values-es-rDO/strings.xml index 4c7090e3d..4846839cb 100755 --- a/auth/src/main/res/values-es-rDO/strings.xml +++ b/auth/src/main/res/values-es-rDO/strings.xml @@ -76,7 +76,7 @@ Ingresa tu número de teléfono Ingresa un número de teléfono válido Ingresa el código de 6 dígitos que enviamos al número - Se reenviará el código en 0:%02d + Se reenviará el código en %1$s Verifica tu número de teléfono Verificando… Código incorrecto. Vuelve a intentarlo. diff --git a/auth/src/main/res/values-es-rEC/strings.xml b/auth/src/main/res/values-es-rEC/strings.xml index 4c7090e3d..4846839cb 100755 --- a/auth/src/main/res/values-es-rEC/strings.xml +++ b/auth/src/main/res/values-es-rEC/strings.xml @@ -76,7 +76,7 @@ Ingresa tu número de teléfono Ingresa un número de teléfono válido Ingresa el código de 6 dígitos que enviamos al número - Se reenviará el código en 0:%02d + Se reenviará el código en %1$s Verifica tu número de teléfono Verificando… Código incorrecto. Vuelve a intentarlo. diff --git a/auth/src/main/res/values-es-rGT/strings.xml b/auth/src/main/res/values-es-rGT/strings.xml index 4c7090e3d..4846839cb 100755 --- a/auth/src/main/res/values-es-rGT/strings.xml +++ b/auth/src/main/res/values-es-rGT/strings.xml @@ -76,7 +76,7 @@ Ingresa tu número de teléfono Ingresa un número de teléfono válido Ingresa el código de 6 dígitos que enviamos al número - Se reenviará el código en 0:%02d + Se reenviará el código en %1$s Verifica tu número de teléfono Verificando… Código incorrecto. Vuelve a intentarlo. diff --git a/auth/src/main/res/values-es-rHN/strings.xml b/auth/src/main/res/values-es-rHN/strings.xml index 4c7090e3d..4846839cb 100755 --- a/auth/src/main/res/values-es-rHN/strings.xml +++ b/auth/src/main/res/values-es-rHN/strings.xml @@ -76,7 +76,7 @@ Ingresa tu número de teléfono Ingresa un número de teléfono válido Ingresa el código de 6 dígitos que enviamos al número - Se reenviará el código en 0:%02d + Se reenviará el código en %1$s Verifica tu número de teléfono Verificando… Código incorrecto. Vuelve a intentarlo. diff --git a/auth/src/main/res/values-es-rMX/strings.xml b/auth/src/main/res/values-es-rMX/strings.xml index 4c7090e3d..4846839cb 100755 --- a/auth/src/main/res/values-es-rMX/strings.xml +++ b/auth/src/main/res/values-es-rMX/strings.xml @@ -76,7 +76,7 @@ Ingresa tu número de teléfono Ingresa un número de teléfono válido Ingresa el código de 6 dígitos que enviamos al número - Se reenviará el código en 0:%02d + Se reenviará el código en %1$s Verifica tu número de teléfono Verificando… Código incorrecto. Vuelve a intentarlo. diff --git a/auth/src/main/res/values-es-rNI/strings.xml b/auth/src/main/res/values-es-rNI/strings.xml index 4c7090e3d..4846839cb 100755 --- a/auth/src/main/res/values-es-rNI/strings.xml +++ b/auth/src/main/res/values-es-rNI/strings.xml @@ -76,7 +76,7 @@ Ingresa tu número de teléfono Ingresa un número de teléfono válido Ingresa el código de 6 dígitos que enviamos al número - Se reenviará el código en 0:%02d + Se reenviará el código en %1$s Verifica tu número de teléfono Verificando… Código incorrecto. Vuelve a intentarlo. diff --git a/auth/src/main/res/values-es-rPA/strings.xml b/auth/src/main/res/values-es-rPA/strings.xml index 4c7090e3d..4846839cb 100755 --- a/auth/src/main/res/values-es-rPA/strings.xml +++ b/auth/src/main/res/values-es-rPA/strings.xml @@ -76,7 +76,7 @@ Ingresa tu número de teléfono Ingresa un número de teléfono válido Ingresa el código de 6 dígitos que enviamos al número - Se reenviará el código en 0:%02d + Se reenviará el código en %1$s Verifica tu número de teléfono Verificando… Código incorrecto. Vuelve a intentarlo. diff --git a/auth/src/main/res/values-es-rPE/strings.xml b/auth/src/main/res/values-es-rPE/strings.xml index 4c7090e3d..4846839cb 100755 --- a/auth/src/main/res/values-es-rPE/strings.xml +++ b/auth/src/main/res/values-es-rPE/strings.xml @@ -76,7 +76,7 @@ Ingresa tu número de teléfono Ingresa un número de teléfono válido Ingresa el código de 6 dígitos que enviamos al número - Se reenviará el código en 0:%02d + Se reenviará el código en %1$s Verifica tu número de teléfono Verificando… Código incorrecto. Vuelve a intentarlo. diff --git a/auth/src/main/res/values-es-rPR/strings.xml b/auth/src/main/res/values-es-rPR/strings.xml index 4c7090e3d..4846839cb 100755 --- a/auth/src/main/res/values-es-rPR/strings.xml +++ b/auth/src/main/res/values-es-rPR/strings.xml @@ -76,7 +76,7 @@ Ingresa tu número de teléfono Ingresa un número de teléfono válido Ingresa el código de 6 dígitos que enviamos al número - Se reenviará el código en 0:%02d + Se reenviará el código en %1$s Verifica tu número de teléfono Verificando… Código incorrecto. Vuelve a intentarlo. diff --git a/auth/src/main/res/values-es-rPY/strings.xml b/auth/src/main/res/values-es-rPY/strings.xml index 4c7090e3d..4846839cb 100755 --- a/auth/src/main/res/values-es-rPY/strings.xml +++ b/auth/src/main/res/values-es-rPY/strings.xml @@ -76,7 +76,7 @@ Ingresa tu número de teléfono Ingresa un número de teléfono válido Ingresa el código de 6 dígitos que enviamos al número - Se reenviará el código en 0:%02d + Se reenviará el código en %1$s Verifica tu número de teléfono Verificando… Código incorrecto. Vuelve a intentarlo. diff --git a/auth/src/main/res/values-es-rSV/strings.xml b/auth/src/main/res/values-es-rSV/strings.xml index 4c7090e3d..4846839cb 100755 --- a/auth/src/main/res/values-es-rSV/strings.xml +++ b/auth/src/main/res/values-es-rSV/strings.xml @@ -76,7 +76,7 @@ Ingresa tu número de teléfono Ingresa un número de teléfono válido Ingresa el código de 6 dígitos que enviamos al número - Se reenviará el código en 0:%02d + Se reenviará el código en %1$s Verifica tu número de teléfono Verificando… Código incorrecto. Vuelve a intentarlo. diff --git a/auth/src/main/res/values-es-rUS/strings.xml b/auth/src/main/res/values-es-rUS/strings.xml index 4c7090e3d..4846839cb 100755 --- a/auth/src/main/res/values-es-rUS/strings.xml +++ b/auth/src/main/res/values-es-rUS/strings.xml @@ -76,7 +76,7 @@ Ingresa tu número de teléfono Ingresa un número de teléfono válido Ingresa el código de 6 dígitos que enviamos al número - Se reenviará el código en 0:%02d + Se reenviará el código en %1$s Verifica tu número de teléfono Verificando… Código incorrecto. Vuelve a intentarlo. diff --git a/auth/src/main/res/values-es-rUY/strings.xml b/auth/src/main/res/values-es-rUY/strings.xml index 4c7090e3d..4846839cb 100755 --- a/auth/src/main/res/values-es-rUY/strings.xml +++ b/auth/src/main/res/values-es-rUY/strings.xml @@ -76,7 +76,7 @@ Ingresa tu número de teléfono Ingresa un número de teléfono válido Ingresa el código de 6 dígitos que enviamos al número - Se reenviará el código en 0:%02d + Se reenviará el código en %1$s Verifica tu número de teléfono Verificando… Código incorrecto. Vuelve a intentarlo. diff --git a/auth/src/main/res/values-es-rVE/strings.xml b/auth/src/main/res/values-es-rVE/strings.xml index 4c7090e3d..4846839cb 100755 --- a/auth/src/main/res/values-es-rVE/strings.xml +++ b/auth/src/main/res/values-es-rVE/strings.xml @@ -76,7 +76,7 @@ Ingresa tu número de teléfono Ingresa un número de teléfono válido Ingresa el código de 6 dígitos que enviamos al número - Se reenviará el código en 0:%02d + Se reenviará el código en %1$s Verifica tu número de teléfono Verificando… Código incorrecto. Vuelve a intentarlo. diff --git a/auth/src/main/res/values-es/strings.xml b/auth/src/main/res/values-es/strings.xml index b3ce2fcd0..1b70bffc5 100755 --- a/auth/src/main/res/values-es/strings.xml +++ b/auth/src/main/res/values-es/strings.xml @@ -76,7 +76,7 @@ Introduce tu número de teléfono Introduce un número de teléfono válido Introduce el código de seis dígitos que hemos enviado a - Volver a enviar el código en 0:%02d + Volver a enviar el código en %1$s Verificar el número de teléfono Verificando… El código es incorrecto. Vuelve a intentarlo. diff --git a/auth/src/main/res/values-fa/strings.xml b/auth/src/main/res/values-fa/strings.xml index 331b79fa3..b3a5536af 100755 --- a/auth/src/main/res/values-fa/strings.xml +++ b/auth/src/main/res/values-fa/strings.xml @@ -76,7 +76,7 @@ شماره تلفن خود را وارد کنید شماره تلفن معتبری وارد کنید وارد کردن کد ۶ رقمی ارسال‌شده به - کد پس از %02d:0 مجدداً ارسال می‌شود + کد پس از %1$s:0 مجدداً ارسال می‌شود تأیید شماره تلفن درحال تأیید… کد اشتباه است. دوباره امتحان کنید. diff --git a/auth/src/main/res/values-fi/strings.xml b/auth/src/main/res/values-fi/strings.xml index ff346063d..d7002de49 100755 --- a/auth/src/main/res/values-fi/strings.xml +++ b/auth/src/main/res/values-fi/strings.xml @@ -76,7 +76,7 @@ Anna puhelinnumerosi Anna voimassa oleva puhelinnumero. Anna 6 merkin pituinen koodi, jonka lähetimme numeroon - Lähetä koodi uudelleen seuraavan ajan kuluttua: 0:%02d. + Lähetä koodi uudelleen seuraavan ajan kuluttua: %1$s. Puhelinnumeron vahvistaminen Vahvistetaan… Väärä koodi. Yritä uudelleen. diff --git a/auth/src/main/res/values-fil/strings.xml b/auth/src/main/res/values-fil/strings.xml index 0915df67d..565e2b8ec 100755 --- a/auth/src/main/res/values-fil/strings.xml +++ b/auth/src/main/res/values-fil/strings.xml @@ -76,7 +76,7 @@ Ilagay ang numero ng iyong telepono Maglagay ng wastong numero ng telepono Ilagay ang 6-digit na code na ipinadala namin sa - Ipadala muli ang code sa loob ng 0:%02d + Ipadala muli ang code sa loob ng %1$s I-verify ang numero ng iyong telepono Bine-verify… Maling code. Subukang muli. diff --git a/auth/src/main/res/values-fr-rCH/strings.xml b/auth/src/main/res/values-fr-rCH/strings.xml index fac959937..1b7f04540 100755 --- a/auth/src/main/res/values-fr-rCH/strings.xml +++ b/auth/src/main/res/values-fr-rCH/strings.xml @@ -76,7 +76,7 @@ Saisissez votre numéro de téléphone Saisissez un numéro de téléphone valide Saisissez le code à six chiffres envoyé au - Renvoyer le code dans 0:%02d + Renvoyer le code dans %1$s Valider votre numéro de téléphone Validation… Code erroné. Veuillez réessayer. diff --git a/auth/src/main/res/values-fr/strings.xml b/auth/src/main/res/values-fr/strings.xml index 8fe2bb073..8c4d981df 100755 --- a/auth/src/main/res/values-fr/strings.xml +++ b/auth/src/main/res/values-fr/strings.xml @@ -76,7 +76,7 @@ Saisissez votre numéro de téléphone Saisissez un numéro de téléphone valide Saisissez le code à six chiffres envoyé au - Renvoyer le code dans 0:%02d + Renvoyer le code dans %1$s Valider votre numéro de téléphone Validation… Code erroné. Veuillez réessayer. diff --git a/auth/src/main/res/values-gsw/strings.xml b/auth/src/main/res/values-gsw/strings.xml index 3c8d012ed..01402e26e 100755 --- a/auth/src/main/res/values-gsw/strings.xml +++ b/auth/src/main/res/values-gsw/strings.xml @@ -76,7 +76,7 @@ Telefonnummer eingeben Geben Sie eine gültige Telefonnummer ein Geben Sie den 6-stelligen Code ein, der gesendet wurde an - Code in 0:%02d erneut senden + Code in %1$s erneut senden Telefonnummer bestätigen Wird verifiziert… Falscher Code. Versuchen Sie es noch einmal. diff --git a/auth/src/main/res/values-gu/strings.xml b/auth/src/main/res/values-gu/strings.xml index 12aa15d72..7530c4c01 100755 --- a/auth/src/main/res/values-gu/strings.xml +++ b/auth/src/main/res/values-gu/strings.xml @@ -76,7 +76,7 @@ તમારો ફોન નંબર દાખલ કરો એક માન્ય ફોન નંબર દાખલ કરો અમે આ ફોન નંબર પર મોકલેલ 6-અંકનો કોડ દાખલ કરો - 0 માં કોડ ફરીથી મોકલો:%02d + 0 માં કોડ ફરીથી મોકલો:%1$s તમારો ફોન નંબર ચકાસો ચકાસી રહ્યાં છીએ… કોડ ખોટો છે. ફરી પ્રયાસ કરો. diff --git a/auth/src/main/res/values-hi/strings.xml b/auth/src/main/res/values-hi/strings.xml index 09194051e..e185c5e69 100755 --- a/auth/src/main/res/values-hi/strings.xml +++ b/auth/src/main/res/values-hi/strings.xml @@ -76,7 +76,7 @@ अपना फ़ोन नंबर डालें कोई मान्य फ़ोन नंबर डालें हमारी ओर से भेजा गया 6-अंकों वाला कोड डालें - 0:%02d में कोड फिर से भेजें + %1$s में कोड फिर से भेजें अपने फ़ोन नंबर की पुष्टि करें पुष्टि की जा रही है… गलत कोड. फिर से कोशिश करें. diff --git a/auth/src/main/res/values-hr/strings.xml b/auth/src/main/res/values-hr/strings.xml index 9f799d3cd..34a0fb8c6 100755 --- a/auth/src/main/res/values-hr/strings.xml +++ b/auth/src/main/res/values-hr/strings.xml @@ -76,7 +76,7 @@ Unesite telefonski broj Unesite važeći telefonski broj Unesite 6-znamenkasti kôd koji smo poslali na broj - Ponovno slanje koda za 0:%02d + Ponovno slanje koda za %1$s Potvrda telefonskog broja Potvrđivanje… Pogrešan kôd. Pokušajte ponovo. diff --git a/auth/src/main/res/values-hu/strings.xml b/auth/src/main/res/values-hu/strings.xml index 879234e03..120335ad6 100755 --- a/auth/src/main/res/values-hu/strings.xml +++ b/auth/src/main/res/values-hu/strings.xml @@ -76,7 +76,7 @@ Adja meg telefonszámát Érvényes telefonszámot adjon meg. Adja meg a telefonszámra elküldött 6 számjegyű kódot - Kód újraküldése ennyi idő elteltével: 0:%02d + Kód újraküldése ennyi idő elteltével: %1$s Telefonszám igazolása Ellenőrzés… Hibás kód. Próbálja újra. diff --git a/auth/src/main/res/values-in/strings.xml b/auth/src/main/res/values-in/strings.xml index d74dcfcc2..f9544c542 100755 --- a/auth/src/main/res/values-in/strings.xml +++ b/auth/src/main/res/values-in/strings.xml @@ -76,7 +76,7 @@ Masukkan nomor telepon Anda Masukkan nomor telepon yang valid Masukkan kode 6 digit yang kami kirimkan ke - Kirimkan ulang kode dalam 0.%02d + Kirimkan ulang kode dalam 0.%1$s Verifikasi nomor telepon Anda Memverifikasi… Kode salah. Coba lagi. diff --git a/auth/src/main/res/values-it/strings.xml b/auth/src/main/res/values-it/strings.xml index bbd79dffd..3607513a6 100755 --- a/auth/src/main/res/values-it/strings.xml +++ b/auth/src/main/res/values-it/strings.xml @@ -76,7 +76,7 @@ Inserisci il numero di telefono Inserisci un numero di telefono valido Inserisci il codice a 6 cifre che abbiamo inviato al numero - Invia di nuovo il codice tra 0:%02d + Invia di nuovo il codice tra %1$s Verifica il numero di telefono Verifica… Codice errato. Riprova. diff --git a/auth/src/main/res/values-iw/strings.xml b/auth/src/main/res/values-iw/strings.xml index 76ecde56b..5af806ada 100755 --- a/auth/src/main/res/values-iw/strings.xml +++ b/auth/src/main/res/values-iw/strings.xml @@ -76,7 +76,7 @@ הזן את מספר הטלפון שלך מספר הטלפון שהזנת לא תקין הזן את הקוד בן 6 הספרות ששלחנו אל - שולח קוד חדש בעוד %02d:0 + שולח קוד חדש בעוד %1$s:0 אמת את מספר הטלפון מאמת… הקוד שגוי. נסה שוב. diff --git a/auth/src/main/res/values-ja/strings.xml b/auth/src/main/res/values-ja/strings.xml index f2723d81f..1c4cbcaa2 100755 --- a/auth/src/main/res/values-ja/strings.xml +++ b/auth/src/main/res/values-ja/strings.xml @@ -76,7 +76,7 @@ 電話番号を入力してください 有効な電話番号を入力してください 送信された 6 桁のコードを入力してください - 0:%02d 秒後にコードを再送信します + %1$s 秒後にコードを再送信します 電話番号を確認 確認しています… コードが間違っています。もう一度お試しください。 diff --git a/auth/src/main/res/values-kn/strings.xml b/auth/src/main/res/values-kn/strings.xml index a087b41a5..896112312 100755 --- a/auth/src/main/res/values-kn/strings.xml +++ b/auth/src/main/res/values-kn/strings.xml @@ -76,7 +76,7 @@ ನಿಮ್ಮ ಫೋನ್‌ ಸಂಖ್ಯೆಯನ್ನು ನಮೂದಿಸಿ ಮಾನ್ಯವಾದ ಫೋನ್ ಸಂಖ್ಯೆಯನ್ನು ನಮೂದಿಸಿ ನಾವು ಕಳುಹಿಸಿರುವ 6 ಅಂಕಿಯ ಕೋಡ್‌ ನಮೂದಿಸಿ - ಇಷ್ಟರ ಒಳಗೆ ಕೋಡ್ ಮತ್ತೆ ಕಳುಹಿಸಿ 0:%02d + ಇಷ್ಟರ ಒಳಗೆ ಕೋಡ್ ಮತ್ತೆ ಕಳುಹಿಸಿ %1$s ನಿಮ್ಮ ಪೋನ್‌ ಸಂಖ್ಯೆಯನ್ನು ಪರಿಶೀಲಿಸಿ ಪರಿಶೀಲಿಸಲಾಗುತ್ತಿದೆ… ಕೋಡ್ ತಪ್ಪಾಗಿದೆ. ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ. diff --git a/auth/src/main/res/values-ko/strings.xml b/auth/src/main/res/values-ko/strings.xml index b0a000b37..a0e25b6e6 100755 --- a/auth/src/main/res/values-ko/strings.xml +++ b/auth/src/main/res/values-ko/strings.xml @@ -76,7 +76,7 @@ 전화번호 입력 올바른 전화번호를 입력하세요. 전송된 6자리 코드를 입력하세요. - 0:%02d 후에 코드 재전송 + %1$s 후에 코드 재전송 전화번호 인증 인증 중… 코드가 잘못되었습니다. 다시 시도하세요. diff --git a/auth/src/main/res/values-ln/strings.xml b/auth/src/main/res/values-ln/strings.xml index 718924292..967967392 100755 --- a/auth/src/main/res/values-ln/strings.xml +++ b/auth/src/main/res/values-ln/strings.xml @@ -76,7 +76,7 @@ Saisissez votre numéro de téléphone Saisissez un numéro de téléphone valide Saisissez le code à six chiffres envoyé au - Renvoyer le code dans 0:%02d + Renvoyer le code dans %1$s Valider votre numéro de téléphone Validation… Code erroné. Veuillez réessayer. diff --git a/auth/src/main/res/values-lt/strings.xml b/auth/src/main/res/values-lt/strings.xml index 2ea648cb6..ba1ad0587 100755 --- a/auth/src/main/res/values-lt/strings.xml +++ b/auth/src/main/res/values-lt/strings.xml @@ -76,7 +76,7 @@ Įveskite telefono numerį Įveskite tinkamą telefono numerį Įveskite 6 skaitmenų kodą, kurį išsiuntėme - Siųsti kodą iš naujo po 0:%02d + Siųsti kodą iš naujo po %1$s Patvirtinti telefono numerį Patvirtinama… Klaidingas kodas. Bandykite dar kartą. diff --git a/auth/src/main/res/values-lv/strings.xml b/auth/src/main/res/values-lv/strings.xml index 1449ac2c0..cc58f23e2 100755 --- a/auth/src/main/res/values-lv/strings.xml +++ b/auth/src/main/res/values-lv/strings.xml @@ -76,7 +76,7 @@ Ievadiet savu tālruņa numuru Ievadiet derīgu tālruņa numuru Ievadiet 6 ciparu kodu, ko nosūtījām - Vēlreiz nosūtīt kodu pēc 0:%02d + Vēlreiz nosūtīt kodu pēc %1$s Verificēt tālruņa numuru Notiek verifikācija… Nepareizs kods. Mēģiniet vēlreiz. diff --git a/auth/src/main/res/values-mo/strings.xml b/auth/src/main/res/values-mo/strings.xml index 278e0f228..59672d971 100755 --- a/auth/src/main/res/values-mo/strings.xml +++ b/auth/src/main/res/values-mo/strings.xml @@ -76,7 +76,7 @@ Introduceți numărul de telefon Introduceți un număr valid de telefon Introduceți codul din 6 cifre pe care l-am trimis la - Retrimiteți codul în 00:%02d + Retrimiteți codul în 0%1$s Confirmați numărul de telefon Se verifică… Cod greșit. Încercați din nou. diff --git a/auth/src/main/res/values-mr/strings.xml b/auth/src/main/res/values-mr/strings.xml index 6a3c84374..05b1cf76f 100755 --- a/auth/src/main/res/values-mr/strings.xml +++ b/auth/src/main/res/values-mr/strings.xml @@ -76,7 +76,7 @@ तुमचा फोन नंबर टाका कृपया वैध फोन नंबर टाका वर आम्ही पाठवलेला 6 अंकी कोड टाका - कोड 0:%02dमध्ये पुन्हा पाठवा + कोड %1$sमध्ये पुन्हा पाठवा तुमच्या फोन नंबरची पडताळणी करा पडताळणी करत आहे… कोड चुकीचा आहे. पुन्हा प्रयत्न करा. diff --git a/auth/src/main/res/values-ms/strings.xml b/auth/src/main/res/values-ms/strings.xml index 03930d48a..a2e5727a0 100755 --- a/auth/src/main/res/values-ms/strings.xml +++ b/auth/src/main/res/values-ms/strings.xml @@ -76,7 +76,7 @@ Masukkan nombor telefon anda Masukkan nombor telefon yang sah Masukkan kod 6 digit yang kami hantar ke - Hantar semula kod dalam 0:%02d + Hantar semula kod dalam %1$s Sahkan nombor telefon anda Mengesahkan… Kod salah. Cuba lagi. diff --git a/auth/src/main/res/values-nb/strings.xml b/auth/src/main/res/values-nb/strings.xml index 9d690a5f8..cec4fa26f 100755 --- a/auth/src/main/res/values-nb/strings.xml +++ b/auth/src/main/res/values-nb/strings.xml @@ -76,7 +76,7 @@ Oppgi telefonnummeret ditt Oppgi et gyldig telefonnummer Oppgi den 6-sifrede koden vi sendte til - Send koden på nytt om 0:%02d + Send koden på nytt om %1$s Bekreft telefonnummeret ditt Bekrefter… Feil kode. Prøv på nytt. diff --git a/auth/src/main/res/values-nl/strings.xml b/auth/src/main/res/values-nl/strings.xml index be8d2b21b..df6e6e0b4 100755 --- a/auth/src/main/res/values-nl/strings.xml +++ b/auth/src/main/res/values-nl/strings.xml @@ -76,7 +76,7 @@ Voer uw telefoonnummer in Voer een geldig telefoonnummer in Voer de zescijferige code in die we hebben verzonden naar - Code opnieuw verzenden over 0:%02d + Code opnieuw verzenden over %1$s Uw telefoonnummer verifiëren Verifiëren… Onjuiste code. Probeer het opnieuw. diff --git a/auth/src/main/res/values-no/strings.xml b/auth/src/main/res/values-no/strings.xml index b20606d53..5a6ad671b 100755 --- a/auth/src/main/res/values-no/strings.xml +++ b/auth/src/main/res/values-no/strings.xml @@ -76,7 +76,7 @@ Oppgi telefonnummeret ditt Oppgi et gyldig telefonnummer Oppgi den 6-sifrede koden vi sendte til - Send koden på nytt om 0:%02d + Send koden på nytt om %1$s Bekreft telefonnummeret ditt Bekrefter… Feil kode. Prøv på nytt. diff --git a/auth/src/main/res/values-pl/strings.xml b/auth/src/main/res/values-pl/strings.xml index 664c11127..c7b61a38c 100755 --- a/auth/src/main/res/values-pl/strings.xml +++ b/auth/src/main/res/values-pl/strings.xml @@ -76,7 +76,7 @@ Wpisz numer telefonu Wpisz prawidłowy numer telefonu Wpisz sześciocyfrowy kod, który wysłaliśmy na numer - Kod można ponownie wysłać za 0:%02d + Kod można ponownie wysłać za %1$s Zweryfikuj swój numer telefonu Weryfikuję… Nieprawidłowy kod. Spróbuj jeszcze raz. diff --git a/auth/src/main/res/values-pt-rBR/strings.xml b/auth/src/main/res/values-pt-rBR/strings.xml index eee868828..8993fc5ff 100755 --- a/auth/src/main/res/values-pt-rBR/strings.xml +++ b/auth/src/main/res/values-pt-rBR/strings.xml @@ -76,7 +76,7 @@ Insira o número do seu telefone Insira um número de telefone válido Insira o código de 6 dígitos enviado para - Reenviar código em 0:%02d + Reenviar código em %1$s Confirmar seu número de telefone Verificando… Código incorreto. Tente novamente. diff --git a/auth/src/main/res/values-pt-rPT/strings.xml b/auth/src/main/res/values-pt-rPT/strings.xml index c3da239ea..2828db9ac 100755 --- a/auth/src/main/res/values-pt-rPT/strings.xml +++ b/auth/src/main/res/values-pt-rPT/strings.xml @@ -76,7 +76,7 @@ Introduza o seu número de telefone Introduza um número de telefone válido Introduza o código de 6 dígitos que enviámos para - Reenviar código em 0:%02d + Reenviar código em %1$s Validar o número de telefone A validar… Código errado. Tente novamente. diff --git a/auth/src/main/res/values-pt/strings.xml b/auth/src/main/res/values-pt/strings.xml index 7febe8d88..535aa857d 100755 --- a/auth/src/main/res/values-pt/strings.xml +++ b/auth/src/main/res/values-pt/strings.xml @@ -76,7 +76,7 @@ Insira o número do seu telefone Insira um número de telefone válido Insira o código de 6 dígitos enviado para - Reenviar código em 0:%02d + Reenviar código em %1$s Confirmar seu número de telefone Verificando… Código incorreto. Tente novamente. diff --git a/auth/src/main/res/values-ro/strings.xml b/auth/src/main/res/values-ro/strings.xml index 99dd4f10d..c879eaf43 100755 --- a/auth/src/main/res/values-ro/strings.xml +++ b/auth/src/main/res/values-ro/strings.xml @@ -76,7 +76,7 @@ Introduceți numărul de telefon Introduceți un număr valid de telefon Introduceți codul din 6 cifre pe care l-am trimis la - Retrimiteți codul în 00:%02d + Retrimiteți codul în 0%1$s Confirmați numărul de telefon Se verifică… Cod greșit. Încercați din nou. diff --git a/auth/src/main/res/values-ru/strings.xml b/auth/src/main/res/values-ru/strings.xml index 5e38eb081..850d7cdf7 100755 --- a/auth/src/main/res/values-ru/strings.xml +++ b/auth/src/main/res/values-ru/strings.xml @@ -76,7 +76,7 @@ Введите номер телефона Введите действительный номер телефона. Введите 6-значный код, отправленный на номер - Отправить код ещё раз можно будет через 0:%02d. + Отправить код ещё раз можно будет через %1$s. Подтвердить номер телефона Проверка… Неверный код. Повторите попытку. diff --git a/auth/src/main/res/values-sk/strings.xml b/auth/src/main/res/values-sk/strings.xml index 14ec23c21..46cc93460 100755 --- a/auth/src/main/res/values-sk/strings.xml +++ b/auth/src/main/res/values-sk/strings.xml @@ -76,7 +76,7 @@ Zadajte svoje telefónne číslo Zadajte platné telefónne číslo Zadajte 6-ciferný kód, ktorý sme odoslali na adresu - Kód sa znova odošle o 0:%02d + Kód sa znova odošle o %1$s Overiť telefónne číslo Overuje sa… Nesprávny kód. Skúste to znova. diff --git a/auth/src/main/res/values-sl/strings.xml b/auth/src/main/res/values-sl/strings.xml index ef84b5ae6..236704e03 100755 --- a/auth/src/main/res/values-sl/strings.xml +++ b/auth/src/main/res/values-sl/strings.xml @@ -76,7 +76,7 @@ Vnesite telefonsko številko Vnesite veljavno telefonsko številko Vnesite 6-mestno kodo, ki smo jo poslali na številko - Ponovno pošlji kodo čez 0:%02d + Ponovno pošlji kodo čez %1$s Preverjanje telefonske številke Preverjanje… Napačna koda. Poskusite znova. diff --git a/auth/src/main/res/values-sr/strings.xml b/auth/src/main/res/values-sr/strings.xml index fd87c0f6c..eceecae82 100755 --- a/auth/src/main/res/values-sr/strings.xml +++ b/auth/src/main/res/values-sr/strings.xml @@ -76,7 +76,7 @@ Унесите број телефона Унесите важећи број телефона Унесите 6-цифрени кôд који смо послали на - Кôд ће бити поново послат за 0:%02d + Кôд ће бити поново послат за %1$s Верификујте број телефона Верификује се… Кôд није тачан. Пробајте поново. diff --git a/auth/src/main/res/values-sv/strings.xml b/auth/src/main/res/values-sv/strings.xml index d95ebe57d..6818bee0e 100755 --- a/auth/src/main/res/values-sv/strings.xml +++ b/auth/src/main/res/values-sv/strings.xml @@ -76,7 +76,7 @@ Ange ditt telefonnummer Ange ett giltigt telefonnummer Ange den sexsiffriga koden vi skickade till - Skicka kod igen om 0:%02d + Skicka kod igen om %1$s Verifiera ditt telefonnummer Verifierar … Fel kod. Försök igen. diff --git a/auth/src/main/res/values-ta/strings.xml b/auth/src/main/res/values-ta/strings.xml index ca01da64d..21ed2d82e 100755 --- a/auth/src/main/res/values-ta/strings.xml +++ b/auth/src/main/res/values-ta/strings.xml @@ -76,7 +76,7 @@ ஃபோன் எண்ணை உள்ளிடவும் சரியான ஃபோன் எண்ணை உள்ளிடவும் இந்த எண்ணுக்கு நாங்கள் அனுப்பிய 6 இலக்கக் குறியீட்டை உள்ளிடவும்: - குறியீட்டை 0:%02d நேரத்தில் மீண்டும் அனுப்பவும் + குறியீட்டை %1$s நேரத்தில் மீண்டும் அனுப்பவும் ஃபோன் எண்ணைச் சரிபார் சரிபார்க்கிறது… தவறான குறியீடு. மீண்டும் முயலவும். diff --git a/auth/src/main/res/values-th/strings.xml b/auth/src/main/res/values-th/strings.xml index 650ecb602..e656fbdb2 100755 --- a/auth/src/main/res/values-th/strings.xml +++ b/auth/src/main/res/values-th/strings.xml @@ -76,7 +76,7 @@ ป้อนหมายเลขโทรศัพท์ของคุณ ป้อนหมายเลขโทรศัพท์ที่ถูกต้อง ป้อนรหัส 6 หลักที่เราส่งไปยัง - ส่งรหัสอีกครั้งใน 0:%02d + ส่งรหัสอีกครั้งใน %1$s ยืนยันหมายเลขโทรศัพท์ กำลังยืนยัน… รหัสไม่ถูกต้อง โปรดลองอีกครั้ง diff --git a/auth/src/main/res/values-tl/strings.xml b/auth/src/main/res/values-tl/strings.xml index d57c8cedf..61bd40ef2 100755 --- a/auth/src/main/res/values-tl/strings.xml +++ b/auth/src/main/res/values-tl/strings.xml @@ -76,7 +76,7 @@ Ilagay ang numero ng iyong telepono Maglagay ng wastong numero ng telepono Ilagay ang 6-digit na code na ipinadala namin sa - Ipadala muli ang code sa loob ng 0:%02d + Ipadala muli ang code sa loob ng %1$s I-verify ang numero ng iyong telepono Bine-verify… Maling code. Subukang muli. diff --git a/auth/src/main/res/values-tr/strings.xml b/auth/src/main/res/values-tr/strings.xml index 71d261569..15604a3a1 100755 --- a/auth/src/main/res/values-tr/strings.xml +++ b/auth/src/main/res/values-tr/strings.xml @@ -76,7 +76,7 @@ Telefon numaranızı girin Geçerli bir telefon numarası girin Şu telefon numarasına gönderdiğimiz 6 haneli kodu girin: - 0:%02d içinde kodu yeniden gönder + %1$s içinde kodu yeniden gönder Telefon numaranızı doğrulayın Doğrulanıyor… Yanlış kod. Tekrar deneyin. diff --git a/auth/src/main/res/values-uk/strings.xml b/auth/src/main/res/values-uk/strings.xml index 120d09fac..148c8b2ad 100755 --- a/auth/src/main/res/values-uk/strings.xml +++ b/auth/src/main/res/values-uk/strings.xml @@ -76,7 +76,7 @@ Введіть свій номер телефону Введіть дійсний номер телефону Введіть 6-значний код, який ми надіслали на номер - Повторно надіслати код через 0:%02d + Повторно надіслати код через %1$s Підтвердити номер телефону Підтвердження… Неправильний код. Повторіть спробу. diff --git a/auth/src/main/res/values-ur/strings.xml b/auth/src/main/res/values-ur/strings.xml index c82d67f82..0dd801265 100755 --- a/auth/src/main/res/values-ur/strings.xml +++ b/auth/src/main/res/values-ur/strings.xml @@ -76,7 +76,7 @@ اپنا فون نمبر درج کریں براہ کرم ایک درست فون نمبر درج کریں ہماری جانب سے حسبِ ذیل کو بھیجا گیا 6 عدد کا کوڈ درج کریں - 0:%02d میں دوبارہ کوڈ بھیجیں + %1$s میں دوبارہ کوڈ بھیجیں اپنے فون نمبر کی توثیق کریں توثیق ہو رہی ہے… غلط کوڈ۔ دوبارہ کوشش کریں۔ diff --git a/auth/src/main/res/values-vi/strings.xml b/auth/src/main/res/values-vi/strings.xml index 50738036f..4c977fc36 100755 --- a/auth/src/main/res/values-vi/strings.xml +++ b/auth/src/main/res/values-vi/strings.xml @@ -76,7 +76,7 @@ Nhập số điện thoại của bạn Nhập số điện thoại hợp lệ Nhập mã 6 chữ số mà chúng tôi đã gửi cho bạn - Gửi lại mã sau 0:%02d + Gửi lại mã sau %1$s Xác minh số điện thoại của bạn Đang xác minh… Mã không chính xác. Hãy thử lại. diff --git a/auth/src/main/res/values-zh-rCN/strings.xml b/auth/src/main/res/values-zh-rCN/strings.xml index 5a62bc5de..354346b11 100755 --- a/auth/src/main/res/values-zh-rCN/strings.xml +++ b/auth/src/main/res/values-zh-rCN/strings.xml @@ -76,7 +76,7 @@ 输入您的电话号码 请输入有效的电话号码 输入我们发送至以下电话号码的 6 位数验证码 - %02d 后可重新发送验证码 + %1$s 后可重新发送验证码 验证您的电话号码 正在验证… 验证码有误,请重试。 diff --git a/auth/src/main/res/values-zh-rHK/strings.xml b/auth/src/main/res/values-zh-rHK/strings.xml index ec81ac859..71f9afa4c 100755 --- a/auth/src/main/res/values-zh-rHK/strings.xml +++ b/auth/src/main/res/values-zh-rHK/strings.xml @@ -76,7 +76,7 @@ 請輸入您的電話號碼 請輸入有效的電話號碼 請輸入傳送至以下電話號碼的 6 位數驗證碼 - 0:%02d 秒後將重新發送驗證碼 + %1$s 秒後將重新發送驗證碼 驗證您的電話號碼 驗證中… 驗證碼錯誤,請再試一次。 diff --git a/auth/src/main/res/values-zh-rTW/strings.xml b/auth/src/main/res/values-zh-rTW/strings.xml index 6e9efae10..1046cb147 100755 --- a/auth/src/main/res/values-zh-rTW/strings.xml +++ b/auth/src/main/res/values-zh-rTW/strings.xml @@ -76,7 +76,7 @@ 請輸入您的電話號碼 請輸入有效的電話號碼 請輸入傳送至以下電話號碼的 6 位數驗證碼 - 0:%02d 秒後將重新發送驗證碼 + %1$s 秒後將重新發送驗證碼 驗證您的電話號碼 驗證中… 驗證碼錯誤,請再試一次。 diff --git a/auth/src/main/res/values-zh/strings.xml b/auth/src/main/res/values-zh/strings.xml index 0be8a8910..521a658be 100755 --- a/auth/src/main/res/values-zh/strings.xml +++ b/auth/src/main/res/values-zh/strings.xml @@ -76,7 +76,7 @@ 输入您的电话号码 请输入有效的电话号码 输入我们发送至以下电话号码的 6 位数验证码 - %02d 后可重新发送验证码 + %1$s 后可重新发送验证码 验证您的电话号码 正在验证… 验证码有误,请重试。 diff --git a/auth/src/main/res/values/strings.xml b/auth/src/main/res/values/strings.xml index 8f074b868..f784d4d4e 100644 --- a/auth/src/main/res/values/strings.xml +++ b/auth/src/main/res/values/strings.xml @@ -129,7 +129,7 @@ Enter your phone number Enter a valid phone number Enter the 6-digit code we sent to - Resend code in %s + Resend code in %1$s Verify your phone number Verifying… Wrong code. Try again. From 7479f58a1103d1c5fb5a0942f03b0489d09669b3 Mon Sep 17 00:00:00 2001 From: Ademola Fadumo Date: Wed, 15 Oct 2025 16:17:26 +0100 Subject: [PATCH 06/12] chore: firebase auth image (temp) --- .../com/firebase/composeapp/MainActivity.kt | 2 +- .../res/drawable-hdpi/firebase_auth_120dp.png | Bin 0 -> 14632 bytes .../res/drawable-mdpi/firebase_auth_120dp.png | Bin 0 -> 7189 bytes .../res/drawable-xhdpi/firebase_auth_120dp.png | Bin 0 -> 15823 bytes .../res/drawable-xxhdpi/firebase_auth_120dp.png | Bin 0 -> 31998 bytes .../drawable-xxxhdpi/firebase_auth_120dp.png | Bin 0 -> 35146 bytes 6 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 composeapp/src/main/res/drawable-hdpi/firebase_auth_120dp.png create mode 100644 composeapp/src/main/res/drawable-mdpi/firebase_auth_120dp.png create mode 100644 composeapp/src/main/res/drawable-xhdpi/firebase_auth_120dp.png create mode 100644 composeapp/src/main/res/drawable-xxhdpi/firebase_auth_120dp.png create mode 100644 composeapp/src/main/res/drawable-xxxhdpi/firebase_auth_120dp.png diff --git a/composeapp/src/main/java/com/firebase/composeapp/MainActivity.kt b/composeapp/src/main/java/com/firebase/composeapp/MainActivity.kt index 640ad1389..20e9fdc71 100644 --- a/composeapp/src/main/java/com/firebase/composeapp/MainActivity.kt +++ b/composeapp/src/main/java/com/firebase/composeapp/MainActivity.kt @@ -117,7 +117,7 @@ class MainActivity : ComponentActivity() { AuthMethodPicker( modifier = Modifier.padding(innerPadding), providers = configuration.providers, - logo = AuthUIAsset.Resource(R.drawable.fui_ic_googleg_color_24dp), + logo = AuthUIAsset.Resource(R.drawable.firebase_auth_120dp), termsOfServiceUrl = configuration.tosUrl, privacyPolicyUrl = configuration.privacyPolicyUrl, onProviderSelected = { provider -> diff --git a/composeapp/src/main/res/drawable-hdpi/firebase_auth_120dp.png b/composeapp/src/main/res/drawable-hdpi/firebase_auth_120dp.png new file mode 100644 index 0000000000000000000000000000000000000000..b03b18b5328a0c33551c66183a648dac54836187 GIT binary patch literal 14632 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 literal 0 HcmV?d00001 diff --git a/composeapp/src/main/res/drawable-mdpi/firebase_auth_120dp.png b/composeapp/src/main/res/drawable-mdpi/firebase_auth_120dp.png new file mode 100644 index 0000000000000000000000000000000000000000..820ba76f9072f6b9bac53c8eeec9fa1a97e7fdd5 GIT binary patch literal 7189 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 literal 0 HcmV?d00001 diff --git a/composeapp/src/main/res/drawable-xhdpi/firebase_auth_120dp.png b/composeapp/src/main/res/drawable-xhdpi/firebase_auth_120dp.png new file mode 100644 index 0000000000000000000000000000000000000000..351fa59fe5a0c215853266f7ae53ed06e2d2b001 GIT binary patch literal 15823 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 literal 0 HcmV?d00001 diff --git a/composeapp/src/main/res/drawable-xxxhdpi/firebase_auth_120dp.png b/composeapp/src/main/res/drawable-xxxhdpi/firebase_auth_120dp.png new file mode 100644 index 0000000000000000000000000000000000000000..9b68e8ade0dbed106857e558c5472bf511e6dd03 GIT binary patch literal 35146 zcmcG#g;!L6*FH>2Nl7V4mnflhNJ^)4gGftD!_eIg(hc$jh7yLHA*7L%?(WVR>OFWr zzdzw!z;Zcj&L{TX*R}IRd{&mh#U{r_LPElola*9MLPB17`onw%eB$#Lngaau?5l!| zB+}#4PhMMT67UI@qpYqo5)wQ8(;u=&v8WsHA%=^bk`%@YIu7wGT%lu2JtQO=BsocO z4Uf5lHqRdhlb$R0-p7@Dn(yY&Q37UdZCN=!Jfjtpz_fd5t7S?d6vd?*>cRACOTpsI zJegyp0`ZY6VtqrQk3WLz$A{k$c487=$uetV*i20+g4lw^By1Gg{<<9t9AEcV?t%Bf zsa|DA;5{$D5p|U``9=3iZ{KU-YvFD5uJ_OXfB!`y5JKyb9U~J7GdL^VnQ#|-__<RT{m(5KID%rkG+R;2w#ZF?Zs1^NZCrc)Hmz)c*JmZD_@2009v-O?wkTZT zhmT0@skM-QsL9JNf*IQP$^-e92{9uQqvLe%8j#KeMw$J!3B3HN;(bC0N zi_O<3cEBn#r(DmwUWO)9qO2F0UE}2a8it9xgYUn6So_1w=kUTGWjqvtPLsAjY;!Ep zxs3D>$)9vrY4S4k1KNHdZ)l$PQP8Imrn4BXNQ9-hDCVz=$3zbQ!L-GJKdkO{$Qv!; z6%4jSf+HGD8!CtGDD;%HHKJ(+vLXx&SM5r#5uxV_A z8qi0Zk+`1)ZksK3y*mv5-(v}wH!xH(ur00deqo-mv=2_3H_aY0R?kc zTTL|L$XLGVcVoA^o@|M6)OA5NFGEdGc>f!?4jB>OH!$lay-2W0;@)28U``*lQiamr z=*b~Dv!f8}LeyQZN&DPQ495T&GJP@S$2x8$U2zV-8S50N)rjTTHm3-jeq@l zWNTWZQt0D+-u32dSA4WQT*OC)bC?g)fySFAR}yL(&%rPfP=}$O2cy+qC`6p>ea>zE zgI5a(4QuA51fXObe|E+?(U+c(bAnqYQBDVJ%x33B;o1i*nblxQQ^4+ssz0ESp}XWT zn&D@w#LLGhK#|08E*=(8A%Gr+WdeU4HI2m9JsngCO}g0Mo7Pr==o-1><##lf9av!w zO*)wK237a=GlS)Fpxn&+Xp7#&>garIpR=uN=ZkK#2fy7Htu`}k0S=RT{>{h5u|i~i+hAmlYx;#eVU=Bqx3!mP(Z zw^7`#_sA5_e7sTrP+>ahib=;)zYmQN;B*M_pO)b>8Vu;wuQbblx?N#Y;j-|xcoxph zFm+bW5FGUOG1kc=aZqA`Y^AWOKh)3@(8R@Q+#D7FS2SC077QwM?%$W$Dt~d%#O*fq zBLme(&&l6pH#;A~rn9HxpL+sI1NQzSf91c1Ak8wUUC$nF;;%Q)A4G<6>{B6Qp)9s_ z$HM;Yjnz*n0ro2sTev>LiU46e`YbOIU|H_a?>~xxJ!b~7iK_p7?NvlwH`1h!S?4B> zoP^df1(oZgTyATzI`ty49Di-)`kCgAym5yzn;bV@VtxeOQ8V&iB2Lqiq1D&xvC>q&uBJ%q|ChWY^g27~&_}-w#ba zb!*U*05MIxs`>O~XV?|l4(DU9FF}Ini|E51+pLjj&7`XJR+2m~R2+`mP^IC25c#o$ z;_Klm`*%IOnMbaZyi(DIcA1VVRp!^w1Ir7e=X9)%l1jfrO%!D+aOT9OL|1>*+|hB9 zM+w_78~ZP{Su=p}(WVD@=2O}p&6xZCVJRl!mc6T2{hcw=M>G5$tBdzYb;ET=e&88ll(K2)+n)aC^}>DMSK z$)KpgE5Z%Dz-Eqq*X5|qNbk{Jk+0hW@1w0llzxvLm@T~!b9yBvJ-R5AaK=W5Y=$Sc ze^+s&<4i5SU-|u#Z&EoG5DJzzbZV&MIOleyo}+k2r`bPfo zZb$zB6%$o4uj-Pmuby_sw)&mGbvwLPXd{F2{jpE#N2`B}7$BHe9h(p-4iQ6{0ALV!Af;z_t z0>jL^z^@gmdO{grJ;{lj742%Jmf-Ri4JWjy1VE-P@d5<@vrF%sC`xs|!2fO~b(BY3 z7ez+fF@ye5_l)iF{*DmiF12DNPz0p;MfHF9cM7iybYH>QrV-b9JYy?7jv>Gz-5s`h z;Lv?6+Fo&X6Zzi-gZgBk80>sDiYPg6;V81$h$}&j$0&Q)H)Oi}Y~_C#H+4KLRL*Ta zJos7@)RW*~{R(bM`v$Gdk7eJDUnu|I)p{`fkPC9I*9)H|IasSwc^P9Qh@y=4Rzd#n z#FsH5*RvZj91D>g(FW13^u|i|wAj5Jo8OyF|KP0|ywUcXPzL`a3owbn-Ku?0uhV)z zgf;efemOLFA5(wMb$&pMx_L;s>fe`?BkKCA3nlpI$x#ge&8qP~bIN^T?#oV*dzl3t z9{#^C^;6?sq#NtYF7&eWB2CwU@_!Jv;RlB_J9pjAKI)Xm;QaF%e<=|v+d=NMW2ff7 z+uG&mR=m<2tc6t_ILpFGk5Xb(w#@jd1E?~Ow(VJYVH{g6j@%8eH6o0~zG$B#6! z5!tr?#CyLIS?_0f)EOEF!kBzuWqrbgRlNEpk?F0FHy}mXiD! zX;g+clF{r{sA^Tw0Ri``x+Ua~*yIr9ckt9cO1xd2istn_{Rm}H5UR>n$za(8*-BF&4i0JJ<$D7)$JKv zXQKQT2oMj_OnTzTz-~1`CSLuJtfu2wnnp4#(LV))2_*XD$DqSz;U?Lt8*D^7MzHxb z7Dmn;H71N}i1vmn)!?#f_S%2ImE#+H8IU?*^@kL9{dWxv1#0eWPR1YYHgV#8ghKi# zzaJJluGD-sZmc_1o>3Y6ot|;Zxa2#|c%~WVWxU{|v}AMU=ODT(`{zb&5+IkSg`@~4 z-5=RrBS_K8Fa)md>(O?ntXYg@6aQ?%sN?+-ZKz>2tHYDpY2v+!pTV~c6NlOl#0i_S zqk&!>=1JhIpB(T_7iKhT%z1GXDIes^s=-i0vOQE7aiZs7f935V%0FH=k3pDDW6cI( zBPhLmoj$y1P*i}YLg#js&9{WmjyWVYH1#Z`fUxG$FUJiW(hwpyeSYH_=sLVzRyRnn7kZJ;IoL-~Ap$GK?h{P7#ju#d;>6d(wRs`R! z7i0HB_SHS#Q&g8CO!a;7nr4`t4pT+|tukRle{LQr_VH!liN&JlVTi$9%O&;w670F& zHqEq;P|q#LnrZ$%V>|P$^MC7f;%6;}9076Yu70%Bu$L2pWcPb7T2THuCoEC#zx!Yn zR`JC*j5GcK2h)dp;aIKuIql3MahC7WFRH(E*|F6a# zwl*mFO2;?J5cQw?V7O}|Ds}`s>Nh-i>W)ua2#wDx@j)R^r_ApwHHc_m<3^%b=*s~_ zuXvzJg6F%}*G~PEnBpRg`?>yI%hp4-j!*GbO!Pk*8eGiK5GRiqDhU7~gQ&jCn63@Q zZC(d79aq54&u3iw7>&hvMYbxzcLty{L*l_p5v#?c^h@C7GG{ ze#aBAnbrqa3yu{|nq0CAmHv>+G2AZ;zB0)=a zep;;${qIW}?HWFfq7j}X@pZ&Kd{}9<@y!HN-|0>aKrT`o()V`_NC}E8gDi9!IddYd z$uB$b=`vxNt;@dMPHRi)E-+G!T*ty^y;8zM{Jj&Lb^5#?xu(I-|4H4dKxD&s&8h9! z${tSoSmnqHU$VJtsXa>g(KW)wnB<&Q^J=l7RtjQVgNxDtDX}1vh(Y7Wk(o0%7|He{822f{BQ9%Lqj1mYCljMNLQWezO{%hA zzj@a=U)79fMz|=7DyNhMy4B7KJen3WtFdqSS_gG3)s*9W!nnx= zrL1qyt0V`3bjV!1Bc-Lieg19AXZJ-BfwAL%YY;d4qU~s58;*_ojA99jcKdC&e3gW? z*QAXeYKJSST0e!=bZ@f6s9Q|vopSpjYmF}A3#BgiJEgVod1dg;z(!Ua#GRHRM@#*FFqc;H&E(}*vQNLHRN`c8(q z&i|*i*ku2;aQRUf$08>?TI4ehe3$1YHGY8C)2g3yB|fqN-%N+i)wWO>W{#VXd|9_heG*5&rU$H*QalTE5=N@8Cge}*1Ei%%}J z=mz)OB3mXdPc(a(6efI~zO@ruPYb7yZB${&`aD{beZKp4`D}CekBSj`#*#KrR9MNeQdr!>EB~oko;HCLSD^%46;F*v(xQ+9{YzS7{{#I*5YL4|?dq$f zDia+v+rO*`uZw1d-MRbM?4n)~JRu+j?N3O~%jNCI1lc$53UXvcuIf%5;74qZj@sDA z^MuX2K8x!7Mij_4xT&Fk^*)ZPZE z?YMfKg!R_DYGrWwZK?`=CPk*0P;MRZ;q_cc*msgCLQ6Y)wrM>>-^Q z@NPr0NxuXDTmToaZHf1KZA;W~=g11q(uPo=?0aV< zkj7|O6++0%nA%Tm{2`kL8L{q1Mz;zpxt-jbq$!n$-ruqViGKJU7<$S_OLsEm))Fr5 z-3&vhUhJ^3!npA)!Eiy*RtIC|PGNC9D09H;G%+uR_@(zGcdVG32@(z_B<)Q$mJPlu zvAPE^%Qi|$0LN@}E8Bj;oki%6~esZbBeW+Lf!VlasZ&@VKR&O8uVQt#vrb)@DN9?md zW@o}Yx{r-#D1tkb#O2ws)jFbSNE$H8^4MfC1{vcAw+-JrXkXmx8@ZzkFkdbKk#t+o z>(*sD6Qb&|n~Niy%NE#j#2`97UQ31^RI2tC-B=7_3`d>4amjL7jx*shY9v~kA+5eE zA!X1}xe~Kkl!D&(Ha9K|1@H#`eU`dB-f81UHurjrJ`R-jI7esnm>Ja9TJ;-oW;ZvU zj;LHSu|u}*u#_;+4nM}}C=K9kP5g#C@+~vz;k4tn$`IZ$;+Gr3mpzCNsb~nAL(_RN zg})*nf5|Cn%yl2u@`b+ac&Y;^WjFZuVNjc|Eda{?O*;$Oa^NXT#sRNk8L8 z;A}H5<^=S+bI%jFVv&D}M{v|WL|FZGb@n#yb!chTq=xQ+zPxJlPNp$jK10XU%ey8+ z8UzRf$7Oy1E0?+@VfWZgd4pnq5K<<)b23YcFbe$nuBj~HegB6YzmV>-&))gRy+8;^ zYc^GTRr=6;ZWJrx+=&MUia#@$H~pFU?kdsy`Wmm-ttx5?7iueUEeQRm&nav(J}q#`T2y zfO;Bj1XrJu_tyd_4(l1^y4wwkB=cEg=lw=(C|V`Gms!$x4%EoyhHoU|dus)K`$A`x zJ0e)Czg1{4Ii|wcjYGV4yeGhAvF%->MxVEj&e5L+-c2YgFKHKu0Z5r9UK`)p&R z&wVqhE}kb8G)TYNdHNCA?=3}cz?D=ghZ+_9VeTux*BiFMN0q8$#8+`ux@@g% zbc7hk^M2_DH=33*^FV=sPFR#lo>r@w+J(F6ed`2`Ik`zfYondS$MgKL_3y-v;=9P8`0YbnnMTk-zrz>&2je{Z zZeX{ZHIZe(I#=QLvynOO6hLREebiF9H#3ZlS^e%G=ku|h@XibayxKTsE^NV&Lm!PK z$L6*;4NwsqVvA%y<+IQ6!{{yX63$+d(c&3FBQc3~@b61HfS%DU@q!7$?e&`?OhxS` z9iUY#9HkHT+g+kc93R(>v?uJhX-fyoZWM*up|G}|<_D#AX?h^T^cOriy$$zLg03&( zVdcAXh?V*|PHtP8!$PbY*0hZt2USjbMwZ2^che0Bp3U>PYe)+}x(W9~E+A0AF$ALF z4{SMX3Y;TKGU)~78Z&zL{myx7PjYrf2O&)vZbJA1#&kE)t%z;D;hd(T$+;(qehxmA zYur4e?%J+pT0XUt@$=6R-oWZy#^V)6e{0edp8B{Y{8QrMkEUM{)vs;bWXF}+D)|>! zHnaB0bS7f^%FyD>I3&ap5;5T0sU@7EJ&F4HcHM?cMjj4=c7k1m@CA+O0{6{#BOB`G zw@WHxc#$b^yH~e6%xRA71Kz)f$ErZ@FAi}M@_2kt{(F3FzqBb&CkPrjB!)scbsjRW z{X-umxWWwS=4vRQ_)G0GyP+F=!P-DNUF!BqP2BvbfsisS zucj6*!o<{em-l@{7WcYzU2?XI?RFQ8QEA*gXb{5v31oKuz^S)s+>3rWz~rt@ehm#D z=FWRQ&Rgp`s(j3!9WLcJE^9dXW-|obCkW^wt%q}w02dh+r6!v!(abDcv zH~B&)viD0SUyqS6qN_a0A2QFr+=OFGP$RWo^ztG{0%TVGrqKb_KDq0QB9z`J*5V36 zx0=yFkxTP9qP-M1xZO`hDNeuWpWxsrn=6XVo;z*#?pWM3!Bwa<29%T-DA#bV;VOiW zNIac0t}u4%!I-NQ20|WOQjF!$IHnR4tMBjvF)&Vn(VzQ?ls4JMx$7f`v9I)%f)`RB z-Zg)mqpWRlrGVolI&M{xGPD|keGD%16}qHz3%<`J0w`rBB^*E4p6N}SonxS?oC2a{c{$pmov_k| z7%e74+Lg|tw2_dq9UrmfskX=YS+a!W5F(~Ggw{5b z-6=3uyiqP@Y^=Z-yO$&hhhm8&nj+LCI9|WKKZKmIRl*z%MqdX0HLP9TmDRGf{s|-j zzly&68P+DSTBws;XFVT2tQN=z=*@8A9{XpgWI9f`mp=^vdwca4x~Uwc6$-jhh68VJ zp2u6}bxJ5lO(>~NT^by?v0I3)v*@QL%Qc=Ye`+WNZ|DoM(!TaXXSmZ`gzmjwgkmrG z7C6x@N3gQ!)SZ){NgUZ?3EPzxE42eG4o)CA3nQ84d;CiV19*IJ%&4-}>$R-%;M~!e z!$_GB>z*W(5rkYoB=FZSou6!+vygT}=#&xN#s$iN8Px770=IMyYVfRN`tl^0jX0nL zq(O7k(nq&k6WX4#_!~->@E?CF1W7VT>m7lakJCaGljvX~_%Dv$2YRjAkK0Ax4K;hH zr;9-f7e_YqvQ-Q`ro2A4sBP}{i$+vQdTP}c+wZmtViu1R&Y^o1Pl5QQqCv&|{`$TW zv|cw><2G*)pW+QVJc(hjZxlkWkW{YzRH%RBA`l)o;JSRBb>80`17e00eW`SG8Im)$ zjmgBC`RQzSzuxWsAk4HpW}B$x`S6xRPUC*WkBfO3@_zr7hrIz~231z{<{*BQh9_0U z`VUH*7sCO;qt_PU{~Zr>Z~ebnfN8t5sxN zdg)=n@ZKSYC{a}-`rTg-CYV%_3a_d_9VApciv?lKueOEW5H;Hqft6kF)g4o+xwPKqW}q`MhidJO6vd?<46zMA%f(DjbwtDzX;3;EAF<7-;zqY z3R*Z9V2jCOHyfj3vHuNYzjyW7sWq`jDWyBh{x30*$EF~p5knS(i>$b!4R9;2vkgZ} ziDt#kPz4s3Opr`-7Hv1_OYWeQ3s~Py_%&NfjA|&U-W%f_gm?%!V!Dci%P|IIQNaR4 z!Xpc-m{DU&ar~T!YX8`mZ+zU;UV90y&4twML4o?EI8^we?XiK}SbE=&qX5fbZs3K`{5P+)6dI@Ja!Zo*cDeZ- zi%y~QPT^2X@#a!br5BLum+~xO)eWPj!H}@NomBjk+b9p>^*#apQB#DHcR|a<_88U! z!xVi4td4e^gl{Ly-XP_#nD(`@$U0WpdWongyhFrXE8a}Q>=m%5cAmXegySMrfga>- z_5o+In4d6u&vl|oZkDxOFNgi^IXi|<@vU~tKuOl?E+~2pT~`Ws#SdfhR=f#ZkvTKg z?9M%f7h*>QH~iya?6*Q^y8Qb+SaG2JhN3Sq;6fLfJz*s4!7fe!M`5-eb7M6J+3Qt* zzAX-u_+?K7Ah($ceuy7#GAJ(zYRn6QO1!}P`O<|& z4t`>{<6;oQ2j<$-^hfUkx@AVlQ285{@Zd3*T{m!RgSWuN2w#Qmq05NgCriR=Rhb>< zTsCNgLkC&Uj1G4g2*Z*v&0Qev5w@i;=3v*roZ|fRxr>nc(xIVUq&#H?p&=nZ{?ae6 zL=Lg|(4}eNld$H)t|-@e^D;f@ju>^dYU$|&(j9|j#23I5QQPX;#pD2NK#fX9f5wD4@KfL*4ZkRGP31S?>&GG_RUY|2?CMVGD_TF_4xCFC!o_6?j;|HI_gU#Cq& z*##NYzyI{;rNV2PO3hANPPM7uFdHl+6&n8|$IU$_0Kagt9?hbBCqQd!g4igo=~6c+cFq;E01aGrJW%G5zqrg+_{$Yp($=&gbTR92 z+Vo9#7iKo~E(BEB&HI^!CFNZ1OcP$W*x~#cKXM$S0d~QvgGkez-xXqdlQDc?Bc)~| za2(sd-Q0N-FcxQSw*VZyITr8^d5+46L`|0=2Z$Sx2mM7a^+8&rI+;c9w(Ov{ipWrb zmLdipD!o5bdAFhN}WuWV7)51JnaNh57 z9T^5Pbab)Dj~zjjWl@5a?D>EOalDMAc>S1F{`%BNLD%(Tw}`(94%H$WiqCyt>o0ye z3n?-%Sr$R}go&K2rXW^|*~a*^V(HH8F8&>rdu<4R#F&lE&%s#&h<%&eNqX-Q9%yWO zM1$8wj2InJ?M|XBw?D-crf^@`Yb1DRZaQBIGSHSUZD!h*7N}McSboNW(q!gx6b%b! zY;a@+TLYHHG@TGA{cFTw*7l!`5j}aF{fR0bAPcl^(;UE6UTHwDQQmz}MsPAJaux)g z_?+^I+^zEEJkZpB9h9SA-u<7s4z^s)9_H3)jQ|qFx=(U^O1DNWw4cxJKDL6un_pMG zjaeIMl3L=*D&9$NPRf(WKMs0VzHBI(+={mNg{7NB2-nIXaHaZ_8?F%q(`)v?hx;{a zTs`>ZzGgbhwg)_U4V8G`FXUF=YdwlcGoVwS1?3yF6m3Du<%7p+k_v=)oI%*4NlpbL zw)5}`c5{TSu_nl5YM^zSs0r-)h;`btY%s72&6kdCk3DVPh7p~3-lr6TB?f59a7BRX zFs5F(KKYpN+C9mi?WcN@Fw^-44U-?i6%Uv`R+3fgZ#DU)&e$l8W5y2V!NpR(td>4$ z(4(sddeKD%?AT>k0D=3t0!(Ckkt?lX_TvLkg_iGkCkeYp#1UO?DncjCgmjGb#zOYF z1dDH9N=Nel$6&TAR-3|cB%=#)>Bg0GVm;Lu)Gi-VUCiCtitINdU-QNfS;A?ms%*9H z`KY&abEm1Ks#eWQ=$LxdbQq;?@k?)B7`M9Rr?hmSSqZJStoE?K`5C3}a?#qAdP@(* zeQe!AzL?LuKE%dZc&S43uA=iVUrHt?!zAwk8sa(f57AAS~a2>&2 zH~X_b_tA#!*w-TEhqVIeHb_7ns3h?Kxnq;|$dH@nOVV*TY2iwoN+sy_xbUk~^2U}} znDoh~0+0WO5?#kDGf?1odxBxLZ$AfHjfjZtwp@3?yiYjTLkipO8PO}};l#^i1)6lm z4K+YiXDWZ{lqk4=4mAI*&MI@@ic(vSKojB{<$>wiVk)$ev#aUj{wz*%9PX6{+umnd z^8x1aEPlAwzT45`LYnJkT1=C&uV}kvEI(Ek8`oCYA3!?NMSAAA?eB77<8xv_6*45; z;1SgkJ{ulP3Xip2W}q|m*TUje~?jnSuGSHp~DUj3bHQ1M>EWPEPPtFx9&3gPbuHfZS@M%ji zhl@{y9me4g&PNi(6V2H&jiMsGb+Cuki7i%}dfri>tD<}3jTG*!y+R)aQmJ*=yv&#M zx6fYldHvQ{wkUnzHX$rNN$`aW@uwJR^7>K5n7N8#U^SA??BILeWV4Q5vOfO$wUtI*{`>*S(ig zbt6`=8fS4KG6&BE@=t*L;m@v$b0KT%V}I7U7wL`BDZw8^!34^aA~^<7q&1rGdlq~C zB0AJ&7u0dsF_G%vjoITohi4f9Ov?wWXu<_Ni>BO>+YU&kNpw$`9X7goe(+E@IYs(( z6|!oD=R~eSeSM$8m;mq6j;c$!xyPgejR;7x?6{;2n&VLKj31t)pmAG?HC-Gwoz0~g zmAL-SX2!pt$eWos4oFw9$uvOZeivP`O+&eJO!6$HR@+aO|2@}VF^F^G9TD}os_!~5 zCbZe2bwXnJ2g;qPaaxSW&CbY+D?j>n|EhB5(;FzmxLCJiKIAxx`z0Pn$x@m*N&i$ zomeH+C?`1b481Rm{0Sk3OLt?Y6H{vIL7bJYWhkqb+uQ`mKnuD-R>1N}K_l(gIV0*j zBG<`ewskU4Bu^zHTwvlUsxB{yHJhA69ZikJqN2feJL5P`dPs!6UV5W&it3-KJrH#F z9^3Wwx%WX+q}+q*f{-tM%W~!J%Rm{&b@>4(aNt^C9L>NS)55mvDRN8-ToL=Emkqye% z`PmV0wLQxyc;sHiA-c$E)>bUZnh;=GVvfAG;#1Yozq$X0ZKNe&^LqKBF#vX*__c?G z0;4$_=a%iPa`WM4rru@194XGnrK~;z=-wnr%bW(DtLm%EK&Ox#AxtNw$h74`>ALsE%WmWV%B3fUIJEQr!2G%{d7qIn*1oFuvQA=G=lnH#0GH z2>H0>D|Z5^xMxG~b_=Mwpt%)AU+T$Fc1h=|=4@w&iD06E94A-P_Ie*pRWs|?0d7=d zp9bb(I-NYuJ0>L;|FA44$>u#evq-~m*^QA%7y*@jL;6(nCP-e*+ax>~BZa4;FPzFj zuA_FBQGr8l%qhfNNM+zbmZs5utF3Uo^{T0o_Nlq;0W=Q$e<77oMBILj0)@Q_o?K_P zre@ZH79SFqhF6heq&)r@J*VsPc|%i{{O9mv(=EMKa8FZR7mhkrA#Dvqg>;mE?jtV$ z73!!5;flWoS0Cit_jq|JGz_^Wal%n(KV@ejkuXV-m>`Avjv}L+YIhMUo12pNt42bW zOr&zwuT;s=QZ+xi@+e-i*$lK%XE|!725TzXs^ii=-);OxFZtnyYTqsA_kBcN+jhe* zhp2z^0Rc>x6P9o(I-iETJJK7=F?F& zma6;QUtBX!?%AVzY@TcD&07I8HLHz*D{X6qT;hQUo0m+Vp_4rbyh#5^FDz*s!Vo{0I4q;p{G5p!jsGZht>Zf34 z{Ml(SwaaV6jFK}Yv@8+omB3~1fjK}zF%huP?7mA;|3(MG7r~d4?-dO<<~dd?lwp8( z^}~Oz@LvvL<#XkV@qy-k&5|FlHRZ(7E^}Vlhy(_+(BP+Q%UKA0fGcl3gfr078r|pq zt93?fA6mAGxGfei2-lPP_{4B52TWn|=TB%ui+?g7m#*BU63V!Mmy(G$^O4f0#T23H zwSYI#f2>|zgKxX)%s-)-9By9Kcqe70A%olX8^Gmg`ol=^*9sd>1L+f)va`?TUj6Pg z*i&DF^>`PoP6n-YPlIbEaDyIt1YUV`GqbSlpnx{4U|*fp0-Fown~=?T7B_HKIJ4Z8 zZ+Q+&i*GtoC#S>!=wr1c=QcKZOlQK_$^wgAN8kSy2S#5)kEE0 zJZ~yXO1hKqloS8R1#Yn4Zl23+ z8fYlGTr|-|$Y!ZId*9rT0TTo+?Uq5`Vm+$k9jE(Zy2jp6;DBQ&c!joO{Sw|Imfy62 zL$C!(iFZSh$42mbZrLmjU)GDt$$S5Fx)%Do`NREz)n)`>^I%ne!aZl$2aL1?lP$k* zBJgKk!c51*2%K+3f9!B1m*3CGm>8b;wAWX5|*lb6h2nu-)W zXSAQQ%Ns!nCThObnF~#qgQA|1MtJPTX^&XJzhsg;1Cl190N_1^Jqggm>$T^FQU0a6 z8~V!b*5eUdNe-}JXYf}<|Eb?B5GUAU!c)U_&W=vn=!|Qg>UurF25v%Aou_H^1xH*z z-}EoNKC4U(mU6o4im);R=D6~L6I8HytS;qvFU+bsCKEBmgel9bAokm#@f>-v4HJ6P zC?CDLUf8e#CBnl`U&QT?zu(-!Cq&LFIdRl?M-&z-jL?PG#DVg;;K^scp{BQtSV@Xb zmx0Y2n}a=fh{nH{2E11gf-%ocrr*uk6rZ3BBPN1d^^Xevbzs54Ie8#)b)F+-5N&HI zOs88m;bWQJNI1?re14`UVs_2I_kB_{F^~VRRg9|;Uo7bMaUMgQn_5gbHGEQRhJQtx z)|LZ$dR|fv3VX;%j)7AW(@6aHu&Y(-U9>QrmSZ845Ef5EcDBf8TD=0 z;0D|;h0;(GAoq2Xi{nI@YHY8lpE_LlafU95w%iNfC(D}n09%}MdAiS<(Zv|9HsPf* z;AP@8D-o8$(Tn#QB~t7g%8_MWk&NC;RjAs5Yd+wT; zoMI;Ay`5fX4cHIgFA&B9llRFAdPg@7&Xg8_U_GN)js+}seAQ!)d?zq`L&2}7%FX%@ z&=-&bCS@kS$Ep{vS@9f) z=DPrYK~j({k@#D{bV}nu$emM;Ok!D(S1c6h6rq1HZ{D=`#`4#_#;X5Xaxb0ckn#$Q z2%~BWIP;kMeOB4ff1g{XA>H|$pYPx)eknRUg*eOpOo{!+>&MjLsab^y zPaAF^E&3*P70-Elr`wIAnX#4bpetF*v*Vj{1t{zd|9)hGgMZHl1(Ala+U(MDV1SSM z5g@or=TwM(rZwGYqW#&^qLV$HGzE^M)A{s$7LsjNvGaBBG`y z^OJUU`juncz#LJNo>m2ts0XOx&(42MTUEG%v+*Z>^xn{Z42e9;PSJg@^OCS zUiSt@meko(imU+^dhA zQO>jmyush0R7n6#fd^>cBW}RtP$g-i&{ayreQ95s`XBqNTe*a+q z9Ru}#7`E4L9;E)a$(j|e<-%igs`;Oa{%eed;s~w`N$6yA_B#jJB4a4YgmY?K`czTT zigj*vQ+B2T)IYe4CYlts-|QcYp8an<@Sy88>4}r<2!%qp_nXxJ(k&7kY8A`QYPMVz zGes6#vcqmkWofKlJI^umk@VgHVeUj9#E&OdoBA+wL}+_HS_cTX4ETNl?|KCTOeIGc zDpqpPsa@Gm_m=)6<&Q!SM`CxKR|@76*6KQ|K%Y*IHbfjtn|$T#<}Vd}V6g!*F&tLI z57$}=u=9Cue|?ncftfVsqwxoLZkWb_c-}lPl~Ss`k$GoI zbH-L>rExlxW|qhe@V6VB^b~!Xw|vtSY55j`Z1V}A@E_J3iyeEV6nfAupSv!UStyEjo7vzKYVT^x*@}TjFYe#&u*f)Tcd&YEY?11O0w8 zv{SIWYoJyQ;w|sM(w6s+dhIOQG~1l51P#J{z0#t4F+3b4NcKy=h7vFa3+&!88NN3B zwCt+{4y0abs(aqc&-9l=kI}9|nV>LTSR9s`uMz+Boec_aj~h#yqh%TSeOMx`EH0BV z3-oP+9Zjctu440QHbd1=pS$TCqd|&NwbNNsb_hQ;bCRtNq^|#(VLwhe%Oi~ywmk3x zV37}m4NRfm;c=_Zv(}XGXY^Iq^A#f?MuNZH{anUR_sscvFFscZk^c2iBNFMgc{8}e zxpK}%@N7Z?N+PwMWUr+;nv?LyW3n}bB++&oo(q?yGd}C?<1WA!=?l!D&+DHNf z(IcNsXn?%7#Mmw8Lm>~!nI(6KpS-G(Dsb-65$h&`>>nAB}mqw zr~eIwS2||oQ@lJ0$FIEnO?}=mX(Hd99Q ziGk!%I>hz?Ouy$%1|I#KP=)t`&3uBH^XILmce>kIePSTfa?d!b4$ zTZsy~1mCg*h^l~Cv-EEV-pk41M9M(Kr`$#XPom3Kj8>bC zKFHlt{u(b`)r2&ZN53eAm>WLz?fyl0ygpj&MRf9|I8f{(ByvS3zYLYrOd=145wX?> zjC*XBvx-b#n2+#~5O?5mZ355zvb9WdsGQ<3AwUf03NOt~x%q}+0Y&ZTo$vE)=xd;M zu3TM-e9OCsN{q4;W-}=;fv9 zsi@2)6AufMk&-$*7X2NI)miqB%Xd}*uN3A)pPlUBDpcA5_%9WqXX&F>HE`=_WdF_b za$8?yNY=1sMgm@*7e5i2R`ql%-rooxaiOjH2OR(MNBH_w17OqYiNe7PC+jF7V0Uhp z4gJdP)|Mae1%`xw4=C>#MZBJ zIrrbzWuTgI0slm|h>$vITaBfdYG8wb%IYtHr;^i#dP<`l?z(Yb!ST-7Cg`D$^A?Qg zYygW0VeOL<8ieBCUoDs)p6g547y>L*#@@y3Ah+~;76?&N;4%H*KFL7qo5r8XW9tF1 zbpJlZ5x8$MH9qVtq`d$BsTQlAC>ki@wBFQca6jnoxTfg`ITzFB?hxCm1GMsFkNrll z7q0Hv9W81#JYe8NW@!UB?tz1!{+^3t3&)Va@nobsbmhdKK&(88nA{vDfdgX)Xtr6& z!{%}tMTmEeQ1xr^>FmE|eI?da1z(>^lEC3L_f_imD3QF6p`CLxf?bgx{t{v2hBo3e zaCAclnv)zZ3sBW)^T!K7T4OsKm%jU|&@Rfk$X&)TS@)d}C#t_&Rs0xOSblo`+qWN~ zZdcZgw99LA1aLqo^B$=IUcr$RGl^ri+ji^$MlACNB8Gi3n&Jr`()lW zXOu1eI87JN*>UvC6PL{R6TiM)-I zj(^hy4k9@s$c;!z|KBV?7^xmzDj*S>kk`n`^_PopcH;fMAv;b8|BvvcQ#tzUl5Wtd zd`}CVx3VVf523*MV7$CXZ=udM`)#VD;>%QiL(|i4djHM^@j^SO%R0@!Iu|1SrLX%8 z7(erSb=(Zh{ci6UsJ;|Y*LmAq4)v^D(x=HQ8We^Wf%34U^OoxWr2SS7QVK?%IZrFEIIKjiAXgJ6uxuwA z?^Y93AAh6KFsXN^_?AxgjdZ8!UVFxCI3CqE?vpq(<5rdC?D8WDWOA7<7XZ}&)@at` z;V!#^ynx1z;#Zs_r=<$XPKbxdgiD4j|96?#Z+7?P$q5d*(JK(1UiZg8j6iTygs-=* zkHIJDhFS9RxQKtSOF))D*{;xW&-|M^K#hu}9Z%rcH@lY@S4e7gL91Fjjx}2ahq?Q@ z?Aq0(0@WUU3>`s)hVjz8R%q?1Zs~P){%>J=Te14B!zjGg-DNatX6^dx?wVSZBY$eC zw&nh$Dm?aF1JX-G?ct@-ThSyW?PP~UM@`x0Y_`>b4WZ#nW};p!qY$xVnoSy)_(&F) zk(Ks{^j4p*Ool#`2cjZT2Faape??lQ0~F$X=K09JllfK6AM**_;0s`q*aMqa-_s+2U+2-?P=p7S9_mSNg3>5sHf7MQ{K!r67of1DU1e zgNQ2;JBi__UUXhcU&wEEnlsuZoCF*JhcQ~d@=sUycPr74D0c)NcKcJ6Ve$$s``{LN z_Z+G(z<;M?GyO}Of`KV$uV&v1SCC7jg0jt0gOy*WW|MzZ;#=hpyIBnV+8Fo2JVvI3 ziagstsb#@?4!;L1TE6>={oeckXu8UvxSFPmy9C!ja1ZVl0)*i1?!nzXK!9Mu-QC?` zA!vZ$5P~jFa28$sy*%}OMN!3%W$yM&cb{{5dghw#1?|zuwlxt(kjk<#pmN>uneRNT z+F3(t^74jO7_4~IxSP?Tb{n~cMBm5}k{VFGeWPxpvJw_F{zJIR>ni%~!GS(W{ak}v4AA90Nq4gos*JC&z`SUv z``8R_r`ckI!a5V_%4Dk1u2(K{a30((Tu_t8kya02DZ=NZ-vMRx=6KHA=uik*0)>C_*@msn~I*BwXS;vO$*$?}isBb{O&G8B6 z%1iTMc9WATE0|2VFW!*B^%}ks?A}C(Kh7a_q}|sM?7(jLYffDFp;%URu4k0_`(1S3S*r9D zL3)g0Uo6C7Jy81deq#r@?+r&s5k-PYV~M94tPIzWJA<=?vkC-O*vxUxqf)!i6B(G9 zO^yIrT(}mdd{at}kSi1DOw|R}83UO&$%f~qHd<8B6VzMhciqeMfgRU6uX~A#w$zAP zWd9-nP51QVkD?rFil zdxem*zRfGXJN}YjyO3>H8)N7F3`S8BWzNwHYf|4wxo!YL`Mj8to=Ny0a zHsdIJZ?vbqhnL*k#nbdJ6$cy+#xH~Tag8WC(_go+v9-^<4u|iwK7l0BnXjhSrGux4 z()}*=`j`IGO#;cdLfz+f zgy+l_19SiQ!C3{?$`2Nv!tqjl&yh0Qwhp`Vp2B%*eM7bGX>1((tsei4-1tmnEFKCR za)0<7F-clD8XM`(~BJ|5OTotoXt0E@Tzaf1*)0yd?A8j)z z*A&;59cD>$YLdH1gh^xJ*iW^4)#h0SW=8qEQ}Pdlm#=e^mggs4Q}K5>e{=}`1?3T; zy%DNgX)yS@UF9X*zHOWCWkcrIn@H#7tyZjc5)gzyj?I&B8vF;~a|RAzSWa|Ygux{^ zH^3XWj_a;c!ik1~LLG09;%kejIu6IeqeX zI#aWAKTH!}0=$lr_sE$J?_5LH`+?wx?Tc~u)BM{3WeMHTUu|+^I%2%$FaA>{J0+o^ zz;g4k-EHp~J}@-N=E?jNY~ z@7{>O!V!0mxpwQXH|wa$_h>BioKdc^wOQzFQMDU%JEyJp?tLJu`mv3(DAW5RX0=~+| zcu`!Y(#$J@3px@aU^9HM_=(*Ha$Rmj2?nfwG?WnQ=T0v_3_l!$KwJ|h@omOzBBCfE z)Fu3;FMbln&i(cs-Zy<%&LqYrF{IqHNF~98J0+d>OOr#aAdnLN9Q1(9`f`e$Z8+80 zjjHH#SV`Nrj|CBGN?YDHUjd8Zic&g#;k|p*>#Gy2QDdi_DYd?INH5siNw z*+!l6`sl!Qyc$z|N0_m1XS3X&@_4A_F(wJ`{5Kp7n$%&8F0;jGfP8`3IvSafW z{pl&5d8qi3VYb1F(rD^e%%2%jLJTN%z#6|O!bukzo@~w2p1i~U0J{Cv(E(XhS$GcJ zN(bW*k+-!S^^6>GZz)%WDiM5LQh6el;A0>hW&?aFucX=T zqtXjEK3dP2KwK92)iyOP8{RJi^z(7;{P#W-mq+oWdSFPy*MayPiqK6y_{O|gt*6Gw zhyD&W-SUX61~hG4PeO>Oly-6QnTQ_~Y{jO*Z|Dcn=@e#j-CtBTkl>NS#1SEd!hJ9? zi6gpu*JSt8?@z662DZXPEl|D81>sWH+om)%(qz&}xSYN`iOo9XXqbJ{x^X_IXCJme zsSr{l1he#S!fa#NH)T=uh(e;-RX+zY=xw>k;7JM_`c0(y?s>JQ(Q0Ht8e5jQEjf6* zbilkNZ`nO`FNh>XfE9RD&RY;pzLRH3ZN}YxESTo~fg(+0E1-uU5RtwBhCR`jS}xu6 z;I5`7C*fLs!YalnWf(x>L5C}%nUOLN*JEl_P2NHmc^E+yxjgk}s-`MVVqS$PFyh~T z9K(Arg+mBCNmmBO9B!IvNILfwtVH&&{^s|gkbr#IwOAb!orjo6K_K)K&m0?K1Fjt? z@x-8ei|oq#=BeY+aU0c)Ka6H_CWI39mZ%a1usadV8d5v)-f{mbOxd>4wjKMVijHKD z7q#GMepIA?745gm*Hwn!V;@I~e|=Jot~bv9|Ld8zQVm9UEnqr@1goedvtO+AwG zgPD-v;Nv3h{EIi{s~-lf|2Sp}r{*m9nM^T+QhBA3TS=G}sVn z0IzCU2v$NDj^5YD-y%Er-nnkl?e)Wjh4_b?@81-kud9B3-~EH-SYuIQ?uX_;&Fu4u zJ_eFmF|WZdnh;qBUOg9*o3PA5*&D|X{gXqqAhDMgUkW=fWkLwSC);ba&427=po}xS z{w>)3L>-we<2r_>Z+<2-u{&Bc91(iAKZA{WGw{-5TJfbM&pu(Xokc?9{2++A0oJfp znbzN9-*9dEio8J!-|7p!Gpd#p*)Z6;H8JXjnn$44kE*@hyK0Mtt0j5zvvO^|vSUie z1=Dk-QPFQ@-HtTyI5}keBvWMg{PFt_Cbgv*ALG~m&z_w6n*UYadEiR(=h%1X%_3q+ zpL@LP34azFqFtqISPnn6>HZduvAM}eh;OdH9~JD%*|`3jFbsiIiYT+U46t3n-eTVy zy!$%1Y@F7x0?Aj;1&P^(^acV3E46|9#MFhX5@Wl$SU3hMoh=2r`%|=_qN=vF9a6yT z9NxjzQf}lKb;GR8QiDFWW{-;t#^FuvXMFvEF0{zq0@x+ZtmBM|94`qp#0xkSG`O5#qUw#Z1c9OsdqZ!@Ef>|e{_q2i+d?lxY zq%at>T5Hu(V&dD{gge#td2kU%t8Y8c*g)ZT6W&t*B9&h<3)_BI*L<*NB=GETh;DZ%NO>n@E zVoLp6-R*n$Mn*x6#E4Xjt8gL;uH05))yU&l+}eX!#FbVHw6R`$^kR~cA*VbfK~p$O z2B^Z(P`|{6;Y#L9i z2vuJOhreWZ1l*hhZrA3pj>&xsezN_3A-*w;=GF!Oiw`QOp?J87Lkl4!Q-!fvsDnae zFQri`*v-^`@+obHC`2E!T~yU%{ZGFA@KS+y^o5~YWxFEm){oWmeH9zxf4Lp~`~tZQ z*A2d-EBUMQPQ=R!Y?p$e$@eCO-pJj zfIGYgy%pVk-1l?CIU%_CNh$aIKUTrR!#RZY+6u*ACUQ>;B%qAUx0 zMlE{9u{BuvxbDh}Lb4ZZ-}awH1jb1`9Jit`oLD!h^8a*yF>Lik^er(>nXuwE72{jB z#!ere^_>#2`Fbi2ogkIOEuoPJbhmO>8efKkjBWU%x`fE}v!YYK2AEny{G%r>HDTlT z!8uc)TIR2_o1cHN%YC+x0`CY+4gg4JjT5-}$n~{_(i(ik9zzQ|THJ z;y9^ut0Fi$O~N8Reo@rM!Y;qm*rV!duxei3F8ou$ip+aZr~@x)y1mEF@+7UTFnh}K zBsB;X&XP0m@qUSsZi{7yts=Zr=Iw|J)vfLfb>Gsb5f%Bi&i1vYMk^7_D1z9{k{7zC=~ z04~xs@Dv6c*X2k~-&j7PhX*4+W^D`Xbbg3Hac+%jDFX>1VgJ3d`6|c?VTQ;JrE1W@P1Y_F}}ZoT#V5gQZY^H2Tewrx#2q%D3%PEZTm6 zsvGPgTm6#x8zrV0A0P6T$<xA`xU!bcjCpDCJ_4W~W>}xJYV8M?JxZ}`c z2FobqY|XTMR#RdRf096+MAdDZJ}E&f$XkelVn47JlH|r@L+s!jWz7nAAzZXf;`6hP z*-tnd_Otgg=e~VMXodDi<+*hSU{`08Q23l&Z_9+gx55e`39pN?Bn0C8UQSkQ<3y87 zm_&`wL)Z5LGX}%5LNddp*BIqVAcW;OmHhKaoH7e?+-CHKFZ8A3)!>N&C|vohz{}&7 z8LBAyc1UtYz)zR#>P2M&*vZ-uH4Tcob6;hNrJEna>X<5jleNFgX>@bZikReFed~&E z{UO{pu*;ZR_uth0gakOuG}eX0t-F$(_P4v_1jhqs94ah2jK88d^Fe6Ps$qz`URuPE zwr%dw8&&fM=v9Z@!{BJx5T*UOJ^Y3LUEG)ASyZEMM;l`cI7v zDoc)VTZ{1id(YjcKl$;mAgN_P7gHnEmhcs1%^S6jKMDqYJJG2a^eYwM57ukwaYk{J z;*}8ce<6oghw6dLi>BPHA%be=;UN32skgGOx5Y0RQsKR(<_-oyh7H6Zn>*vk&7q6XR?f?g!kORIYy!3f3n%CS*u?rn$tmiq>r!Ok` z_l-jg5=@rFTi8^X0)!5iw$oZJwEx5?{oFLZk(aRrQ?{y^h*+GBsgj%RiuKjvk?Bj# zRI9bg3inTA#fK>zMyN2{k_FSOAFOFG-u|%5rdt-Tk8#@Q5DX^G3FneMYg@FCHc7TL zrJkX&0G|FGiuLv6az_}Q3Eg-0%&VTW1fSXAFPS-qk9)zz)8j=9(c6)XoRo)mi+t$x zcGq6{yZo?Yr3#yUL1%*}v-#CjEz~uG7J2)J45vTp2s#Ho%wsTdNT#Oq96Hw#PXir@ zA~jVBc~}0@zz?!GsE%#9pWt!8Nh~w6{MQc|rf|-0-Xd}PJ{p>us)mL+?M5JFCnxqz z=kyQnKG@jU^$hWc`hdF{!q zUO{5TRv2&Ae~OsYVMZRR`hWnw)W|XOg?h#3BW6VZWTG?OmS6I=wiNVUInA_U>*k03 zN@-P9^pyV|cedN4;QXII6bo(&QLc7&EbP6HTvb{%g^i84;M*q8Ky4!*I?B6WGI0lw z%PT8B*9+QrCr&}~ii(O_lXZQ6PX#8cV`S*1-Zz80%(~?lGXh@M8xaRVRpsD^^nKOrCX$)d z+jmp-%j@c7>|!l3#mg3M1AuQ^QWB0#>Hou7z(0LNgj8h8e70D2)pNxGSld|W7Z!VWQP|Yz)|<}GnxC-)`pXDy@H zyVq$}I$=}lVwAe?Z}v$!PCCIg|2@v!&tLj?)6$BrVe$}vvRbz57!#|0YxUVnMqlfR z7)~ya&8#V^+Q^02<^GiQ!E6b!dEC;`+e0^a|GRPH((djrS&j%Hccas8!|v(`0%YDe zzsI$6BjaKXOtwOK42Th5anP%IL>o4qXmPTXR>dDxkV*OcGVjG->th4c%+sNEubj2@ zex&GuzEclxQBr6ipOoEc`*vJ)PY_5P4yHimpIh8EG=q7J3NG8m?Pl3^ngZG&Jt&L} zQA0kAI&5S6McgNM`tTOoMm}`;AIL9>fGl-deCQ>*pa=rEQO+Z8y)Zg^7?Tj-U>&<5 z!#Ij>#9r>Ewtbf5W13i_mzVW`27P6`=pjZm5PV`-%1Frf!$FHt@1T%HGLqq9W<_J% zqn^%1+R7gkMTD~Q5;$P{x5i}B+S;0~CvENBlq;=*&(Q4*J_kK43bz3nu?w6S&H+N- z68xVgKLp+OECOQx5D#n(MOW0+Oz1>*1y9q;scS`iEQs;upzg~kwI+g!Zcu12J`mSB zWsa;kaG7lvm*ht}mB!Vzm?>L(PzMzJN;KVu2oM>0n<^nN&~*ZTP}Lq`9XS=wFfJN; z87kFEA3hSkH_(asBWjtVOeaYtg|nfs9ZnF4zk0qC2qz-7l`^xZ(x@>=dvWsP)yMAK z9zRT*ubqxc3l`HPpzLS6DV?7F;4}!l+Z|vIEJ!XaEd1(oxo1gx>4qfLRMBkle_npVG6zp`|IG|}@1W&%oDDFp^;B-orTKE} z#9$O&{Go?_>JSja_daDE`L(riiHV73Ef;BU7SuQZ_uA&0R1a$l^&FSbjbKozXdYy7 z`&D%SY;hy2fm{8Y%0fZ=ZUp!lbLa?&Dvv&M<2KUHLr$Vklwmh!vnHMnFy6?}^EgIB%S<40t1ME}M@Pua8=CIds zydU=hVx{#Te2T+4xi(QL1KNqd{@h5<2J5`#S8kU>5CFU1w(M4@Ho(0-!_&K`Q_he$ z;x6l-f^Ye&$KNqe8`3un*dyfD%>$S+u?JD-%@g(=k8XW-d*2i-L+xzdLfICn zkH6eE8o}C*;+*?CdKr&@;0Tttz1IZEw|#x%AuzuAsr;-2A1D%fX8dQ2mp^I|R1be~=S~K) z0UKPlcF~mt#~A&(AD1KX=b`xDKeo&}<0q>B3$Sbo@T~op428Z~Y2oIq%uLrn1@P@s zm)Du<09emXPfzb2)vLDq@-wa4u~tyRwm{MM0f0Dtmn8}&LK5d9Ibkp#e7e0O#RS#e z8+8s`t-=1EBen79>mAic>jbl~u=pI+w7GhUl;E@XT+P-u#>8@7U0qGj&nM3nmjmb) zURhab)^@)b=j$EiY8Cm50(u@QaQbr_nKv>t1`|(^uZ!uG3b_UPkJmRjz_n_2lS~5v z&%f?X!(dd(iB!Ye`*h+I(KAuj-JPBH^0+TZ+8H&~>wA4@Hu+VF^H2fchYF(U%aQ?P z=FKmo^Xo+*4`bYQ`}_Oa^*ZERe`E)y^66UT6YEryMGY!bYaFlvIT!}YcOpulBi)TS zdK=(*{P$-nn||Yaq2Y8fC%<+xrx+^SsCpo7tBEjFJG7dlnDYv4vz$VkUGex-gs<1sM}Nq z|KAJnKA-sjHr`u{9xl703o@26isPRAY2mxMeD{S&WoowcK@@l2?JMGEHEomSGibT~ z`^mNk$?*>ix=6~iumn3CKArWx1TwR*$UIVDi(aYxsSR;hSq*x5LVyd&ruW}vLQ^SF zVdTjC34lWdEWV9oum2G374ON*JbqDhxHK zLk!Ry>)ssVVy?OMj%z{`L649a-+n#2WHphiY02j1=9u=r@IRTnc7M)S-KMVbt`)pN z6{8Y%c2yz>PZ@lqZvl}3v!?8iX3jJ0MzrZk;nJB5wRF3xs>SzyuPHwXe0({jzA5r7 zog942HUoAKwbAXq{-ZAfusNMnURxZ#;r!$+2Jt)8iHV6KEUL8RmqQF1KT^UL5-ROzCPQcCEb+>av!^rauL z*VI%-M3~ zEnffxs?V$U!O*PX@4xKmQ%BI_{i14C{ct_}H7=SLtxJ=T(9 zQD5_+FYZGmtYc@i5qu;w}#!AxCL#k8COyQQg zVFRWV0avPgj;o(-Dir#^5tCB)na4U{F+WEBwB{O93tb*dCt3c+91byHPRmZgH|9UM z`wl3KxCsc`_%sPq60BI*FT_v@VAp>n4Rl2}DSReE$fh?4U{QH(Ex=#8m5%{>cyMx3 z0&qzndP5bn_*C`vWbeS9|lG_hrc=QS;+rzQ;tL-k{T3waQ3~OO(xen6Oi2AMersJt> z1NU8rhlkZ?OoSGg0^AKK+ZBBm^yh^wj7Rl6yHqQf%!nZ6Yw5%jAfTK^h(1|hCvL9& zG;$5xj?*(4UfK#L&BXuCghN21H{KGnENjhiOD++ld z(W+mHrgL9oV+gqYWkv;OL$&D$OQqG4ML1Bz2-~DW<3M&Ui>Hcy6N_xI-5G0LUU*Sbj7&@icAj-DljZNUw6!l&>iiMSf18R% zq^JR6bIXSU(7a!GmbMwNu%+OO{W-1U!TQ+%K}CAbNo`bt!OE-K5iKfvR4n z^!DO*ZV39NUko2m`>Y1-Kf=+8ZUX9n*+?_;jlvdxbpAU+a_H7kJAMng6$Rb2)V;mw zi1Q3=b!hrBQy7@^P1Nt^ydRyt-gMv_h*6?Jgr3%b4QHc)`tT3EAz~YTpA-KZl{c3a zI<9;_bzcL3%|K8c&30_~makaDTae(nXD3l!r`|+D+(phOv?CV*Mj?wYHp>arp?v%d z9TC5nm1oDnKw@HI65RWSHB4;3vUm3KeAaO}DV)J+J{%dwy?dgRBa{NDr4<9UR*1pt>cnPH42?Ps8e z#;sku8i1XMKsQ2<*L(cB9bDv4o!?ByZO42GF>0lXbw}#iVM=x=O1`TGFB~1DE8m6h zzY$UWZXXFH1VKx!_ESnAUth?X>{sY1;<^29>_OKxPLLt?z=r%GqIbj?t`8?|m)CVc zIV9Xx8=a^1;>u%oJ-}vuqbnxs_^XN%KI=}Z3pla+4YZaodW?+gHNYy&UcQBF)Z={O zyL?CX7P^;o3fxZ4f>C*JJ9e}x(Sjlz;XENFMT6;>)&)J{L#jd$qJcWvvw zENOH-v)n3(p<&8Xnph^Hlk#S-Az+i}A0w-?Pf?i2z4 z0EkQs5y?Mb#TI&iLJsL2t#q-w#TCWhDj}~cXJ_??`Cl>IDWdAHDrqDEN-t_+pLaiI z)XZdGAau8r)BzZE|HnrrOEo)wU9?ZvI%i1qc2&~ce7jy$v4%HZXRo40R#MgMnrXtr2u2ms;t?`StC zD~nF(8hk}K6_-E?)2EW5&eZ`7952#zF{NGYTy87P`(QF#%uYPDR%EtvUg-T+-;f&F zBNLqp@G1SiFVT2p!si~F8E8|N?T3|BKq>i>a~OX@Y#}+_&~1X(!1*WQ;5NiGYU}>` z=m9iYd6fP;(x}s&378DWKk;eOxxd56*kZznoDlitCRXXhYhUKko9gB3_=XXZ5oba( z;~Xd;=bvL6UNQ9<<=kfOW|JTvhTW(;J^FoTc^)GHnYN(^0XiY8z~RmSv7`XG)mo>A zU`BcI>Bv|LO9sD_QGwC=>wb56;VuQ}0_3>jKYo+xS%~lCRrF zjibKZ-npONxD@RXr-r{)J}*B9#TM39rp*Q$b1>=1F#%LYKJ9&q-cVmn+qzKok&2$) z*yWUX-kjTIK%V%^r8s-Dl7d(i9C;B1l)A4de=|v$A-T0ms!gonO_zrBQEH&K*QMX3 zyhKk8>vS%pOW(iq77EQ393vot5I`olIpH~?N-^XJhWBFtIk0Q|Y^mx8f|qt&YP1~4 zY;SID-D@8G>aYR?=H@rDNGa-YS6R$@V*~-^^uW0)o?7USVS+Qe;0(8{ZAVtm1et4m zjMm0!qrvHx6^8B+@qW$!o+9JK1-Uz9rbeXdOSR+-xOa~eJ{5oj&eh6RR`fg5)2^+p z3HEpn6BDxPd@wXLEU2D)=(EDv@XsbCk<4H1uguzCL}(>1H*VEp8o!si;Jb4i{1?YY zk)VXBC5QdrtobMEf}@y%Yd!Wg-lPfi%V z0VeA$Vwr*?-QI4{8R4QwhFCx+aiK&yzY|5S*Hz-srn9#~Au=9=5^HhHzkMz))8r8@ zKaBue?l~u4%py4&vb+a!lYolArDd4ZYo2_LZ|7opp(`Tpd|BTjUx{zxTq(u7y}h6% zn5oNj0hdDJJc-ZO$D*X>bYFRSc@gJCM_saSxgcWHeS-NecMl5N1fAylZs$vPo=?1# z@7q}I-plnMoA8|eJfT>7c>HZw^6I=uC|WnOI%`?C`!+EJ&CMDyaX%CT^D6j(z2}AF z)5@IR-J8f#zLihK1%C~C-A^*!w3BNzHTFf5_L@h_?BoxB`2?<#8`K`;S04<`cJYtK zu#gB&OEjuSaW3GzZhFBCKGT2yp}6h27sHk!wFfS==2(g?G zbss!fz@QH)jG;UV;iIKQA{+Ga7(KuW2R_r4g0GlA=~q5yV&`2^Q?_CLSxoB1JrUfP z>zHF$w~MRk4ShA^TLbIB{IVndyV0SK3FzmaaP<>DDSoIu`0Mg`E&f@rO>ORO5aMl1 zz)MH=;&N15a;u!(eY0rt{tswovj;@Ls;$AJ^VgDj22JnPmNd9(3|?N-WUf&bYGzh^ zFb7PlaNJzZC_n!2GMUj0Uki(8z1@r48i}FFA!+7nVq#9(@alFIhdUEGIGUtW&%aaO zAIBI6+#ta+Defa&%dR*dR=6viq2*4cEb!F&lJ{+>&7{h4>yKb*p}RwI;8Leh==H(8 zl?S_-`v_nBIu<3X$7dQTAl91#q`P6#CY-2Gfvu#WTPVCh+~rx3L*F~N?2+<=Hr1dL ztHWAvRAuD_m{x+I62}8Q%jSDyoiJFloKIelqjc+45>bHf?Co_XFN>+$b~Pj$&=izA zhqM4&mfwsJ@L?&~d073(g1x=0=Z^zns=s#~rqxzSSIEGiBW^y4o#;0i{E{5h=0FOU zU>bRG+57Q=yqgxHLL+pY$(jrREVb&3?`D1g}&+ zbXtEhrL1nst|3ADMVrcCKxAJ(gBip*T|)*q%G{-s_P>W% zDq9{)&o|764sy2m!@{Z5X#WkgZ5Q3@jQDcC6>g$oWGG>d0Z>i^INVh-#&Uq@f2MWS zXsD{LCQmvdiXMvmRCMQgpe7ws2ICO7ZNYOz11A#qCBQZTExolb%&x7=L3!edeFKlt zzo-Ge}#A_8rULJM*sZ@Th^D!i@RP)8FR?$#wj5rT_&59A`f*41h zin}s(dGa=WNk{ZC)=Ds~mW6lyrb%x*TDK$A9fNY0tN7+PYI1a(nDRQk;@0Zz16mxa z#$kxUJ!yu|{+`aP)Na<(vb{RI>2qakI)734@1L0T{jA_wD5 zi;uP`5H~pt3v8`(UN8E?d~iA&Bn~EE_4uIpp&}11wGlU5M5wjnU$ztuKygB$rA=4A zd4V0wT~f4d+z}U0j;6KjdkEC}Ua_v>Y^JHAch9g0Uu&SzqfI9y(T4cije{8Q)d-nI z3UR7I-BFA;khe}-=xd2RlE=Xz66-98#BwlkiFr~RO><* zW6(+dqZcIuNzWz50Yg|p5$j4gF7W=_2g+)M$!xM8P2=N=Q>qXOgC~b?XmCsl;(5WD zKj*n^IXycXuN}JreWDkiuU7NO%xmE>kSq}kY!emp6)>Af(juKWBc0G3@L%>9;_JJL z2$khc6d85Q`l|W}mI#e;Og5b#y76v6&p2>eZ3IWC$yy4LPTa!^a!(>EXbb9aGE6p1 zTlxxHS%teMn72L7IF=IHwJj>Y6ku;)E%bv+Oe&{?KJmfp<$a%S_pdqicU0=WsVp4@ zB10Jaio1`8{o7*01bVU-3p2yg26pyv6qK+C3a60ucT%^>v(fVwzhnJlfotBjz=Nv? zM-%~S#7w)t$JQoF@;zms8`9W?Ha{zv<82WLPD=F+I@#I{L%dGB5+2a?WJX{NBTl$8 zHn+HKCtqof8ve=F^aP~BXb}yAZmx9AAa1eQBRo?|pE=)l28^LnQ{RiMBPXF;RT@wg zK}iH*vw|Sjvq>5{q`N7thU~Ano`iqwhkG7&$yUO35ydf~QWn{=beg*#gws%&d5%WZ zdb(%{vP?-QPeNG zWS{NKJ|N2Wp1#Z4*^~VBbSNtkXSec{1B%?S^hMPD*c*=9$>U7B;R=nowcm zfIq$g18*6u^dX$;ZVL`B*)>85@8j)bZ_06}qp0U#Rsx;eg(WNy7_H2ySBSp{Vb1$# zB;qi$q(2$J?oU3>MciTd z@p&`p%a|VW6^vu8s0ogVDS$=Zs0+L+F6Ma2XkqZz@>y%GWy%4h1GC9mdJ^TCU+@?y z9sje604xSaG1V)am&X5_{>y`FP?-OY)rboPMO7%UWF$A=h~8kQsTz@KVKR^`w>6=r zqawf_znfRU%>UF4rP|Vevo&6j8F<@s?gI2z6~(5LQ8`7zQ^HxpkGW)Xl6+`HVzo#f z<*zO%eE*$NY^B+0#8w9c!Ee5gRI?=tKHF+>y8UNE7DdLor4Kauh;_YejXZx)u9iAY z`k|Z*Lj)I-0{sqyu3KO_L`omDX=*kzDi+-MyYt?X%!=3{faGr({$OVdxOIr;kLaqk z@nGAJLH?Z1F_b)`W~F|Ik47=+JH3kuyGkixG} zbhJ?FQxHWR#7(7pWVNaLK2F*gG^Bph z%{<`mk$d1S)5DZsd~sssT_V1&19XOG3pDB@MJODCCZZO0Wq_C>=Fx z4CedkU4coGj`!`H^Y$O!xDHQJPL}z9jsMxmP^+`=CF18>b`OrceV*()(mvL9AVST>6p!c- z*kW>Y10OVEu#L`mdLhfaS){2>8YXP=aoGHSDilYw;(tkCwkDckA+mxFUb^fzkxCam z#=7Nx(Xj(`N}Wq3{9V##6un<$H_!GUG_4VN(%L0_yCB=iUb2A+rmh-Rjcbdn58-1? zRJ2>d1`x%9*#1VIpr>f%ExYi#A?!DepUD3Lb0a(IVH{MAS2qYxc#)XayJGg0j;^8S zr2(es&S0QLag4ErROOT8A_YL|MHL!Q&joOe-z9`u;(Hn1QRDD4VuOAt$|Y{egQDtC z>VIgj0(VwqD%X^leKo!H>mm2|^aM!#u()J}TZ$P=@XfhUt6$jd!w^m{iS<^N()7*U zSVP<#AXM zq8a=B)l6a?vmHJVaV3vFqayln`6tf`va@3SroOov9X;a%L(aG~a7-a@ih-G;{zn^6 ztpWUp@dr^z7zJW3qLNCg2Qt!1V?mqZ9O(K>xQLYhrJ6r%^H2xF@kWtbYa|pcW4@&q zW}Q6@k07ku>gTOz<3j?Sf5(zhT~jkIL4np62_xnjGvsh@^oM)v>Zt8M}? zkC@vZWCQnQD^zLpq5u?3XP`X~TPRKv=;w7xc+;$+(k#ZAHk(NdIR$z^mocB8U$V6= zN4oigxmBRUBkwCTH29Wv=m%SU*M$lpWQVNC&AEr5(UtHV8 z>??Atyg=08=$>C{jMs}lJfR?dlHAGXJIOL!oxiUKN)Sr8D_{9U25a5@<*JI!s`fez%Z@g5bB6MbQpd6l(0)-a6W^MoEv% zwC3k89AYfA4rk^C$83wLzMe~ZRHFoc0thp!NS z>ELh|*#!c&dEV0^z{}=tuChN2G{%;}Tbs<=!GPo+H?ZPQ1D^jrXi|f&nr)X`y`hec zf)Si9U`-;vTKiny-A7bNuUGa;2s>TSS{j4FyBV!FCKbR^6IMdM(wd@fr$5C{(x_~~ zC(pBkiJbz>CU@l^?>OFu|H*A&W>`*M)aw9uYV2&09q|DX!AM3K>VVdR!P{?b#P6FfZ4H)0T6ROBJ;;H9 zTr=<4(XOJL5=TO8s6lWl^04?zZ(s=#O!iLn1<#>y$t7O`wlo&J9p;puyk+T}y~e1H zmbZboHASr0;TBQrlj_=UW&O6l+4?qVC}!yAAIum$vxf|cEHKA>v_AdtH2)1;5L0vScuz32+sY;*hmG(iXuy2Yalsr5zABi_X|vQFcDi- zGl(d*tE7)?4DMvp>*V@i`J^Zy>25|9cwl?!=ng+6m>bmCN4zfH&cN3K>6{3PnnAvK zI>s0B8s_cm_wypZ;bSsC5{RRknX%$J&m-}e2=}McOxOjJkK8q>Fyhl@psPgMf#4H( zO$r#7AbqTr@hk)JHu!`fJ57CwUQH%BHLIRtIaS_KpjoZ}`t24v=%?68)zXiAC7ZOD zYJc{Z@Dka#bAW31npXObaDxgHd0Rjnb`P%SgF^11OE`KnJU3uk&9=a{ z(yMzHmfd7hkSe>+EZ^LUxgdFd=$UnPNcsqgXH4dNb24ArvosJJlmA1e!5c*AjUzci z82>qfZ&Ws>|J-}vyOpMqc~AF=v670M=t#d`8`rhlMCiAyc!@A|hK?ndZ03!Uofpe0 zHPa+CLGdjCC7LLYpy8rd)&Q43x%7Fve>vjlWFm=)O6TP7*opJ2C@qQSB;W%y|Wqqt5}O|jOw2~ zGsGO$5Cmo2Kp1gxR1BOVTWpm%?Ra-(4K8xacwD+}7Udmr)LGx5^gf>7t_i+F{T3b4 zK9h_vPOzZvkj!iZSrEG-KgZ?S^n-yxDU+3w(1^MvPEProXNPfSXS~L7(3JJZON4vH z=+-{I*Vz)2SKI#AvM$47c*?c_JwBQyd^}8^66sm6$c&G&tWI`G_)@SYVqmDxT=(Od>yvZr7cUCPZ8y$A1PnO9d){eQb; zX9as7=EiwJ2w(}r3Yzz_W1|jyXy1sRLo0Z4%Z*-xA3yoY#=JfyHp>0Wa;xuPm!fK< zQ!_VJcS0y>3d+qgq`dRf1M6S#4hV$u?QVLghQXsAPF_+ARdjBUkx`n_Di6??!H7Q_K$yYb& zTGuR^e#q1g@pmv2opA!|{Lf|~KQg~BHnZ;8L^^}_dQY*}y{J6=_H-kbtStVZOqP8b zI7D+?tj$aGxnna|WX4>MnHho zEAWk~)g^Ul5NHX;|0-e4{JCPbdsE}n6~MXo;8+BbHCdKsJLo)mUJzJa$oKo3{R*P# zc0}HwPN4LE-n9#O`>>v&bbv7R{WcR#KSOk^kc+qWE5Y|e1%iuTEl{|eF40GCaFA%e z$L&J{-kzZ$-pUP_Q(X;<8}v8(_LfO=jB8?`6BU{v@>4KqBNcwCm|pg)ci&fzi>0Gg ztgpLb9(n8K<6{cNF1w|+MGGY#h&deVW&mpJyZk7EE{`2R9l!>+9bhE?JK9Zk;QL+$ z0~=F(-NRI5MTR{=7_>AdKu^?W+$B3g)|eM{HsqJfhf!J_nv z*GNRh{I>tr=YnMCZx?PE40{h)C<8COOW!jNc@d~PuJhEMF1(W!kCxS#XHH}A89as; zgMTzN!T%^gjw*rv5h=s_2??Nz1SR-yr+zm8Q5Ycc@LNmn390YPYG{{8=gs{W@icXl zxfl5qdz^OR1fEZgsf{y_tm}4)x$aM;bpR<_G4ejcx~Yze6^1Ahg*Q)MWsTOz@55l1 zJ=q%M9!pS`Umac0nAC0yr~_#h-lo6`#~1<_w}0$yLO23$jr}uXjZCKP=(>_CbnPDV zOyVqrHsy^lcEmPjS2Lfxd>mbeD1_B>BG&Le6m7%29XyBBW$+!c7k}tG3 zj>0sHiS1vGabd&ZU|8yuf2b%doTOGM_z*fXD&fmY!)uAl1}}$mb`#}EvT79CI4w&< z%0n?uCcg5>^WpMEEaOm?@L^F#*Bgx0VfrMklYmljxO1zF{2uWXt}>|?k4|;!F9kB? zn!FfI$>@s{;xi_ceZ=JteO(A}LLxR2&=>FU5&VKYaV=Zu+EvX{l1HkSm7T7a^Rl1F z;M3@2ikbJ&)O#!mI+HaRD`+6 z2VRAY3zmpJtU_Ikm#JG4BiMftMzJY4Hr@>&Y!I4|IHOIc-!N2YKKoBp^4Zi~MGa7x z(3}&WZ}04D{BI|L6-d za80OVtX*>5QzgiD);G&v0tZ3mec{nt`7-CfY}W4B;*>deoAkQ7PnfsZTK?90ki|OR zdvjADpX&bz&AGnW?K3#or4~1CvXeEtvs&kC(cv!N{l5FQ?|uDuh5h3*T(73*PMJNy z^8AkTmrG@i`~SYllb~kM@3Q|~;!2U7Av^jry6no7Ckvpa; zr%K-WTgO|!eeVjc4*qO@Ke5thU!m+;|2eBfc5O*Kx=iCh<9~~k(`7t?Dk{r(DWdExL~e}>NbfFY8z&{D0hK&D9R~pVFa#& zzEvnwY39d0ULY_0k%;5wZP+~{|drGLtVR1=A{8*^B_9;zA7-MivED2~NV_V0OW z=(;CLFu_rv?cl8Za}SnyFTZr_igV=J Date: Wed, 15 Oct 2025 16:24:45 +0100 Subject: [PATCH 07/12] refactor: remove user defined instant verification flag from UI --- .../ui/screens/phone/EnterPhoneNumberUI.kt | 24 -------------- .../ui/screens/phone/PhoneAuthScreen.kt | 32 ++++++------------- .../composeapp/ui/screens/PhoneAuthMain.kt | 2 -- 3 files changed, 9 insertions(+), 49 deletions(-) diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/phone/EnterPhoneNumberUI.kt b/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/phone/EnterPhoneNumberUI.kt index 9bb888873..54b65af8a 100644 --- a/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/phone/EnterPhoneNumberUI.kt +++ b/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/phone/EnterPhoneNumberUI.kt @@ -65,8 +65,6 @@ fun EnterPhoneNumberUI( configuration: AuthUIConfiguration, isLoading: Boolean, phoneNumber: String, - useInstantVerificationEnabled: Boolean, - onUseInstantVerificationChange: (Boolean) -> Unit, selectedCountry: CountryData, onPhoneNumberChange: (String) -> Unit, onCountrySelected: (CountryData) -> Unit, @@ -128,26 +126,6 @@ fun EnterPhoneNumberUI( } ) Spacer(modifier = Modifier.height(16.dp)) - if (provider.isInstantVerificationEnabled) { - Row( - modifier = Modifier - .align(Alignment.End) - .clickable { - onUseInstantVerificationChange(!useInstantVerificationEnabled) - }, - verticalAlignment = Alignment.CenterVertically, - ) { - Text("Use instant verification for SMS") - Spacer(modifier = Modifier.width(8.dp)) - Icon( - imageVector = if (useInstantVerificationEnabled) Icons.Default.CheckBox else - Icons.Default.CheckBoxOutlineBlank, - contentDescription = "", - tint = MaterialTheme.colorScheme.primary, - ) - } - Spacer(modifier = Modifier.height(16.dp)) - } Row( modifier = Modifier @@ -199,8 +177,6 @@ fun PreviewEnterPhoneNumberUI() { }, isLoading = false, phoneNumber = "", - useInstantVerificationEnabled = true, - onUseInstantVerificationChange = { value -> }, selectedCountry = CountryUtils.getDefaultCountry(), onPhoneNumberChange = {}, onCountrySelected = {}, diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/phone/PhoneAuthScreen.kt b/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/phone/PhoneAuthScreen.kt index f7928b49c..db8b83673 100644 --- a/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/phone/PhoneAuthScreen.kt +++ b/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/phone/PhoneAuthScreen.kt @@ -14,9 +14,9 @@ package com.firebase.ui.auth.compose.ui.screens.phone -import android.app.Activity import android.content.Context import android.util.Log +import androidx.activity.compose.LocalActivity import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState @@ -27,7 +27,6 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import com.firebase.ui.auth.compose.AuthException import com.firebase.ui.auth.compose.AuthState import com.firebase.ui.auth.compose.FirebaseAuthUI @@ -44,7 +43,6 @@ import com.google.firebase.auth.AuthResult import com.google.firebase.auth.PhoneAuthProvider import kotlinx.coroutines.delay import kotlinx.coroutines.launch -import androidx.activity.compose.LocalActivity enum class PhoneAuthStep { /** @@ -70,8 +68,6 @@ enum class PhoneAuthStep { * @param error An optional error message to display to the user. * @param phoneNumber (Step: [PhoneAuthStep.EnterPhoneNumber]) The current value of the phone * number input field. - * @param useInstantVerificationEnabled - * @param onUseInstantVerificationChange * @param onPhoneNumberChange (Step: [PhoneAuthStep.EnterPhoneNumber]) A callback to be invoked * when the phone number input changes. * @param selectedCountry (Step: [PhoneAuthStep.EnterPhoneNumber]) The currently selected country @@ -100,8 +96,6 @@ class PhoneAuthContentState( val isLoading: Boolean = false, val error: String? = null, val phoneNumber: String, - val useInstantVerificationEnabled: Boolean, - val onUseInstantVerificationChange: (Boolean) -> Unit, val onPhoneNumberChange: (String) -> Unit, val selectedCountry: CountryData, val onCountrySelected: (CountryData) -> Unit, @@ -148,8 +142,6 @@ fun PhoneAuthScreen( val step = rememberSaveable { mutableStateOf(PhoneAuthStep.EnterPhoneNumber) } val phoneNumberValue = rememberSaveable { mutableStateOf(provider.defaultNumber ?: "") } - val useInstantVerificationEnabled = - rememberSaveable { mutableStateOf(provider.isInstantVerificationEnabled) } val verificationCodeValue = rememberSaveable { mutableStateOf("") } val selectedCountry = remember { mutableStateOf( @@ -199,16 +191,14 @@ fun PhoneAuthScreen( is AuthState.SMSAutoVerified -> { // Auto-verification succeeded, sign in with the credential - if (useInstantVerificationEnabled.value) { - coroutineScope.launch { - try { - authUI.signInWithPhoneAuthCredential( - config = configuration, - credential = state.credential - ) - } catch (e: Exception) { - // Error will be handled by authState flow - } + coroutineScope.launch { + try { + authUI.signInWithPhoneAuthCredential( + config = configuration, + credential = state.credential + ) + } catch (e: Exception) { + // Error will be handled by authState flow } } } @@ -230,10 +220,6 @@ fun PhoneAuthScreen( isLoading = isLoading, error = errorMessage, phoneNumber = phoneNumberValue.value, - useInstantVerificationEnabled = useInstantVerificationEnabled.value, - onUseInstantVerificationChange = { value -> - useInstantVerificationEnabled.value = value - }, onPhoneNumberChange = { number -> phoneNumberValue.value = number }, diff --git a/composeapp/src/main/java/com/firebase/composeapp/ui/screens/PhoneAuthMain.kt b/composeapp/src/main/java/com/firebase/composeapp/ui/screens/PhoneAuthMain.kt index cf4cf456a..47e7bb8c7 100644 --- a/composeapp/src/main/java/com/firebase/composeapp/ui/screens/PhoneAuthMain.kt +++ b/composeapp/src/main/java/com/firebase/composeapp/ui/screens/PhoneAuthMain.kt @@ -101,8 +101,6 @@ fun PhoneAuthMain( configuration = configuration, isLoading = state.isLoading, phoneNumber = state.phoneNumber, - useInstantVerificationEnabled = state.useInstantVerificationEnabled, - onUseInstantVerificationChange = state.onUseInstantVerificationChange, selectedCountry = state.selectedCountry, onPhoneNumberChange = state.onPhoneNumberChange, onCountrySelected = state.onCountrySelected, From 4266dd6fe428c011b8813c559aebb3fb32951d3f Mon Sep 17 00:00:00 2001 From: Ademola Fadumo Date: Thu, 16 Oct 2025 02:56:28 +0100 Subject: [PATCH 08/12] fix: bump robolectric to 4.15.1 required because dismissing a ModalBottomSheet failed in compose ui tests --- buildSrc/src/main/kotlin/Config.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/Config.kt b/buildSrc/src/main/kotlin/Config.kt index 424a9fb0c..393af44dc 100644 --- a/buildSrc/src/main/kotlin/Config.kt +++ b/buildSrc/src/main/kotlin/Config.kt @@ -103,7 +103,7 @@ object Config { const val mockitoCore = "org.mockito:mockito-core:5.19.0" const val mockitoInline = "org.mockito:mockito-inline:5.2.0" const val mockitoKotlin = "org.mockito.kotlin:mockito-kotlin:6.0.0" - const val robolectric = "org.robolectric:robolectric:4.14" + const val robolectric = "org.robolectric:robolectric:4.15.1" const val core = "androidx.test:core:1.5.0" const val archCoreTesting = "androidx.arch.core:core-testing:2.1.0" From 47945b40a1cf907a08c282ab4c0fdc671fdca8ef Mon Sep 17 00:00:00 2001 From: Ademola Fadumo Date: Thu, 16 Oct 2025 03:07:07 +0100 Subject: [PATCH 09/12] test: e2e tests for PhoneAuthScreen --- .../string_provider/AuthUIStringProvider.kt | 11 +- .../DefaultAuthUIStringProvider.kt | 11 +- .../compose/ui/components/CountrySelector.kt | 81 ++-- .../ui/screens/phone/PhoneAuthScreen.kt | 8 +- auth/src/main/res/values/strings.xml | 2 + .../com/firebase/composeapp/MainActivity.kt | 2 +- .../ui/auth/compose/testutil/Constants.kt | 3 + .../ui/auth/compose/testutil/EmulatorApi.kt | 47 +- .../compose/ui/screens/EmailAuthScreenTest.kt | 30 +- .../compose/ui/screens/PhoneAuthScreenTest.kt | 406 ++++++++++++++++++ 10 files changed, 522 insertions(+), 79 deletions(-) create mode 100644 e2eTest/src/test/java/com/firebase/ui/auth/compose/testutil/Constants.kt create mode 100644 e2eTest/src/test/java/com/firebase/ui/auth/compose/ui/screens/PhoneAuthScreenTest.kt diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/string_provider/AuthUIStringProvider.kt b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/string_provider/AuthUIStringProvider.kt index cede1890f..67b24fb1d 100644 --- a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/string_provider/AuthUIStringProvider.kt +++ b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/string_provider/AuthUIStringProvider.kt @@ -210,14 +210,11 @@ interface AuthUIStringProvider { /** Invalid verification code error */ val invalidVerificationCode: String - /** Country code label */ - val countryCode: String + /** Select country modal sheet title */ + val countrySelectorModalTitle: String - /** Select country dialog title */ - val selectCountry: String - - /** Search countries hint */ - val searchCountries: String + /** Select country modal sheet input field hint */ + val searchCountriesHint: String // Provider Picker Strings /** Common button text for sign in */ diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/string_provider/DefaultAuthUIStringProvider.kt b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/string_provider/DefaultAuthUIStringProvider.kt index e944c4823..130eb74fe 100644 --- a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/string_provider/DefaultAuthUIStringProvider.kt +++ b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/string_provider/DefaultAuthUIStringProvider.kt @@ -204,14 +204,11 @@ class DefaultAuthUIStringProvider( override val invalidVerificationCode: String get() = localizedContext.getString(R.string.fui_incorrect_code_dialog_body) - override val countryCode: String - get() = localizedContext.getString(R.string.fui_country_hint) - - override val selectCountry: String - get() = localizedContext.getString(R.string.fui_country_hint) + override val countrySelectorModalTitle: String + get() = localizedContext.getString(R.string.fui_country_selector_title) - override val searchCountries: String - get() = localizedContext.getString(R.string.fui_country_hint) + override val searchCountriesHint: String + get() = localizedContext.getString(R.string.fui_search_country_field_hint) /** * Multi-Factor Authentication Strings diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/ui/components/CountrySelector.kt b/auth/src/main/java/com/firebase/ui/auth/compose/ui/components/CountrySelector.kt index cafabf02f..06a743fe2 100644 --- a/auth/src/main/java/com/firebase/ui/auth/compose/ui/components/CountrySelector.kt +++ b/auth/src/main/java/com/firebase/ui/auth/compose/ui/components/CountrySelector.kt @@ -29,6 +29,8 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowDropDown +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme @@ -44,7 +46,11 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.dp import com.firebase.ui.auth.compose.configuration.string_provider.DefaultAuthUIStringProvider import com.firebase.ui.auth.compose.data.ALL_COUNTRIES @@ -67,7 +73,7 @@ fun CountrySelector( selectedCountry: CountryData, onCountrySelected: (CountryData) -> Unit, enabled: Boolean = true, - allowedCountries: Set? = null + allowedCountries: Set? = null, ) { val context = LocalContext.current val stringProvider = DefaultAuthUIStringProvider(context) @@ -101,7 +107,10 @@ fun CountrySelector( .clickable(enabled = enabled) { showBottomSheet = true } - .padding(start = 8.dp), + .padding(start = 8.dp) + .semantics { + contentDescription = "Country selector" + }, verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp) ) { @@ -111,7 +120,7 @@ fun CountrySelector( ) Text( text = selectedCountry.dialCode, - style = MaterialTheme.typography.bodyLarge + style = MaterialTheme.typography.bodyLarge, ) Icon( imageVector = Icons.Default.ArrowDropDown, @@ -135,7 +144,7 @@ fun CountrySelector( .padding(bottom = 16.dp) ) { Text( - text = stringProvider.selectCountry, + text = stringProvider.countrySelectorModalTitle, style = MaterialTheme.typography.headlineSmall, modifier = Modifier.padding(bottom = 16.dp) ) @@ -143,7 +152,7 @@ fun CountrySelector( OutlinedTextField( value = searchQuery, onValueChange = { searchQuery = it }, - label = { Text(stringProvider.searchCountries) }, + label = { Text(stringProvider.searchCountriesHint) }, modifier = Modifier.fillMaxWidth() ) @@ -153,44 +162,50 @@ fun CountrySelector( modifier = Modifier .fillMaxWidth() .height(500.dp) + .testTag("CountrySelector LazyColumn") ) { items(filteredCountries) { country -> - Row( - modifier = Modifier - .fillMaxWidth() - .clickable { - onCountrySelected(country) - scope.launch { - sheetState.hide() - }.invokeOnCompletion { - if (!sheetState.isVisible) { - showBottomSheet = false - searchQuery = "" - } - } + Button( + onClick = { + onCountrySelected(country) + scope.launch { + sheetState.hide() + showBottomSheet = false + searchQuery = "" } - .padding(vertical = 12.dp, horizontal = 8.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically + }, + colors = ButtonDefaults.buttonColors( + contentColor = MaterialTheme.colorScheme.onSurfaceVariant, + containerColor = Color.Transparent + ), + contentPadding = PaddingValues.Zero ) { Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 12.dp, horizontal = 8.dp), + horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = country.flagEmoji, + style = MaterialTheme.typography.headlineMedium + ) + Spacer(modifier = Modifier.width(12.dp)) + Text( + text = country.name, + style = MaterialTheme.typography.bodyLarge + ) + } Text( - text = country.flagEmoji, - style = MaterialTheme.typography.headlineMedium - ) - Spacer(modifier = Modifier.width(12.dp)) - Text( - text = country.name, - style = MaterialTheme.typography.bodyLarge + text = country.dialCode, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant ) } - Text( - text = country.dialCode, - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant - ) } } } diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/phone/PhoneAuthScreen.kt b/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/phone/PhoneAuthScreen.kt index db8b83673..ebae031e5 100644 --- a/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/phone/PhoneAuthScreen.kt +++ b/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/phone/PhoneAuthScreen.kt @@ -150,8 +150,9 @@ fun PhoneAuthScreen( } ?: CountryUtils.getDefaultCountry() ) } - val fullPhoneNumber = + val fullPhoneNumber = remember(selectedCountry.value, phoneNumberValue.value) { CountryUtils.formatPhoneNumber(selectedCountry.value.dialCode, phoneNumberValue.value) + } val verificationId = rememberSaveable { mutableStateOf(null) } val forceResendingToken = rememberSaveable { mutableStateOf(null) } @@ -186,7 +187,7 @@ fun PhoneAuthScreen( verificationId.value = state.verificationId forceResendingToken.value = state.forceResendingToken step.value = PhoneAuthStep.EnterVerificationCode - resendTimerSeconds.intValue = 60 // Start 60-second countdown + resendTimerSeconds.intValue = provider.timeout.toInt() // Start 60-second countdown } is AuthState.SMSAutoVerified -> { @@ -270,7 +271,7 @@ fun PhoneAuthScreen( phoneNumber = fullPhoneNumber, forceResendingToken = forceResendingToken.value, ) - resendTimerSeconds.intValue = 60 // Restart timer + resendTimerSeconds.intValue = provider.timeout.toInt() // Restart timer } catch (e: Exception) { // Error will be handled by authState flow } @@ -280,7 +281,6 @@ fun PhoneAuthScreen( resendTimer = resendTimerSeconds.intValue, onChangeNumberClick = { step.value = PhoneAuthStep.EnterPhoneNumber - //phoneNumberValue.value = "" verificationCodeValue.value = "" verificationId.value = null forceResendingToken.value = null diff --git a/auth/src/main/res/values/strings.xml b/auth/src/main/res/values/strings.xml index f784d4d4e..96df2a488 100644 --- a/auth/src/main/res/values/strings.xml +++ b/auth/src/main/res/values/strings.xml @@ -36,6 +36,8 @@ Email Phone Number Country + Select a country + Select for country e.g. +1, "US" Password Confirm Password New password diff --git a/composeapp/src/main/java/com/firebase/composeapp/MainActivity.kt b/composeapp/src/main/java/com/firebase/composeapp/MainActivity.kt index 20e9fdc71..33423b1a5 100644 --- a/composeapp/src/main/java/com/firebase/composeapp/MainActivity.kt +++ b/composeapp/src/main/java/com/firebase/composeapp/MainActivity.kt @@ -85,7 +85,7 @@ class MainActivity : ComponentActivity() { defaultCountryCode = null, allowedCountries = emptyList(), smsCodeLength = 6, - timeout = 60L, + timeout = 120L, isInstantVerificationEnabled = true ) ) diff --git a/e2eTest/src/test/java/com/firebase/ui/auth/compose/testutil/Constants.kt b/e2eTest/src/test/java/com/firebase/ui/auth/compose/testutil/Constants.kt new file mode 100644 index 000000000..5f538e8c5 --- /dev/null +++ b/e2eTest/src/test/java/com/firebase/ui/auth/compose/testutil/Constants.kt @@ -0,0 +1,3 @@ +package com.firebase.ui.auth.compose.testutil + +const val AUTH_STATE_WAIT_TIMEOUT_MS = 5_000L diff --git a/e2eTest/src/test/java/com/firebase/ui/auth/compose/testutil/EmulatorApi.kt b/e2eTest/src/test/java/com/firebase/ui/auth/compose/testutil/EmulatorApi.kt index 731770a67..aff906c85 100644 --- a/e2eTest/src/test/java/com/firebase/ui/auth/compose/testutil/EmulatorApi.kt +++ b/e2eTest/src/test/java/com/firebase/ui/auth/compose/testutil/EmulatorApi.kt @@ -7,11 +7,26 @@ import java.net.HttpURLConnection internal class EmulatorAuthApi( private val projectId: String, emulatorHost: String, - emulatorPort: Int + emulatorPort: Int, ) { private val httpClient = HttpClient(host = emulatorHost, port = emulatorPort) + /** + * Clears all data from the Firebase Auth Emulator. + * + * This function calls the emulator's clear data endpoint to remove all accounts, + * OOB codes, and other authentication data. This ensures test isolation by providing + * a clean slate for each test. + */ + fun clearEmulatorData() { + try { + clearAccounts() + } catch (e: Exception) { + println("WARNING: Exception while clearing emulator data: ${e.message}") + } + } + fun clearAccounts() { httpClient.delete("/emulator/v1/projects/$projectId/accounts") { connection -> val responseCode = connection.responseCode @@ -37,6 +52,36 @@ internal class EmulatorAuthApi( ?: throw Exception("No VERIFY_EMAIL OOB code found for user email: $email") } + fun fetchVerifyPhoneCode(phone: String): String { + val payload = + httpClient.get("/emulator/v1/projects/$projectId/verificationCodes") { connection -> + val responseCode = connection.responseCode + if (responseCode != HttpURLConnection.HTTP_OK) { + throw Exception("Failed to get verification codes: HTTP $responseCode") + } + + connection.inputStream.bufferedReader().use { it.readText() } + } + + val verificationCodes = JSONObject(payload).optJSONArray("verificationCodes") ?: JSONArray() + + return (0 until verificationCodes.length()) + .asSequence() + .mapNotNull { index -> verificationCodes.optJSONObject(index) } + .lastOrNull { json -> + val jsonPhone = json.optString("phoneNumber") + // Try matching with and without country code prefix + // The emulator may store the phone with a country code like +1, +49, etc. + jsonPhone.endsWith(phone) || + phone.endsWith(jsonPhone.removePrefix("+")) || + jsonPhone == phone || + jsonPhone == "+$phone" + } + ?.optString("code") + ?.takeIf { it.isNotBlank() } + ?: throw Exception("No phone verification code found for phone: $phone") + } + private fun fetchOobCodes(): JSONArray { val payload = httpClient.get("/emulator/v1/projects/$projectId/oobCodes") { connection -> val responseCode = connection.responseCode diff --git a/e2eTest/src/test/java/com/firebase/ui/auth/compose/ui/screens/EmailAuthScreenTest.kt b/e2eTest/src/test/java/com/firebase/ui/auth/compose/ui/screens/EmailAuthScreenTest.kt index 773ea1981..602890412 100644 --- a/e2eTest/src/test/java/com/firebase/ui/auth/compose/ui/screens/EmailAuthScreenTest.kt +++ b/e2eTest/src/test/java/com/firebase/ui/auth/compose/ui/screens/EmailAuthScreenTest.kt @@ -22,6 +22,7 @@ import com.firebase.ui.auth.compose.configuration.authUIConfiguration import com.firebase.ui.auth.compose.configuration.auth_provider.AuthProvider import com.firebase.ui.auth.compose.configuration.string_provider.AuthUIStringProvider import com.firebase.ui.auth.compose.configuration.string_provider.DefaultAuthUIStringProvider +import com.firebase.ui.auth.compose.testutil.AUTH_STATE_WAIT_TIMEOUT_MS import com.firebase.ui.auth.compose.testutil.EmulatorAuthApi import com.firebase.ui.auth.compose.testutil.awaitWithLooper import com.google.common.truth.Truth.assertThat @@ -30,8 +31,6 @@ import com.google.firebase.FirebaseOptions import com.google.firebase.auth.AuthResult import com.google.firebase.auth.FirebaseUser import com.google.firebase.auth.actionCodeSettings -import org.json.JSONArray -import org.json.JSONObject import org.junit.After import org.junit.Before import org.junit.Rule @@ -41,10 +40,6 @@ import org.mockito.MockitoAnnotations import org.robolectric.RobolectricTestRunner import org.robolectric.Shadows.shadowOf import org.robolectric.annotation.Config -import java.net.HttpURLConnection -import java.net.URL - -private const val AUTH_STATE_WAIT_TIMEOUT_MS = 5_000L @Config(sdk = [34]) @RunWith(RobolectricTestRunner::class) @@ -93,7 +88,7 @@ class EmailAuthScreenTest { ) // Clear emulator data - clearEmulatorData() + emulatorApi.clearEmulatorData() } @After @@ -102,7 +97,7 @@ class EmailAuthScreenTest { FirebaseAuthUI.clearInstanceCache() // Clear emulator data - clearEmulatorData() + emulatorApi.clearEmulatorData() } @Test @@ -525,7 +520,7 @@ class EmailAuthScreenTest { configuration: AuthUIConfiguration, onSuccess: ((AuthResult) -> Unit) = {}, onError: ((AuthException) -> Unit) = {}, - onCancel: (() -> Unit) = {} + onCancel: (() -> Unit) = {}, ) { EmailAuthScreen( context = applicationContext, @@ -583,23 +578,6 @@ class EmailAuthScreenTest { } } - /** - * Clears all data from the Firebase Auth Emulator. - * - * This function calls the emulator's clear data endpoint to remove all accounts, - * OOB codes, and other authentication data. This ensures test isolation by providing - * a clean slate for each test. - */ - private fun clearEmulatorData() { - if (::emulatorApi.isInitialized) { - try { - emulatorApi.clearAccounts() - } catch (e: Exception) { - println("WARNING: Exception while clearing emulator data: ${e.message}") - } - } - } - /** * Ensures a fresh user exists in the Firebase emulator with the given credentials. * If a user already exists, they will be deleted first. diff --git a/e2eTest/src/test/java/com/firebase/ui/auth/compose/ui/screens/PhoneAuthScreenTest.kt b/e2eTest/src/test/java/com/firebase/ui/auth/compose/ui/screens/PhoneAuthScreenTest.kt new file mode 100644 index 000000000..9b861f589 --- /dev/null +++ b/e2eTest/src/test/java/com/firebase/ui/auth/compose/ui/screens/PhoneAuthScreenTest.kt @@ -0,0 +1,406 @@ +package com.firebase.ui.auth.compose.ui.screens + +import android.content.Context +import android.os.Looper +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.assertIsEnabled +import androidx.compose.ui.test.assertTextContains +import androidx.compose.ui.test.hasSetTextAction +import androidx.compose.ui.test.hasText +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithContentDescription +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.performScrollTo +import androidx.compose.ui.test.performScrollToNode +import androidx.compose.ui.test.performTextInput +import androidx.test.core.app.ApplicationProvider +import com.firebase.ui.auth.compose.AuthException +import com.firebase.ui.auth.compose.AuthState +import com.firebase.ui.auth.compose.FirebaseAuthUI +import com.firebase.ui.auth.compose.configuration.AuthUIConfiguration +import com.firebase.ui.auth.compose.configuration.authUIConfiguration +import com.firebase.ui.auth.compose.configuration.auth_provider.AuthProvider +import com.firebase.ui.auth.compose.configuration.string_provider.AuthUIStringProvider +import com.firebase.ui.auth.compose.configuration.string_provider.DefaultAuthUIStringProvider +import com.firebase.ui.auth.compose.data.CountryUtils +import com.firebase.ui.auth.compose.testutil.AUTH_STATE_WAIT_TIMEOUT_MS +import com.firebase.ui.auth.compose.testutil.EmulatorAuthApi +import com.firebase.ui.auth.compose.ui.screens.phone.EnterPhoneNumberUI +import com.firebase.ui.auth.compose.ui.screens.phone.EnterVerificationCodeUI +import com.firebase.ui.auth.compose.ui.screens.phone.PhoneAuthScreen +import com.firebase.ui.auth.compose.ui.screens.phone.PhoneAuthStep +import com.google.common.truth.Truth.assertThat +import com.google.firebase.FirebaseApp +import com.google.firebase.FirebaseOptions +import com.google.firebase.auth.AuthResult +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.MockitoAnnotations +import org.robolectric.RobolectricTestRunner +import org.robolectric.Shadows.shadowOf +import org.robolectric.annotation.Config + +@Config(sdk = [34]) +@RunWith(RobolectricTestRunner::class) +class PhoneAuthScreenTest { + @get:Rule + val composeTestRule = createComposeRule() + + private lateinit var applicationContext: Context + private lateinit var stringProvider: AuthUIStringProvider + private lateinit var authUI: FirebaseAuthUI + private lateinit var emulatorApi: EmulatorAuthApi + + @Before + fun setUp() { + MockitoAnnotations.openMocks(this) + + applicationContext = ApplicationProvider.getApplicationContext() + stringProvider = DefaultAuthUIStringProvider(applicationContext) + + // Clear any existing Firebase apps + FirebaseApp.getApps(applicationContext).forEach { app -> + app.delete() + } + + // Initialize default FirebaseApp + val firebaseApp = FirebaseApp.initializeApp( + applicationContext, + FirebaseOptions.Builder() + .setApiKey("fake-api-key") + .setApplicationId("fake-app-id") + .setProjectId("fake-project-id") + .build() + ) + + authUI = FirebaseAuthUI.getInstance() + authUI.auth.useEmulator("127.0.0.1", 9099) + + emulatorApi = EmulatorAuthApi( + projectId = firebaseApp.options.projectId + ?: throw IllegalStateException("Project ID is required for emulator interactions"), + emulatorHost = "127.0.0.1", + emulatorPort = 9099 + ) + + // Clear emulator data + emulatorApi.clearEmulatorData() + } + + @After + fun tearDown() { + // Clean up after each test to prevent test pollution + FirebaseAuthUI.clearInstanceCache() + + // Clear emulator data + emulatorApi.clearEmulatorData() + } + + @Test + fun `initial PhoneAuthStep is EnterPhoneNumber`() { + val configuration = authUIConfiguration { + context = applicationContext + providers { + provider( + AuthProvider.Phone( + defaultNumber = null, + defaultCountryCode = null, + allowedCountries = null, + ) + ) + } + } + + composeTestRule.setContent { + FirebaseAuthScreen(configuration = configuration) + } + + composeTestRule.onNodeWithText(stringProvider.enterPhoneNumberTitle) + .assertIsDisplayed() + } + + @Test + fun `sign-in and verify SMS emits Success auth state`() { + val country = CountryUtils.findByCountryCode("DE")!! + val phone = "15123456789" + + val configuration = authUIConfiguration { + context = applicationContext + providers { + provider( + AuthProvider.Phone( + defaultNumber = null, + defaultCountryCode = null, + allowedCountries = null, + ) + ) + } + } + + // Track auth state changes + var currentAuthState: AuthState = AuthState.Idle + + composeTestRule.setContent { + FirebaseAuthScreen(configuration = configuration) + val authState by authUI.authStateFlow().collectAsState(AuthState.Idle) + currentAuthState = authState + } + + // Show country selector modal + composeTestRule.onNodeWithText(stringProvider.signInWithPhone) + .assertIsDisplayed() + composeTestRule.onNodeWithContentDescription("Country selector") + .assertIsDisplayed() + .performClick() + composeTestRule.waitForIdle() + // Select country from list + composeTestRule.onNodeWithTag("CountrySelector LazyColumn") + .assertIsDisplayed() + .performScrollToNode(hasText(country.name)) + composeTestRule.onNodeWithText(country.name) + .assertIsDisplayed() + .performClick() + composeTestRule.waitForIdle() + composeTestRule.onNodeWithContentDescription("Country selector") + .assertTextContains(country.dialCode) + .assertIsDisplayed() + // Enter phone number + composeTestRule.onNodeWithText(stringProvider.phoneNumberHint) + .assertIsDisplayed() + .performTextInput(phone) + // Submit + composeTestRule.onNodeWithText(stringProvider.sendVerificationCode.uppercase()) + .assertIsDisplayed() + .assertIsEnabled() + .performClick() + composeTestRule.waitForIdle() + + val phoneCode = emulatorApi.fetchVerifyPhoneCode(phone) + + // Check current page is Verify Phone Number & Enter verification code + composeTestRule.onNodeWithText(stringProvider.verifyPhoneNumber) + val textFields = composeTestRule.onAllNodes(hasSetTextAction()) + // Enter each digit into its corresponding field + phoneCode.forEachIndexed { index, digit -> + composeTestRule.waitForIdle() + textFields[index].performTextInput(digit.toString()) + } + composeTestRule.waitForIdle() + // Submit verification code + composeTestRule.onNodeWithText(stringProvider.verifyPhoneNumber.uppercase()) + .performScrollTo() + .assertIsEnabled() + .performClick() + composeTestRule.waitForIdle() + + shadowOf(Looper.getMainLooper()).idle() + + // Wait for authentication to complete + println("TEST: Waiting for auth state change after verification...") + composeTestRule.waitUntil(timeoutMillis = AUTH_STATE_WAIT_TIMEOUT_MS) { + shadowOf(Looper.getMainLooper()).idle() + println("TEST: Auth state during verification: $currentAuthState") + currentAuthState is AuthState.Success + } + shadowOf(Looper.getMainLooper()).idle() + + // Verify authentication succeeded or failed appropriately + // Note: In emulator, this might fail with invalid code, which is expected + println("TEST: Final auth state: $currentAuthState") + assertThat(currentAuthState) + .isInstanceOf(AuthState.Success::class.java) + val user = (currentAuthState as AuthState.Success).user + println("TEST: User phone: ${user.phoneNumber}") + assertThat(authUI.auth.currentUser).isEqualTo(user) + assertThat(authUI.auth.currentUser!!.phoneNumber).isEqualTo( + CountryUtils.formatPhoneNumber( + country.dialCode, + phone + ) + ) + } + + @Test + fun `change phone number navigates back to EnterPhoneNumber step`() { + val defaultNumber = "+12025550123" + val country = CountryUtils.findByCountryCode("US")!! + val configuration = authUIConfiguration { + context = applicationContext + providers { + provider( + AuthProvider.Phone( + defaultNumber = defaultNumber, + defaultCountryCode = country.countryCode, + allowedCountries = null, + timeout = 60L, + ) + ) + } + } + + composeTestRule.setContent { + FirebaseAuthScreen(configuration = configuration) + } + + // Send verification code to get to verification screen + composeTestRule.onNodeWithText(stringProvider.sendVerificationCode.uppercase()) + .performScrollTo() + .performClick() + composeTestRule.waitForIdle() + // Click change phone number + composeTestRule.onNodeWithText(stringProvider.changePhoneNumber) + .performScrollTo() + .performClick() + composeTestRule.waitForIdle() + // Verify we are back to sign in with phone screen + composeTestRule.onNodeWithText(stringProvider.signInWithPhone) + .assertIsDisplayed() + } + + @Test + fun `default country code is applied when configured`() { + val country = CountryUtils.findByCountryCode("GB")!! + val configuration = authUIConfiguration { + context = applicationContext + providers { + provider( + AuthProvider.Phone( + defaultNumber = null, + defaultCountryCode = country.countryCode, + allowedCountries = null, + timeout = 60L, + isInstantVerificationEnabled = true + ) + ) + } + } + + composeTestRule.setContent { + FirebaseAuthScreen(configuration = configuration) + } + + // The country selector should show the default country's dial code (GB = +44) + composeTestRule.onNodeWithContentDescription("Country selector") + .assertTextContains(country.dialCode, substring = true) + .assertIsDisplayed() + } + + @Test + fun `resend code timer starts at configured timeout`() { + val phone = "+12025550123" + val timeout = 120L + val configuration = authUIConfiguration { + context = applicationContext + providers { + provider( + AuthProvider.Phone( + defaultNumber = phone, + defaultCountryCode = "US", + allowedCountries = null, + timeout = timeout, + ) + ) + } + } + + composeTestRule.setContent { + FirebaseAuthScreen(configuration = configuration) + } + + // Send verification code + composeTestRule.onNodeWithText(stringProvider.sendVerificationCode.uppercase()) + .performScrollTo() + .performClick() + composeTestRule.waitForIdle() + + // Check that timer text is displayed (should show 2:00 for 120 seconds) + val expectedTimerText = stringProvider.resendCodeTimer("2:00") + composeTestRule.onNodeWithText(expectedTimerText, substring = true) + .assertIsDisplayed() + } + + @Test + fun `default phone number is pre-filled when configured`() { + val defaultNumber = "+12025550123" + val country = CountryUtils.findByCountryCode("US")!! + val configuration = authUIConfiguration { + context = applicationContext + providers { + provider( + AuthProvider.Phone( + defaultNumber = defaultNumber, + defaultCountryCode = country.countryCode, + allowedCountries = null, + timeout = 60L, + isInstantVerificationEnabled = true + ) + ) + } + } + + composeTestRule.setContent { + FirebaseAuthScreen(configuration = configuration) + } + + // The send verification code button should be enabled since phone number is pre-filled + composeTestRule.onNodeWithText(stringProvider.sendVerificationCode.uppercase()) + .performScrollTo() + .assertIsEnabled() + } + + @Composable + private fun FirebaseAuthScreen( + configuration: AuthUIConfiguration, + onSuccess: ((AuthResult) -> Unit) = {}, + onError: ((AuthException) -> Unit) = {}, + onCancel: (() -> Unit) = {}, + onStepChange: ((PhoneAuthStep) -> Unit) = {}, + ) { + PhoneAuthScreen( + context = applicationContext, + configuration = configuration, + authUI = authUI, + onSuccess = onSuccess, + onError = onError, + onCancel = onCancel, + ) { state -> + onStepChange(state.step) + + when (state.step) { + PhoneAuthStep.EnterPhoneNumber -> { + EnterPhoneNumberUI( + configuration = configuration, + isLoading = state.isLoading, + phoneNumber = state.phoneNumber, + selectedCountry = state.selectedCountry, + onPhoneNumberChange = state.onPhoneNumberChange, + onCountrySelected = state.onCountrySelected, + onSendCodeClick = state.onSendCodeClick, + ) + } + + PhoneAuthStep.EnterVerificationCode -> { + EnterVerificationCodeUI( + configuration = configuration, + isLoading = state.isLoading, + verificationCode = state.verificationCode, + fullPhoneNumber = state.fullPhoneNumber, + resendTimer = state.resendTimer, + onVerificationCodeChange = state.onVerificationCodeChange, + onVerifyCodeClick = state.onVerifyCodeClick, + onResendCodeClick = state.onResendCodeClick, + onChangeNumberClick = state.onChangeNumberClick, + ) + } + } + } + } +} From 1d3e8084e1c721c2c86a0520918ddfa8d9213ff4 Mon Sep 17 00:00:00 2001 From: Ademola Fadumo Date: Thu, 16 Oct 2025 03:15:32 +0100 Subject: [PATCH 10/12] chore: localize strings --- auth/src/main/res/values-ar/strings.xml | 2 ++ auth/src/main/res/values-b+es+419/strings.xml | 2 ++ auth/src/main/res/values-bg/strings.xml | 2 ++ auth/src/main/res/values-bn/strings.xml | 2 ++ auth/src/main/res/values-ca/strings.xml | 2 ++ auth/src/main/res/values-cs/strings.xml | 2 ++ auth/src/main/res/values-da/strings.xml | 2 ++ auth/src/main/res/values-de-rAT/strings.xml | 2 ++ auth/src/main/res/values-de-rCH/strings.xml | 2 ++ auth/src/main/res/values-de/strings.xml | 2 ++ auth/src/main/res/values-el/strings.xml | 2 ++ auth/src/main/res/values-en-rAU/strings.xml | 2 ++ auth/src/main/res/values-en-rCA/strings.xml | 2 ++ auth/src/main/res/values-en-rGB/strings.xml | 2 ++ auth/src/main/res/values-en-rIE/strings.xml | 2 ++ auth/src/main/res/values-en-rIN/strings.xml | 2 ++ auth/src/main/res/values-en-rSG/strings.xml | 2 ++ auth/src/main/res/values-en-rZA/strings.xml | 2 ++ auth/src/main/res/values-es-rAR/strings.xml | 2 ++ auth/src/main/res/values-es-rBO/strings.xml | 2 ++ auth/src/main/res/values-es-rCL/strings.xml | 2 ++ auth/src/main/res/values-es-rCO/strings.xml | 2 ++ auth/src/main/res/values-es-rCR/strings.xml | 2 ++ auth/src/main/res/values-es-rDO/strings.xml | 2 ++ auth/src/main/res/values-es-rEC/strings.xml | 2 ++ auth/src/main/res/values-es-rGT/strings.xml | 2 ++ auth/src/main/res/values-es-rHN/strings.xml | 2 ++ auth/src/main/res/values-es-rMX/strings.xml | 2 ++ auth/src/main/res/values-es-rNI/strings.xml | 2 ++ auth/src/main/res/values-es-rPA/strings.xml | 2 ++ auth/src/main/res/values-es-rPE/strings.xml | 2 ++ auth/src/main/res/values-es-rPR/strings.xml | 2 ++ auth/src/main/res/values-es-rPY/strings.xml | 2 ++ auth/src/main/res/values-es-rSV/strings.xml | 2 ++ auth/src/main/res/values-es-rUS/strings.xml | 2 ++ auth/src/main/res/values-es-rUY/strings.xml | 2 ++ auth/src/main/res/values-es-rVE/strings.xml | 2 ++ auth/src/main/res/values-es/strings.xml | 2 ++ auth/src/main/res/values-fa/strings.xml | 2 ++ auth/src/main/res/values-fi/strings.xml | 2 ++ auth/src/main/res/values-fil/strings.xml | 2 ++ auth/src/main/res/values-fr-rCH/strings.xml | 2 ++ auth/src/main/res/values-fr/strings.xml | 2 ++ auth/src/main/res/values-gsw/strings.xml | 2 ++ auth/src/main/res/values-gu/strings.xml | 2 ++ auth/src/main/res/values-hi/strings.xml | 2 ++ auth/src/main/res/values-hr/strings.xml | 2 ++ auth/src/main/res/values-hu/strings.xml | 2 ++ auth/src/main/res/values-in/strings.xml | 2 ++ auth/src/main/res/values-it/strings.xml | 2 ++ auth/src/main/res/values-iw/strings.xml | 2 ++ auth/src/main/res/values-ja/strings.xml | 2 ++ auth/src/main/res/values-kn/strings.xml | 2 ++ auth/src/main/res/values-ko/strings.xml | 2 ++ auth/src/main/res/values-ln/strings.xml | 2 ++ auth/src/main/res/values-lt/strings.xml | 2 ++ auth/src/main/res/values-lv/strings.xml | 2 ++ auth/src/main/res/values-mo/strings.xml | 2 ++ auth/src/main/res/values-mr/strings.xml | 2 ++ auth/src/main/res/values-ms/strings.xml | 2 ++ auth/src/main/res/values-nb/strings.xml | 2 ++ auth/src/main/res/values-nl/strings.xml | 2 ++ auth/src/main/res/values-no/strings.xml | 2 ++ auth/src/main/res/values-pl/strings.xml | 2 ++ auth/src/main/res/values-pt-rBR/strings.xml | 2 ++ auth/src/main/res/values-pt-rPT/strings.xml | 2 ++ auth/src/main/res/values-pt/strings.xml | 2 ++ auth/src/main/res/values-ro/strings.xml | 2 ++ auth/src/main/res/values-ru/strings.xml | 2 ++ auth/src/main/res/values-sk/strings.xml | 2 ++ auth/src/main/res/values-sl/strings.xml | 2 ++ auth/src/main/res/values-sr/strings.xml | 2 ++ auth/src/main/res/values-sv/strings.xml | 2 ++ auth/src/main/res/values-ta/strings.xml | 2 ++ auth/src/main/res/values-th/strings.xml | 2 ++ auth/src/main/res/values-tl/strings.xml | 2 ++ auth/src/main/res/values-tr/strings.xml | 2 ++ auth/src/main/res/values-uk/strings.xml | 2 ++ auth/src/main/res/values-ur/strings.xml | 2 ++ auth/src/main/res/values-vi/strings.xml | 2 ++ auth/src/main/res/values-zh-rCN/strings.xml | 2 ++ auth/src/main/res/values-zh-rHK/strings.xml | 2 ++ auth/src/main/res/values-zh-rTW/strings.xml | 2 ++ auth/src/main/res/values-zh/strings.xml | 2 ++ 84 files changed, 168 insertions(+) diff --git a/auth/src/main/res/values-ar/strings.xml b/auth/src/main/res/values-ar/strings.xml index a40372feb..a4a45814b 100755 --- a/auth/src/main/res/values-ar/strings.xml +++ b/auth/src/main/res/values-ar/strings.xml @@ -25,6 +25,8 @@ البريد الإلكتروني رقم الهاتف البلد + اختيار بلد + البحث عن بلد مثلاً +1 أو "US" كلمة المرور كلمة المرور الجديدة يجب ملء هذا الحقل. diff --git a/auth/src/main/res/values-b+es+419/strings.xml b/auth/src/main/res/values-b+es+419/strings.xml index 3edd82e8a..a54686189 100755 --- a/auth/src/main/res/values-b+es+419/strings.xml +++ b/auth/src/main/res/values-b+es+419/strings.xml @@ -25,6 +25,8 @@ Correo electrónico Número de teléfono País + Selecciona un país + Buscar país por ej. +1, "US" Contraseña Contraseña nueva Este campo no puede estar en blanco. diff --git a/auth/src/main/res/values-bg/strings.xml b/auth/src/main/res/values-bg/strings.xml index 8b0588a5d..afd9dff14 100755 --- a/auth/src/main/res/values-bg/strings.xml +++ b/auth/src/main/res/values-bg/strings.xml @@ -25,6 +25,8 @@ Имейл Телефонен номер Държава + Изберете държава + Търсене на държава напр. +1, "US" Парола Нова парола Трябва да се попълни. diff --git a/auth/src/main/res/values-bn/strings.xml b/auth/src/main/res/values-bn/strings.xml index 0cd3dc173..8c8309b18 100755 --- a/auth/src/main/res/values-bn/strings.xml +++ b/auth/src/main/res/values-bn/strings.xml @@ -25,6 +25,8 @@ ইমেল ফোন নম্বর দেশ + একটি দেশ বেছে নিন + দেশ খুঁজুন যেমন +1, "US" পাসওয়ার্ড নতুন পাসওয়ার্ড আপনি এটি খালি ছাড়তে পারেন না। diff --git a/auth/src/main/res/values-ca/strings.xml b/auth/src/main/res/values-ca/strings.xml index 504bbbd32..587a85f98 100755 --- a/auth/src/main/res/values-ca/strings.xml +++ b/auth/src/main/res/values-ca/strings.xml @@ -25,6 +25,8 @@ Adreça electrònica Número de telèfon País + Selecciona un país + Cerca país p. ex. +1, "US" Contrasenya Contrasenya nova Aquest camp no es pot deixar en blanc. diff --git a/auth/src/main/res/values-cs/strings.xml b/auth/src/main/res/values-cs/strings.xml index 491bf8024..bbb83bf4d 100755 --- a/auth/src/main/res/values-cs/strings.xml +++ b/auth/src/main/res/values-cs/strings.xml @@ -25,6 +25,8 @@ E-mail Telefonní číslo Země + Vyberte zemi + Hledat zemi např. +1, "US" Heslo Nové heslo Toto pole nesmí zůstat prázdné. diff --git a/auth/src/main/res/values-da/strings.xml b/auth/src/main/res/values-da/strings.xml index 849ce6796..a0e8ea23a 100755 --- a/auth/src/main/res/values-da/strings.xml +++ b/auth/src/main/res/values-da/strings.xml @@ -25,6 +25,8 @@ Mail Telefonnummer Land + Vælg et land + Søg efter land f.eks. +1, "US" Adgangskode Ny adgangskode Dette felt skal udfyldes. diff --git a/auth/src/main/res/values-de-rAT/strings.xml b/auth/src/main/res/values-de-rAT/strings.xml index 62b6e1c22..967785909 100755 --- a/auth/src/main/res/values-de-rAT/strings.xml +++ b/auth/src/main/res/values-de-rAT/strings.xml @@ -25,6 +25,8 @@ E-Mail-Adresse Telefonnummer Land + Land auswählen + Land suchen z. B. +1, "US" Passwort Neues Passwort Pflichtfeld. diff --git a/auth/src/main/res/values-de-rCH/strings.xml b/auth/src/main/res/values-de-rCH/strings.xml index 9d8bcf3fa..2274dae5d 100755 --- a/auth/src/main/res/values-de-rCH/strings.xml +++ b/auth/src/main/res/values-de-rCH/strings.xml @@ -25,6 +25,8 @@ E-Mail-Adresse Telefonnummer Land + Land auswählen + Land suchen z. B. +1, "US" Passwort Neues Passwort Pflichtfeld. diff --git a/auth/src/main/res/values-de/strings.xml b/auth/src/main/res/values-de/strings.xml index 9b00b4994..1fad657aa 100755 --- a/auth/src/main/res/values-de/strings.xml +++ b/auth/src/main/res/values-de/strings.xml @@ -25,6 +25,8 @@ E-Mail-Adresse Telefonnummer Land + Land auswählen + Land suchen z. B. +1, "US" Passwort Neues Passwort Pflichtfeld. diff --git a/auth/src/main/res/values-el/strings.xml b/auth/src/main/res/values-el/strings.xml index 2cff189ed..724c6aae6 100755 --- a/auth/src/main/res/values-el/strings.xml +++ b/auth/src/main/res/values-el/strings.xml @@ -25,6 +25,8 @@ Ηλεκτρονικό ταχυδρομείο Αριθμός τηλεφώνου Χώρα + Επιλογή χώρας + Αναζήτηση χώρας π.χ. +1, "US" Κωδικός πρόσβασης Νέος κωδικός πρόσβασης Αυτό το πεδίο είναι υποχρεωτικό. diff --git a/auth/src/main/res/values-en-rAU/strings.xml b/auth/src/main/res/values-en-rAU/strings.xml index 999dae092..635f9271b 100755 --- a/auth/src/main/res/values-en-rAU/strings.xml +++ b/auth/src/main/res/values-en-rAU/strings.xml @@ -25,6 +25,8 @@ Email Phone number Country + Select a country + Select for country e.g. +1, "US" Password New password You can\'t leave this empty. diff --git a/auth/src/main/res/values-en-rCA/strings.xml b/auth/src/main/res/values-en-rCA/strings.xml index 82a7adb95..672733c8f 100755 --- a/auth/src/main/res/values-en-rCA/strings.xml +++ b/auth/src/main/res/values-en-rCA/strings.xml @@ -25,6 +25,8 @@ Email Phone number Country + Select a country + Select for country e.g. +1, "US" Password New password You can\'t leave this empty. diff --git a/auth/src/main/res/values-en-rGB/strings.xml b/auth/src/main/res/values-en-rGB/strings.xml index 82a7adb95..672733c8f 100755 --- a/auth/src/main/res/values-en-rGB/strings.xml +++ b/auth/src/main/res/values-en-rGB/strings.xml @@ -25,6 +25,8 @@ Email Phone number Country + Select a country + Select for country e.g. +1, "US" Password New password You can\'t leave this empty. diff --git a/auth/src/main/res/values-en-rIE/strings.xml b/auth/src/main/res/values-en-rIE/strings.xml index 7d28f96c1..5323807c8 100755 --- a/auth/src/main/res/values-en-rIE/strings.xml +++ b/auth/src/main/res/values-en-rIE/strings.xml @@ -25,6 +25,8 @@ Email Phone number Country + Select a country + Select for country e.g. +1, "US" Password New password You can\'t leave this empty. diff --git a/auth/src/main/res/values-en-rIN/strings.xml b/auth/src/main/res/values-en-rIN/strings.xml index 7d28f96c1..5323807c8 100755 --- a/auth/src/main/res/values-en-rIN/strings.xml +++ b/auth/src/main/res/values-en-rIN/strings.xml @@ -25,6 +25,8 @@ Email Phone number Country + Select a country + Select for country e.g. +1, "US" Password New password You can\'t leave this empty. diff --git a/auth/src/main/res/values-en-rSG/strings.xml b/auth/src/main/res/values-en-rSG/strings.xml index 7d28f96c1..5323807c8 100755 --- a/auth/src/main/res/values-en-rSG/strings.xml +++ b/auth/src/main/res/values-en-rSG/strings.xml @@ -25,6 +25,8 @@ Email Phone number Country + Select a country + Select for country e.g. +1, "US" Password New password You can\'t leave this empty. diff --git a/auth/src/main/res/values-en-rZA/strings.xml b/auth/src/main/res/values-en-rZA/strings.xml index 7d28f96c1..5323807c8 100755 --- a/auth/src/main/res/values-en-rZA/strings.xml +++ b/auth/src/main/res/values-en-rZA/strings.xml @@ -25,6 +25,8 @@ Email Phone number Country + Select a country + Select for country e.g. +1, "US" Password New password You can\'t leave this empty. diff --git a/auth/src/main/res/values-es-rAR/strings.xml b/auth/src/main/res/values-es-rAR/strings.xml index 4846839cb..abe63a0e8 100755 --- a/auth/src/main/res/values-es-rAR/strings.xml +++ b/auth/src/main/res/values-es-rAR/strings.xml @@ -25,6 +25,8 @@ Correo electrónico Número de teléfono País + Selecciona un país + Buscar país por ej. +1, "US" Contraseña Contraseña nueva Este campo no puede estar en blanco. diff --git a/auth/src/main/res/values-es-rBO/strings.xml b/auth/src/main/res/values-es-rBO/strings.xml index 4846839cb..abe63a0e8 100755 --- a/auth/src/main/res/values-es-rBO/strings.xml +++ b/auth/src/main/res/values-es-rBO/strings.xml @@ -25,6 +25,8 @@ Correo electrónico Número de teléfono País + Selecciona un país + Buscar país por ej. +1, "US" Contraseña Contraseña nueva Este campo no puede estar en blanco. diff --git a/auth/src/main/res/values-es-rCL/strings.xml b/auth/src/main/res/values-es-rCL/strings.xml index 4846839cb..abe63a0e8 100755 --- a/auth/src/main/res/values-es-rCL/strings.xml +++ b/auth/src/main/res/values-es-rCL/strings.xml @@ -25,6 +25,8 @@ Correo electrónico Número de teléfono País + Selecciona un país + Buscar país por ej. +1, "US" Contraseña Contraseña nueva Este campo no puede estar en blanco. diff --git a/auth/src/main/res/values-es-rCO/strings.xml b/auth/src/main/res/values-es-rCO/strings.xml index 4846839cb..abe63a0e8 100755 --- a/auth/src/main/res/values-es-rCO/strings.xml +++ b/auth/src/main/res/values-es-rCO/strings.xml @@ -25,6 +25,8 @@ Correo electrónico Número de teléfono País + Selecciona un país + Buscar país por ej. +1, "US" Contraseña Contraseña nueva Este campo no puede estar en blanco. diff --git a/auth/src/main/res/values-es-rCR/strings.xml b/auth/src/main/res/values-es-rCR/strings.xml index 4846839cb..abe63a0e8 100755 --- a/auth/src/main/res/values-es-rCR/strings.xml +++ b/auth/src/main/res/values-es-rCR/strings.xml @@ -25,6 +25,8 @@ Correo electrónico Número de teléfono País + Selecciona un país + Buscar país por ej. +1, "US" Contraseña Contraseña nueva Este campo no puede estar en blanco. diff --git a/auth/src/main/res/values-es-rDO/strings.xml b/auth/src/main/res/values-es-rDO/strings.xml index 4846839cb..abe63a0e8 100755 --- a/auth/src/main/res/values-es-rDO/strings.xml +++ b/auth/src/main/res/values-es-rDO/strings.xml @@ -25,6 +25,8 @@ Correo electrónico Número de teléfono País + Selecciona un país + Buscar país por ej. +1, "US" Contraseña Contraseña nueva Este campo no puede estar en blanco. diff --git a/auth/src/main/res/values-es-rEC/strings.xml b/auth/src/main/res/values-es-rEC/strings.xml index 4846839cb..abe63a0e8 100755 --- a/auth/src/main/res/values-es-rEC/strings.xml +++ b/auth/src/main/res/values-es-rEC/strings.xml @@ -25,6 +25,8 @@ Correo electrónico Número de teléfono País + Selecciona un país + Buscar país por ej. +1, "US" Contraseña Contraseña nueva Este campo no puede estar en blanco. diff --git a/auth/src/main/res/values-es-rGT/strings.xml b/auth/src/main/res/values-es-rGT/strings.xml index 4846839cb..abe63a0e8 100755 --- a/auth/src/main/res/values-es-rGT/strings.xml +++ b/auth/src/main/res/values-es-rGT/strings.xml @@ -25,6 +25,8 @@ Correo electrónico Número de teléfono País + Selecciona un país + Buscar país por ej. +1, "US" Contraseña Contraseña nueva Este campo no puede estar en blanco. diff --git a/auth/src/main/res/values-es-rHN/strings.xml b/auth/src/main/res/values-es-rHN/strings.xml index 4846839cb..abe63a0e8 100755 --- a/auth/src/main/res/values-es-rHN/strings.xml +++ b/auth/src/main/res/values-es-rHN/strings.xml @@ -25,6 +25,8 @@ Correo electrónico Número de teléfono País + Selecciona un país + Buscar país por ej. +1, "US" Contraseña Contraseña nueva Este campo no puede estar en blanco. diff --git a/auth/src/main/res/values-es-rMX/strings.xml b/auth/src/main/res/values-es-rMX/strings.xml index 4846839cb..abe63a0e8 100755 --- a/auth/src/main/res/values-es-rMX/strings.xml +++ b/auth/src/main/res/values-es-rMX/strings.xml @@ -25,6 +25,8 @@ Correo electrónico Número de teléfono País + Selecciona un país + Buscar país por ej. +1, "US" Contraseña Contraseña nueva Este campo no puede estar en blanco. diff --git a/auth/src/main/res/values-es-rNI/strings.xml b/auth/src/main/res/values-es-rNI/strings.xml index 4846839cb..abe63a0e8 100755 --- a/auth/src/main/res/values-es-rNI/strings.xml +++ b/auth/src/main/res/values-es-rNI/strings.xml @@ -25,6 +25,8 @@ Correo electrónico Número de teléfono País + Selecciona un país + Buscar país por ej. +1, "US" Contraseña Contraseña nueva Este campo no puede estar en blanco. diff --git a/auth/src/main/res/values-es-rPA/strings.xml b/auth/src/main/res/values-es-rPA/strings.xml index 4846839cb..abe63a0e8 100755 --- a/auth/src/main/res/values-es-rPA/strings.xml +++ b/auth/src/main/res/values-es-rPA/strings.xml @@ -25,6 +25,8 @@ Correo electrónico Número de teléfono País + Selecciona un país + Buscar país por ej. +1, "US" Contraseña Contraseña nueva Este campo no puede estar en blanco. diff --git a/auth/src/main/res/values-es-rPE/strings.xml b/auth/src/main/res/values-es-rPE/strings.xml index 4846839cb..abe63a0e8 100755 --- a/auth/src/main/res/values-es-rPE/strings.xml +++ b/auth/src/main/res/values-es-rPE/strings.xml @@ -25,6 +25,8 @@ Correo electrónico Número de teléfono País + Selecciona un país + Buscar país por ej. +1, "US" Contraseña Contraseña nueva Este campo no puede estar en blanco. diff --git a/auth/src/main/res/values-es-rPR/strings.xml b/auth/src/main/res/values-es-rPR/strings.xml index 4846839cb..abe63a0e8 100755 --- a/auth/src/main/res/values-es-rPR/strings.xml +++ b/auth/src/main/res/values-es-rPR/strings.xml @@ -25,6 +25,8 @@ Correo electrónico Número de teléfono País + Selecciona un país + Buscar país por ej. +1, "US" Contraseña Contraseña nueva Este campo no puede estar en blanco. diff --git a/auth/src/main/res/values-es-rPY/strings.xml b/auth/src/main/res/values-es-rPY/strings.xml index 4846839cb..abe63a0e8 100755 --- a/auth/src/main/res/values-es-rPY/strings.xml +++ b/auth/src/main/res/values-es-rPY/strings.xml @@ -25,6 +25,8 @@ Correo electrónico Número de teléfono País + Selecciona un país + Buscar país por ej. +1, "US" Contraseña Contraseña nueva Este campo no puede estar en blanco. diff --git a/auth/src/main/res/values-es-rSV/strings.xml b/auth/src/main/res/values-es-rSV/strings.xml index 4846839cb..abe63a0e8 100755 --- a/auth/src/main/res/values-es-rSV/strings.xml +++ b/auth/src/main/res/values-es-rSV/strings.xml @@ -25,6 +25,8 @@ Correo electrónico Número de teléfono País + Selecciona un país + Buscar país por ej. +1, "US" Contraseña Contraseña nueva Este campo no puede estar en blanco. diff --git a/auth/src/main/res/values-es-rUS/strings.xml b/auth/src/main/res/values-es-rUS/strings.xml index 4846839cb..abe63a0e8 100755 --- a/auth/src/main/res/values-es-rUS/strings.xml +++ b/auth/src/main/res/values-es-rUS/strings.xml @@ -25,6 +25,8 @@ Correo electrónico Número de teléfono País + Selecciona un país + Buscar país por ej. +1, "US" Contraseña Contraseña nueva Este campo no puede estar en blanco. diff --git a/auth/src/main/res/values-es-rUY/strings.xml b/auth/src/main/res/values-es-rUY/strings.xml index 4846839cb..abe63a0e8 100755 --- a/auth/src/main/res/values-es-rUY/strings.xml +++ b/auth/src/main/res/values-es-rUY/strings.xml @@ -25,6 +25,8 @@ Correo electrónico Número de teléfono País + Selecciona un país + Buscar país por ej. +1, "US" Contraseña Contraseña nueva Este campo no puede estar en blanco. diff --git a/auth/src/main/res/values-es-rVE/strings.xml b/auth/src/main/res/values-es-rVE/strings.xml index 4846839cb..abe63a0e8 100755 --- a/auth/src/main/res/values-es-rVE/strings.xml +++ b/auth/src/main/res/values-es-rVE/strings.xml @@ -25,6 +25,8 @@ Correo electrónico Número de teléfono País + Selecciona un país + Buscar país por ej. +1, "US" Contraseña Contraseña nueva Este campo no puede estar en blanco. diff --git a/auth/src/main/res/values-es/strings.xml b/auth/src/main/res/values-es/strings.xml index 1b70bffc5..516b1233d 100755 --- a/auth/src/main/res/values-es/strings.xml +++ b/auth/src/main/res/values-es/strings.xml @@ -25,6 +25,8 @@ Correo electrónico Número de teléfono País + Selecciona un país + Buscar país por ej. +1, "US" Contraseña Nueva contraseña No puedes dejar este campo en blanco. diff --git a/auth/src/main/res/values-fa/strings.xml b/auth/src/main/res/values-fa/strings.xml index b3a5536af..9d3f7d53e 100755 --- a/auth/src/main/res/values-fa/strings.xml +++ b/auth/src/main/res/values-fa/strings.xml @@ -25,6 +25,8 @@ ایمیل شماره تلفن کشور + انتخاب کشور + جستجوی کشور مثلاً +1، "US" گذرواژه گذرواژه جدید اینجا نباید خالی باشد. diff --git a/auth/src/main/res/values-fi/strings.xml b/auth/src/main/res/values-fi/strings.xml index d7002de49..32ef14a70 100755 --- a/auth/src/main/res/values-fi/strings.xml +++ b/auth/src/main/res/values-fi/strings.xml @@ -25,6 +25,8 @@ Sähköposti Puhelinnumero Maa + Valitse maa + Hae maata esim. +1, "US" Salasana Uusi salasana Tätä ei voi jättää tyhjäksi. diff --git a/auth/src/main/res/values-fil/strings.xml b/auth/src/main/res/values-fil/strings.xml index 565e2b8ec..17947fc00 100755 --- a/auth/src/main/res/values-fil/strings.xml +++ b/auth/src/main/res/values-fil/strings.xml @@ -25,6 +25,8 @@ Mag-email Numero ng Telepono Bansa + Pumili ng bansa + Maghanap ng bansa hal. +1, "US" Password Bagong password Hindi mo ito maaaring iwanan na walang laman. diff --git a/auth/src/main/res/values-fr-rCH/strings.xml b/auth/src/main/res/values-fr-rCH/strings.xml index 1b7f04540..e1c7a6c29 100755 --- a/auth/src/main/res/values-fr-rCH/strings.xml +++ b/auth/src/main/res/values-fr-rCH/strings.xml @@ -25,6 +25,8 @@ E-mail Numéro de téléphone Pays + Sélectionnez un pays + Rechercher un pays p. ex. +1, "US" Mot de passe Nouveau mot de passe Ce champ est obligatoire. diff --git a/auth/src/main/res/values-fr/strings.xml b/auth/src/main/res/values-fr/strings.xml index 8c4d981df..3b1397b40 100755 --- a/auth/src/main/res/values-fr/strings.xml +++ b/auth/src/main/res/values-fr/strings.xml @@ -25,6 +25,8 @@ E-mail Numéro de téléphone Pays + Sélectionnez un pays + Rechercher un pays p. ex. +1, "US" Mot de passe Nouveau mot de passe Ce champ est obligatoire. diff --git a/auth/src/main/res/values-gsw/strings.xml b/auth/src/main/res/values-gsw/strings.xml index 01402e26e..4b9290422 100755 --- a/auth/src/main/res/values-gsw/strings.xml +++ b/auth/src/main/res/values-gsw/strings.xml @@ -25,6 +25,8 @@ E-Mail-Adresse Telefonnummer Land + Land uswähle + Land sueche z. B. +1, "US" Passwort Neues Passwort Pflichtfeld. diff --git a/auth/src/main/res/values-gu/strings.xml b/auth/src/main/res/values-gu/strings.xml index 7530c4c01..f8bef2158 100755 --- a/auth/src/main/res/values-gu/strings.xml +++ b/auth/src/main/res/values-gu/strings.xml @@ -25,6 +25,8 @@ ઇમેઇલ ફોન નંબર દેશ + દેશ પસંદ કરો + દેશ શોધો ઉદા. +1, "US" પાસવર્ડ નવો પાસવર્ડ તમારે આ ફીલ્ડ ભરવું આવશ્યક છે. diff --git a/auth/src/main/res/values-hi/strings.xml b/auth/src/main/res/values-hi/strings.xml index e185c5e69..faf556212 100755 --- a/auth/src/main/res/values-hi/strings.xml +++ b/auth/src/main/res/values-hi/strings.xml @@ -25,6 +25,8 @@ ईमेल फ़ोन नंबर देश + देश चुनें + देश खोजें जैसे +1, "US" पासवर्ड नया पासवर्ड आप इसे खाली नहीं छोड़ सकते. diff --git a/auth/src/main/res/values-hr/strings.xml b/auth/src/main/res/values-hr/strings.xml index 34a0fb8c6..963bf1d4e 100755 --- a/auth/src/main/res/values-hr/strings.xml +++ b/auth/src/main/res/values-hr/strings.xml @@ -25,6 +25,8 @@ E-adresa Telefonski broj Zemlja + Odaberite zemlju + Pretraživanje zemlje npr. +1, "US" Zaporka Nova zaporka Ne možete to ostaviti prazno. diff --git a/auth/src/main/res/values-hu/strings.xml b/auth/src/main/res/values-hu/strings.xml index 120335ad6..f9e5cca93 100755 --- a/auth/src/main/res/values-hu/strings.xml +++ b/auth/src/main/res/values-hu/strings.xml @@ -25,6 +25,8 @@ E-mail Telefonszám Ország + Válasszon országot + Ország keresése pl. +1, "US" Jelszó Az új jelszó Ezt nem hagyhatja üresen. diff --git a/auth/src/main/res/values-in/strings.xml b/auth/src/main/res/values-in/strings.xml index f9544c542..1db30540a 100755 --- a/auth/src/main/res/values-in/strings.xml +++ b/auth/src/main/res/values-in/strings.xml @@ -25,6 +25,8 @@ Email Nomor Telepon Negara + Pilih negara + Telusuri negara, mis. +1, "US" Sandi Sandi baru Anda wajib mengisinya. diff --git a/auth/src/main/res/values-it/strings.xml b/auth/src/main/res/values-it/strings.xml index 3607513a6..d53baebe3 100755 --- a/auth/src/main/res/values-it/strings.xml +++ b/auth/src/main/res/values-it/strings.xml @@ -25,6 +25,8 @@ Indirizzo email Numero di telefono Paese + Seleziona un paese + Cerca paese ad es. +1, "US" Password Nuova password Questo campo non può restare vuoto. diff --git a/auth/src/main/res/values-iw/strings.xml b/auth/src/main/res/values-iw/strings.xml index 5af806ada..34ec20c99 100755 --- a/auth/src/main/res/values-iw/strings.xml +++ b/auth/src/main/res/values-iw/strings.xml @@ -25,6 +25,8 @@ אימייל מספר טלפון מדינה + בחירת מדינה + חיפוש מדינה למשל +1, "US" סיסמה סיסמה חדשה אי אפשר להשאיר את השדה הזה ריק. diff --git a/auth/src/main/res/values-ja/strings.xml b/auth/src/main/res/values-ja/strings.xml index 1c4cbcaa2..c3cf61755 100755 --- a/auth/src/main/res/values-ja/strings.xml +++ b/auth/src/main/res/values-ja/strings.xml @@ -25,6 +25,8 @@ メールアドレス 電話番号 + 国を選択 + 国を検索(例:+1、「US」) パスワード 新しいパスワード 入力必須項目です。 diff --git a/auth/src/main/res/values-kn/strings.xml b/auth/src/main/res/values-kn/strings.xml index 896112312..61db2f14b 100755 --- a/auth/src/main/res/values-kn/strings.xml +++ b/auth/src/main/res/values-kn/strings.xml @@ -25,6 +25,8 @@ ಇಮೇಲ್ ಫೋನ್ ಸಂಖ್ಯೆ ದೇಶ + ದೇಶವನ್ನು ಆಯ್ಕೆಮಾಡಿ + ದೇಶವನ್ನು ಹುಡುಕಿ ಉದಾ. +1, "US" ಪಾಸ್‌ವರ್ಡ್ ಹೊಸ ಪಾಸ್‌ವರ್ಡ್ ನೀವು ಇದನ್ನು ಖಾಲಿ ಬಿಡುವಂತಿಲ್ಲ. diff --git a/auth/src/main/res/values-ko/strings.xml b/auth/src/main/res/values-ko/strings.xml index a0e25b6e6..c239c3b44 100755 --- a/auth/src/main/res/values-ko/strings.xml +++ b/auth/src/main/res/values-ko/strings.xml @@ -25,6 +25,8 @@ 이메일 전화번호 국가 + 국가 선택 + 국가 검색(예: +1, "US") 비밀번호 새 비밀번호 이 입력란을 비워둘 수 없습니다. diff --git a/auth/src/main/res/values-ln/strings.xml b/auth/src/main/res/values-ln/strings.xml index 967967392..9c110a7d9 100755 --- a/auth/src/main/res/values-ln/strings.xml +++ b/auth/src/main/res/values-ln/strings.xml @@ -25,6 +25,8 @@ E-mail Numéro de téléphone Pays + Pona mboka + Luka mboka ndakisa +1, "US" Mot de passe Nouveau mot de passe Ce champ est obligatoire. diff --git a/auth/src/main/res/values-lt/strings.xml b/auth/src/main/res/values-lt/strings.xml index ba1ad0587..a4ef4278e 100755 --- a/auth/src/main/res/values-lt/strings.xml +++ b/auth/src/main/res/values-lt/strings.xml @@ -25,6 +25,8 @@ El. pašto adresas Telefono numeris Šalis + Pasirinkite šalį + Ieškoti šalies, pvz., +1, "US" Slaptažodis Naujas slaptažodis Šį lauką būtina užpildyti. diff --git a/auth/src/main/res/values-lv/strings.xml b/auth/src/main/res/values-lv/strings.xml index cc58f23e2..c3bd47ae6 100755 --- a/auth/src/main/res/values-lv/strings.xml +++ b/auth/src/main/res/values-lv/strings.xml @@ -25,6 +25,8 @@ E-pasts Tālruņa numurs Valsts + Atlasiet valsti + Meklēt valsti, piem., +1, "US" Parole Jauna parole Šo laukumu nedrīkst atstāt tukšu. diff --git a/auth/src/main/res/values-mo/strings.xml b/auth/src/main/res/values-mo/strings.xml index 59672d971..8ca92a1c2 100755 --- a/auth/src/main/res/values-mo/strings.xml +++ b/auth/src/main/res/values-mo/strings.xml @@ -25,6 +25,8 @@ Adresă de e-mail Număr de telefon Țară + Selectați o țară + Căutați țară de ex. +1, "US" Parolă Parolă nouă Câmpul este obligatoriu. diff --git a/auth/src/main/res/values-mr/strings.xml b/auth/src/main/res/values-mr/strings.xml index 05b1cf76f..6d6821196 100755 --- a/auth/src/main/res/values-mr/strings.xml +++ b/auth/src/main/res/values-mr/strings.xml @@ -25,6 +25,8 @@ ईमेल फोन नंबर देश + देश निवडा + देश शोधा उदा. +1, "US" पासवर्ड नवीन पासवर्ड तुम्ही हे रिक्त सोडू शकत नाही. diff --git a/auth/src/main/res/values-ms/strings.xml b/auth/src/main/res/values-ms/strings.xml index a2e5727a0..9edeae853 100755 --- a/auth/src/main/res/values-ms/strings.xml +++ b/auth/src/main/res/values-ms/strings.xml @@ -25,6 +25,8 @@ E-mel Nombor Telefon Negara + Pilih negara + Cari negara cth. +1, "US" Kata Laluan Kata laluan baharu Anda tidak boleh membiarkan ruang ini kosong. diff --git a/auth/src/main/res/values-nb/strings.xml b/auth/src/main/res/values-nb/strings.xml index cec4fa26f..13d7e7990 100755 --- a/auth/src/main/res/values-nb/strings.xml +++ b/auth/src/main/res/values-nb/strings.xml @@ -25,6 +25,8 @@ E-post Telefonnummer Land + Velg et land + Søk etter land, f.eks. +1, "US" Passord Nytt passord Du må fylle ut dette feltet. diff --git a/auth/src/main/res/values-nl/strings.xml b/auth/src/main/res/values-nl/strings.xml index df6e6e0b4..a5e9be4d1 100755 --- a/auth/src/main/res/values-nl/strings.xml +++ b/auth/src/main/res/values-nl/strings.xml @@ -25,6 +25,8 @@ E-mail Telefoonnummer Land + Selecteer een land + Land zoeken bijv. +1, "US" Wachtwoord Nieuw wachtwoord Dit veld mag niet leeg zijn. diff --git a/auth/src/main/res/values-no/strings.xml b/auth/src/main/res/values-no/strings.xml index 5a6ad671b..ad3a91c76 100755 --- a/auth/src/main/res/values-no/strings.xml +++ b/auth/src/main/res/values-no/strings.xml @@ -25,6 +25,8 @@ E-post Telefonnummer Land + Velg et land + Søk etter land, f.eks. +1, "US" Passord Nytt passord Du må fylle ut dette feltet. diff --git a/auth/src/main/res/values-pl/strings.xml b/auth/src/main/res/values-pl/strings.xml index c7b61a38c..eed13d63f 100755 --- a/auth/src/main/res/values-pl/strings.xml +++ b/auth/src/main/res/values-pl/strings.xml @@ -25,6 +25,8 @@ Adres e-mail Numer telefonu Kraj + Wybierz kraj + Szukaj kraju np. +1, "US" Hasło Nowe hasło Nie możesz zostawić tego pola pustego. diff --git a/auth/src/main/res/values-pt-rBR/strings.xml b/auth/src/main/res/values-pt-rBR/strings.xml index 8993fc5ff..5996720fe 100755 --- a/auth/src/main/res/values-pt-rBR/strings.xml +++ b/auth/src/main/res/values-pt-rBR/strings.xml @@ -25,6 +25,8 @@ E-mail Número de telefone País + Selecione um país + Pesquisar país, por ex., +1, "US" Senha Nova senha Esse campo não pode ficar em branco. diff --git a/auth/src/main/res/values-pt-rPT/strings.xml b/auth/src/main/res/values-pt-rPT/strings.xml index 2828db9ac..ed0ad3ffc 100755 --- a/auth/src/main/res/values-pt-rPT/strings.xml +++ b/auth/src/main/res/values-pt-rPT/strings.xml @@ -25,6 +25,8 @@ Email Número de telefone País + Selecione um país + Pesquisar país, por ex., +1, "US" Palavra-passe Nova palavra-passe Não pode deixar este campo em branco. diff --git a/auth/src/main/res/values-pt/strings.xml b/auth/src/main/res/values-pt/strings.xml index 535aa857d..f84a82b3a 100755 --- a/auth/src/main/res/values-pt/strings.xml +++ b/auth/src/main/res/values-pt/strings.xml @@ -25,6 +25,8 @@ E-mail Número de telefone País + Selecione um país + Pesquisar país, por ex., +1, "US" Senha Nova senha Esse campo não pode ficar em branco. diff --git a/auth/src/main/res/values-ro/strings.xml b/auth/src/main/res/values-ro/strings.xml index c879eaf43..96967a80a 100755 --- a/auth/src/main/res/values-ro/strings.xml +++ b/auth/src/main/res/values-ro/strings.xml @@ -25,6 +25,8 @@ Adresă de e-mail Număr de telefon Țară + Selectați o țară + Căutați țară de ex. +1, "US" Parolă Parolă nouă Câmpul este obligatoriu. diff --git a/auth/src/main/res/values-ru/strings.xml b/auth/src/main/res/values-ru/strings.xml index 850d7cdf7..7c0cbb98a 100755 --- a/auth/src/main/res/values-ru/strings.xml +++ b/auth/src/main/res/values-ru/strings.xml @@ -25,6 +25,8 @@ Адрес электронной почты Номер телефона Страна + Выберите страну + Поиск страны, напр. +1, "US" Пароль Новый пароль Это поле должно быть заполнено. diff --git a/auth/src/main/res/values-sk/strings.xml b/auth/src/main/res/values-sk/strings.xml index 46cc93460..6caff4502 100755 --- a/auth/src/main/res/values-sk/strings.xml +++ b/auth/src/main/res/values-sk/strings.xml @@ -25,6 +25,8 @@ E-mail Telefónne číslo Krajina + Vyberte krajinu + Vyhľadať krajinu napr. +1, "US" Heslo Nové heslo Toto pole nesmie byť prázdne. diff --git a/auth/src/main/res/values-sl/strings.xml b/auth/src/main/res/values-sl/strings.xml index 236704e03..3190eecfb 100755 --- a/auth/src/main/res/values-sl/strings.xml +++ b/auth/src/main/res/values-sl/strings.xml @@ -25,6 +25,8 @@ E-pošta Telefonska številka Država + Izberi državo + Iskanje države, npr. +1, "US" Geslo Novo geslo To polje ne sme biti prazno. diff --git a/auth/src/main/res/values-sr/strings.xml b/auth/src/main/res/values-sr/strings.xml index eceecae82..00b9f020e 100755 --- a/auth/src/main/res/values-sr/strings.xml +++ b/auth/src/main/res/values-sr/strings.xml @@ -25,6 +25,8 @@ Имејл Број телефона Земља + Изаберите земљу + Претражите земљу нпр. +1, "US" Лозинка Нова лозинка Ово поље не може да буде празно. diff --git a/auth/src/main/res/values-sv/strings.xml b/auth/src/main/res/values-sv/strings.xml index 6818bee0e..35aff8fa9 100755 --- a/auth/src/main/res/values-sv/strings.xml +++ b/auth/src/main/res/values-sv/strings.xml @@ -25,6 +25,8 @@ E-post Telefonnummer Land + Välj ett land + Sök land t.ex. +1, "US" Lösenord Nytt lösenord Du måste fylla i det här fältet. diff --git a/auth/src/main/res/values-ta/strings.xml b/auth/src/main/res/values-ta/strings.xml index 21ed2d82e..2ed52463b 100755 --- a/auth/src/main/res/values-ta/strings.xml +++ b/auth/src/main/res/values-ta/strings.xml @@ -25,6 +25,8 @@ மின்னஞ்சல் ஃபோன் எண் நாடு + நாட்டைத் தேர்வுசெய்க + நாட்டைத் தேடுக எ.கா. +1, "US" கடவுச்சொல் புதிய கடவுச்சொல் இதை காலியாக விடக்கூடாது. diff --git a/auth/src/main/res/values-th/strings.xml b/auth/src/main/res/values-th/strings.xml index e656fbdb2..9d24cba7f 100755 --- a/auth/src/main/res/values-th/strings.xml +++ b/auth/src/main/res/values-th/strings.xml @@ -25,6 +25,8 @@ อีเมล หมายเลขโทรศัพท์ ประเทศ + เลือกประเทศ + ค้นหาประเทศ เช่น +1 "US" รหัสผ่าน รหัสผ่านใหม่ ต้องป้อนข้อมูลในช่องนี้ diff --git a/auth/src/main/res/values-tl/strings.xml b/auth/src/main/res/values-tl/strings.xml index 61bd40ef2..19d2527a7 100755 --- a/auth/src/main/res/values-tl/strings.xml +++ b/auth/src/main/res/values-tl/strings.xml @@ -25,6 +25,8 @@ Mag-email Numero ng Telepono Bansa + Pumili ng bansa + Maghanap ng bansa hal. +1, "US" Password Bagong password Hindi mo ito maaaring iwanan na walang laman. diff --git a/auth/src/main/res/values-tr/strings.xml b/auth/src/main/res/values-tr/strings.xml index 15604a3a1..bc0e197f7 100755 --- a/auth/src/main/res/values-tr/strings.xml +++ b/auth/src/main/res/values-tr/strings.xml @@ -25,6 +25,8 @@ E-posta Telefon Numarası Ülke + Ülke seçin + Ülke arayın ör. +1, "US" Şifre Yeni şifre Bu alanı boş bırakamazsınız. diff --git a/auth/src/main/res/values-uk/strings.xml b/auth/src/main/res/values-uk/strings.xml index 148c8b2ad..e08e214df 100755 --- a/auth/src/main/res/values-uk/strings.xml +++ b/auth/src/main/res/values-uk/strings.xml @@ -25,6 +25,8 @@ Електронна адреса Номер телефону Країна + Виберіть країну + Шукати країну, наприклад +1, "US" Пароль Новий пароль Потрібно заповнити це поле. diff --git a/auth/src/main/res/values-ur/strings.xml b/auth/src/main/res/values-ur/strings.xml index 0dd801265..321f63854 100755 --- a/auth/src/main/res/values-ur/strings.xml +++ b/auth/src/main/res/values-ur/strings.xml @@ -25,6 +25,8 @@ ای میل فون نمبر ملک + ملک منتخب کریں + ملک تلاش کریں مثلاً +1، "US" پاس ورڈ نیا پاس ورڈ آپ اسے خالی نہیں چھوڑ سکتے۔ diff --git a/auth/src/main/res/values-vi/strings.xml b/auth/src/main/res/values-vi/strings.xml index 4c977fc36..98b73434f 100755 --- a/auth/src/main/res/values-vi/strings.xml +++ b/auth/src/main/res/values-vi/strings.xml @@ -25,6 +25,8 @@ Email Số điện thoại Quốc gia + Chọn quốc gia + Tìm kiếm quốc gia, ví dụ: +1, "US" Mật khẩu Mật khẩu mới Bạn không được để trống trường này. diff --git a/auth/src/main/res/values-zh-rCN/strings.xml b/auth/src/main/res/values-zh-rCN/strings.xml index 354346b11..b8220c70c 100755 --- a/auth/src/main/res/values-zh-rCN/strings.xml +++ b/auth/src/main/res/values-zh-rCN/strings.xml @@ -25,6 +25,8 @@ 电子邮件地址 电话号码 国家/地区 + 选择国家/地区 + 搜索国家/地区,例如 +1、"US" 密码 新密码 此处不能留空。 diff --git a/auth/src/main/res/values-zh-rHK/strings.xml b/auth/src/main/res/values-zh-rHK/strings.xml index 71f9afa4c..dd047e28b 100755 --- a/auth/src/main/res/values-zh-rHK/strings.xml +++ b/auth/src/main/res/values-zh-rHK/strings.xml @@ -25,6 +25,8 @@ 電子郵件 電話號碼 國家/地區 + 選取國家/地區 + 搜尋國家/地區,例如 +1、"US" 密碼 新密碼 此欄位不可留空。 diff --git a/auth/src/main/res/values-zh-rTW/strings.xml b/auth/src/main/res/values-zh-rTW/strings.xml index 1046cb147..3a00351a2 100755 --- a/auth/src/main/res/values-zh-rTW/strings.xml +++ b/auth/src/main/res/values-zh-rTW/strings.xml @@ -25,6 +25,8 @@ 電子郵件 電話號碼 國家/地區 + 選取國家/地區 + 搜尋國家/地區,例如 +1、「US」 密碼 新密碼 此欄位不可留空。 diff --git a/auth/src/main/res/values-zh/strings.xml b/auth/src/main/res/values-zh/strings.xml index 521a658be..c9fc58194 100755 --- a/auth/src/main/res/values-zh/strings.xml +++ b/auth/src/main/res/values-zh/strings.xml @@ -25,6 +25,8 @@ 电子邮件地址 电话号码 国家/地区 + 选择国家/地区 + 搜索国家/地区,例如 +1、"US" 密码 新密码 此处不能留空。 From 9b377a3cf3410c36c8cc90a736ddecc899f19a7f Mon Sep 17 00:00:00 2001 From: Ademola Fadumo Date: Thu, 16 Oct 2025 03:44:36 +0100 Subject: [PATCH 11/12] fix: resend code timer timing issues --- .../ui/auth/compose/ui/screens/PhoneAuthScreenTest.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/e2eTest/src/test/java/com/firebase/ui/auth/compose/ui/screens/PhoneAuthScreenTest.kt b/e2eTest/src/test/java/com/firebase/ui/auth/compose/ui/screens/PhoneAuthScreenTest.kt index 9b861f589..6464f7ecb 100644 --- a/e2eTest/src/test/java/com/firebase/ui/auth/compose/ui/screens/PhoneAuthScreenTest.kt +++ b/e2eTest/src/test/java/com/firebase/ui/auth/compose/ui/screens/PhoneAuthScreenTest.kt @@ -47,6 +47,7 @@ import org.mockito.MockitoAnnotations import org.robolectric.RobolectricTestRunner import org.robolectric.Shadows.shadowOf import org.robolectric.annotation.Config +import org.robolectric.annotation.LooperMode @Config(sdk = [34]) @RunWith(RobolectricTestRunner::class) @@ -321,7 +322,11 @@ class PhoneAuthScreenTest { .performClick() composeTestRule.waitForIdle() - // Check that timer text is displayed (should show 2:00 for 120 seconds) + // Process pending tasks to render the verification screen + shadowOf(Looper.getMainLooper()).idle() + + // With LooperMode.PAUSED, time doesn't advance automatically, + // so the timer will stay frozen at "2:00" (the configured timeout) val expectedTimerText = stringProvider.resendCodeTimer("2:00") composeTestRule.onNodeWithText(expectedTimerText, substring = true) .assertIsDisplayed() From e36b1daf374d6de067fa53426095d586a82898a3 Mon Sep 17 00:00:00 2001 From: Ademola Fadumo Date: Thu, 16 Oct 2025 03:50:11 +0100 Subject: [PATCH 12/12] fix: resend code timer timing issues --- .../firebase/ui/auth/compose/ui/screens/PhoneAuthScreenTest.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/e2eTest/src/test/java/com/firebase/ui/auth/compose/ui/screens/PhoneAuthScreenTest.kt b/e2eTest/src/test/java/com/firebase/ui/auth/compose/ui/screens/PhoneAuthScreenTest.kt index 6464f7ecb..7e1da70b5 100644 --- a/e2eTest/src/test/java/com/firebase/ui/auth/compose/ui/screens/PhoneAuthScreenTest.kt +++ b/e2eTest/src/test/java/com/firebase/ui/auth/compose/ui/screens/PhoneAuthScreenTest.kt @@ -51,6 +51,7 @@ import org.robolectric.annotation.LooperMode @Config(sdk = [34]) @RunWith(RobolectricTestRunner::class) +@LooperMode(LooperMode.Mode.PAUSED) class PhoneAuthScreenTest { @get:Rule val composeTestRule = createComposeRule()