diff --git a/auth/build.gradle.kts b/auth/build.gradle.kts index 0fd30ade8..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 @@ -106,6 +104,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/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 30cc33257..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 @@ -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(timeFormatted: String): String + /** Verifying progress text */ val verifying: String @@ -180,6 +186,36 @@ 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 + + /** Change phone number link text */ + val changePhoneNumber: String + + /** Missing verification code error */ + val missingVerificationCode: String + + /** Invalid verification code error */ + val invalidVerificationCode: String + + /** Select country modal sheet title */ + val countrySelectorModalTitle: String + + /** Select country modal sheet input field hint */ + val searchCountriesHint: 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..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 @@ -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(timeFormatted: String): String = + localizedContext.getString(R.string.fui_resend_code_in, timeFormatted) + override val verifying: String get() = localizedContext.getString(R.string.fui_verifying) override val incorrectCodeDialogBody: String @@ -174,6 +180,36 @@ 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 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 countrySelectorModalTitle: String + get() = localizedContext.getString(R.string.fui_country_selector_title) + + 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/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/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/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/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..06a743fe2 --- /dev/null +++ b/auth/src/main/java/com/firebase/ui/auth/compose/ui/components/CountrySelector.kt @@ -0,0 +1,215 @@ +/* + * 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.Button +import androidx.compose.material3.ButtonDefaults +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.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 +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) + .semantics { + contentDescription = "Country selector" + }, + 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.countrySelectorModalTitle, + style = MaterialTheme.typography.headlineSmall, + modifier = Modifier.padding(bottom = 16.dp) + ) + + OutlinedTextField( + value = searchQuery, + onValueChange = { searchQuery = it }, + label = { Text(stringProvider.searchCountriesHint) }, + modifier = Modifier.fillMaxWidth() + ) + + Spacer(modifier = Modifier.height(16.dp)) + + LazyColumn( + modifier = Modifier + .fillMaxWidth() + .height(500.dp) + .testTag("CountrySelector LazyColumn") + ) { + items(filteredCountries) { country -> + Button( + onClick = { + onCountrySelected(country) + scope.launch { + sheetState.hide() + showBottomSheet = false + searchQuery = "" + } + }, + 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.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..54b65af8a --- /dev/null +++ b/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/phone/EnterPhoneNumberUI.kt @@ -0,0 +1,186 @@ +/* + * 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, + 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.signInWithPhone) + }, + 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)) + + 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 = "", + 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..00c64d614 --- /dev/null +++ b/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/phone/EnterVerificationCodeUI.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.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 +import java.util.Locale + +@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) { + val minutes = resendTimer / 60 + val seconds = resendTimer % 60 + val timeFormatted = + "$minutes:${String.format(Locale.ROOT, "%02d", seconds)}" + stringProvider.resendCodeTimer(timeFormatted) + } 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.verifyPhoneNumber.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..ebae031e5 --- /dev/null +++ b/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/phone/PhoneAuthScreen.kt @@ -0,0 +1,320 @@ +/* + * 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.activity.compose.LocalActivity +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 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 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 activity = LocalActivity.current + 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 verificationCodeValue = rememberSaveable { mutableStateOf("") } + val selectedCountry = remember { + mutableStateOf( + provider.defaultCountryCode?.let { code -> + CountryUtils.findByCountryCode(code) + } ?: CountryUtils.getDefaultCountry() + ) + } + val fullPhoneNumber = remember(selectedCountry.value, phoneNumberValue.value) { + 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 = provider.timeout.toInt() // Start 60-second countdown + } + + is AuthState.SMSAutoVerified -> { + // Auto-verification succeeded, sign in with the credential + 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, + onPhoneNumberChange = { number -> + phoneNumberValue.value = number + }, + selectedCountry = selectedCountry.value, + onCountrySelected = { country -> + selectedCountry.value = country + }, + onSendCodeClick = { + coroutineScope.launch { + try { + authUI.verifyPhoneNumber( + provider = provider, + activity = activity, + 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( + activity = activity, + provider = provider, + phoneNumber = fullPhoneNumber, + forceResendingToken = forceResendingToken.value, + ) + resendTimerSeconds.intValue = provider.timeout.toInt() // 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..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" كلمة المرور كلمة المرور الجديدة يجب ملء هذا الحقل. @@ -76,7 +78,7 @@ أدخل رقم هاتفك يُرجى إدخال رقم هاتف صالح أدخل الرمز المكوّن من 6 أرقام الذي أرسلناه إلى - إعادة إرسال الرمز بعد 0:%02d + إعادة إرسال الرمز بعد %1$s إثبات ملكية رقم هاتفك جارٍ التحقق… الرمز غير صحيح. يُرجى المحاولة مجددًا. @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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" Парола Нова парола Трябва да се попълни. @@ -76,7 +78,7 @@ Въвеждане на телефонния ви номер Въведете валиден телефонен номер Въведете 6-цифрения код, който изпратихме до - Повторно изпращане на кода след 0:%02d + Повторно изпращане на кода след %1$s Потвърждаване на телефонния ви номер Потвърждава се… Неправилен код. Опитайте отново. @@ -87,6 +89,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..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" পাসওয়ার্ড নতুন পাসওয়ার্ড আপনি এটি খালি ছাড়তে পারেন না। @@ -76,7 +78,7 @@ আপনার ফোন নম্বর লিখুন একটি সঠিক ফোন নম্বর লিখুন আমাদের পাঠানো ৬-সংখ্যার কোডটি লিখুন - 0:%02d এ কোডটি আবার পাঠান + %1$s এ কোডটি আবার পাঠান আপনার ফোন নম্বর যাচাই করুন যাচাই করা হচ্ছে… কোডটি ভুল। আবার চেষ্টা করুন। @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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é. @@ -76,7 +78,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. @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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" Κωδικός πρόσβασης Νέος κωδικός πρόσβασης Αυτό το πεδίο είναι υποχρεωτικό. @@ -76,7 +78,7 @@ Εισαγάγετε τον αριθμό τηλεφώνου σας Καταχωρίστε έναν έγκυρο αριθμό τηλεφώνου Εισαγάγετε τον 6ψήφιο κωδικό που σας στείλαμε στο - Επανάληψη αποστολής κωδικού σε 0:%02d + Επανάληψη αποστολής κωδικού σε %1$s Επαλήθευση του τηλεφώνου σας Επαλήθευση… Εσφαλμένος κωδικός. Δοκιμάστε ξανά. @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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" گذرواژه گذرواژه جدید اینجا نباید خالی باشد. @@ -76,7 +78,7 @@ شماره تلفن خود را وارد کنید شماره تلفن معتبری وارد کنید وارد کردن کد ۶ رقمی ارسال‌شده به - کد پس از %02d:0 مجدداً ارسال می‌شود + کد پس از %1$s:0 مجدداً ارسال می‌شود تأیید شماره تلفن درحال تأیید… کد اشتباه است. دوباره امتحان کنید. @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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" પાસવર્ડ નવો પાસવર્ડ તમારે આ ફીલ્ડ ભરવું આવશ્યક છે. @@ -76,7 +78,7 @@ તમારો ફોન નંબર દાખલ કરો એક માન્ય ફોન નંબર દાખલ કરો અમે આ ફોન નંબર પર મોકલેલ 6-અંકનો કોડ દાખલ કરો - 0 માં કોડ ફરીથી મોકલો:%02d + 0 માં કોડ ફરીથી મોકલો:%1$s તમારો ફોન નંબર ચકાસો ચકાસી રહ્યાં છીએ… કોડ ખોટો છે. ફરી પ્રયાસ કરો. @@ -87,6 +89,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..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" पासवर्ड नया पासवर्ड आप इसे खाली नहीं छोड़ सकते. @@ -76,7 +78,7 @@ अपना फ़ोन नंबर डालें कोई मान्य फ़ोन नंबर डालें हमारी ओर से भेजा गया 6-अंकों वाला कोड डालें - 0:%02d में कोड फिर से भेजें + %1$s में कोड फिर से भेजें अपने फ़ोन नंबर की पुष्टि करें पुष्टि की जा रही है… गलत कोड. फिर से कोशिश करें. @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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" סיסמה סיסמה חדשה אי אפשר להשאיר את השדה הזה ריק. @@ -76,7 +78,7 @@ הזן את מספר הטלפון שלך מספר הטלפון שהזנת לא תקין הזן את הקוד בן 6 הספרות ששלחנו אל - שולח קוד חדש בעוד %02d:0 + שולח קוד חדש בעוד %1$s:0 אמת את מספר הטלפון מאמת… הקוד שגוי. נסה שוב. @@ -87,6 +89,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..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」) パスワード 新しいパスワード 入力必須項目です。 @@ -76,7 +78,7 @@ 電話番号を入力してください 有効な電話番号を入力してください 送信された 6 桁のコードを入力してください - 0:%02d 秒後にコードを再送信します + %1$s 秒後にコードを再送信します 電話番号を確認 確認しています… コードが間違っています。もう一度お試しください。 @@ -87,6 +89,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..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" ಪಾಸ್‌ವರ್ಡ್ ಹೊಸ ಪಾಸ್‌ವರ್ಡ್ ನೀವು ಇದನ್ನು ಖಾಲಿ ಬಿಡುವಂತಿಲ್ಲ. @@ -76,7 +78,7 @@ ನಿಮ್ಮ ಫೋನ್‌ ಸಂಖ್ಯೆಯನ್ನು ನಮೂದಿಸಿ ಮಾನ್ಯವಾದ ಫೋನ್ ಸಂಖ್ಯೆಯನ್ನು ನಮೂದಿಸಿ ನಾವು ಕಳುಹಿಸಿರುವ 6 ಅಂಕಿಯ ಕೋಡ್‌ ನಮೂದಿಸಿ - ಇಷ್ಟರ ಒಳಗೆ ಕೋಡ್ ಮತ್ತೆ ಕಳುಹಿಸಿ 0:%02d + ಇಷ್ಟರ ಒಳಗೆ ಕೋಡ್ ಮತ್ತೆ ಕಳುಹಿಸಿ %1$s ನಿಮ್ಮ ಪೋನ್‌ ಸಂಖ್ಯೆಯನ್ನು ಪರಿಶೀಲಿಸಿ ಪರಿಶೀಲಿಸಲಾಗುತ್ತಿದೆ… ಕೋಡ್ ತಪ್ಪಾಗಿದೆ. ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ. @@ -87,6 +89,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..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") 비밀번호 새 비밀번호 이 입력란을 비워둘 수 없습니다. @@ -76,7 +78,7 @@ 전화번호 입력 올바른 전화번호를 입력하세요. 전송된 6자리 코드를 입력하세요. - 0:%02d 후에 코드 재전송 + %1$s 후에 코드 재전송 전화번호 인증 인증 중… 코드가 잘못되었습니다. 다시 시도하세요. @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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. @@ -76,7 +78,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ą. @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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" पासवर्ड नवीन पासवर्ड तुम्ही हे रिक्त सोडू शकत नाही. @@ -76,7 +78,7 @@ तुमचा फोन नंबर टाका कृपया वैध फोन नंबर टाका वर आम्ही पाठवलेला 6 अंकी कोड टाका - कोड 0:%02dमध्ये पुन्हा पाठवा + कोड %1$sमध्ये पुन्हा पाठवा तुमच्या फोन नंबरची पडताळणी करा पडताळणी करत आहे… कोड चुकीचा आहे. पुन्हा प्रयत्न करा. @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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" Пароль Новый пароль Это поле должно быть заполнено. @@ -76,7 +78,7 @@ Введите номер телефона Введите действительный номер телефона. Введите 6-значный код, отправленный на номер - Отправить код ещё раз можно будет через 0:%02d. + Отправить код ещё раз можно будет через %1$s. Подтвердить номер телефона Проверка… Неверный код. Повторите попытку. @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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" Лозинка Нова лозинка Ово поље не може да буде празно. @@ -76,7 +78,7 @@ Унесите број телефона Унесите важећи број телефона Унесите 6-цифрени кôд који смо послали на - Кôд ће бити поново послат за 0:%02d + Кôд ће бити поново послат за %1$s Верификујте број телефона Верификује се… Кôд није тачан. Пробајте поново. @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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" கடவுச்சொல் புதிய கடவுச்சொல் இதை காலியாக விடக்கூடாது. @@ -76,7 +78,7 @@ ஃபோன் எண்ணை உள்ளிடவும் சரியான ஃபோன் எண்ணை உள்ளிடவும் இந்த எண்ணுக்கு நாங்கள் அனுப்பிய 6 இலக்கக் குறியீட்டை உள்ளிடவும்: - குறியீட்டை 0:%02d நேரத்தில் மீண்டும் அனுப்பவும் + குறியீட்டை %1$s நேரத்தில் மீண்டும் அனுப்பவும் ஃபோன் எண்ணைச் சரிபார் சரிபார்க்கிறது… தவறான குறியீடு. மீண்டும் முயலவும். @@ -87,6 +89,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..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" รหัสผ่าน รหัสผ่านใหม่ ต้องป้อนข้อมูลในช่องนี้ @@ -76,7 +78,7 @@ ป้อนหมายเลขโทรศัพท์ของคุณ ป้อนหมายเลขโทรศัพท์ที่ถูกต้อง ป้อนรหัส 6 หลักที่เราส่งไปยัง - ส่งรหัสอีกครั้งใน 0:%02d + ส่งรหัสอีกครั้งใน %1$s ยืนยันหมายเลขโทรศัพท์ กำลังยืนยัน… รหัสไม่ถูกต้อง โปรดลองอีกครั้ง @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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" Пароль Новий пароль Потрібно заповнити це поле. @@ -76,7 +78,7 @@ Введіть свій номер телефону Введіть дійсний номер телефону Введіть 6-значний код, який ми надіслали на номер - Повторно надіслати код через 0:%02d + Повторно надіслати код через %1$s Підтвердити номер телефону Підтвердження… Неправильний код. Повторіть спробу. @@ -87,6 +89,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..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" پاس ورڈ نیا پاس ورڈ آپ اسے خالی نہیں چھوڑ سکتے۔ @@ -76,7 +78,7 @@ اپنا فون نمبر درج کریں براہ کرم ایک درست فون نمبر درج کریں ہماری جانب سے حسبِ ذیل کو بھیجا گیا 6 عدد کا کوڈ درج کریں - 0:%02d میں دوبارہ کوڈ بھیجیں + %1$s میں دوبارہ کوڈ بھیجیں اپنے فون نمبر کی توثیق کریں توثیق ہو رہی ہے… غلط کوڈ۔ دوبارہ کوشش کریں۔ @@ -87,6 +89,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..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. @@ -76,7 +78,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. @@ -87,6 +89,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..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" 密码 新密码 此处不能留空。 @@ -76,7 +78,7 @@ 输入您的电话号码 请输入有效的电话号码 输入我们发送至以下电话号码的 6 位数验证码 - %02d 后可重新发送验证码 + %1$s 后可重新发送验证码 验证您的电话号码 正在验证… 验证码有误,请重试。 @@ -87,6 +89,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..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" 密碼 新密碼 此欄位不可留空。 @@ -76,7 +78,7 @@ 請輸入您的電話號碼 請輸入有效的電話號碼 請輸入傳送至以下電話號碼的 6 位數驗證碼 - 0:%02d 秒後將重新發送驗證碼 + %1$s 秒後將重新發送驗證碼 驗證您的電話號碼 驗證中… 驗證碼錯誤,請再試一次。 @@ -87,6 +89,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..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」 密碼 新密碼 此欄位不可留空。 @@ -76,7 +78,7 @@ 請輸入您的電話號碼 請輸入有效的電話號碼 請輸入傳送至以下電話號碼的 6 位數驗證碼 - 0:%02d 秒後將重新發送驗證碼 + %1$s 秒後將重新發送驗證碼 驗證您的電話號碼 驗證中… 驗證碼錯誤,請再試一次。 @@ -87,6 +89,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..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" 密码 新密码 此处不能留空。 @@ -76,7 +78,7 @@ 输入您的电话号码 请输入有效的电话号码 输入我们发送至以下电话号码的 6 位数验证码 - %02d 后可重新发送验证码 + %1$s 后可重新发送验证码 验证您的电话号码 正在验证… 验证码有误,请重试。 @@ -87,6 +89,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..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 @@ -129,7 +131,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 %1$s Verify your phone number Verifying… Wrong code. Try again. @@ -140,7 +142,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/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/buildSrc/src/main/kotlin/Config.kt b/buildSrc/src/main/kotlin/Config.kt index 481ee19cd..393af44dc 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 { @@ -83,6 +92,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 { @@ -93,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" 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 a4afb5fdf..33423b1a5 100644 --- a/composeapp/src/main/java/com/firebase/composeapp/MainActivity.kt +++ b/composeapp/src/main/java/com/firebase/composeapp/MainActivity.kt @@ -1,25 +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 com.firebase.composeapp.ui.screens.MainScreen +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?) { @@ -29,51 +53,134 @@ class MainActivity : ComponentActivity() { val authUI = FirebaseAuthUI.getInstance() authUI.auth.useEmulator("10.0.2.2", 9099) - // 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) } + 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 = 120L, + 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 + ) { + 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.firebase_auth_120dp), + 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) + } + } + } + ) + } + } + } + } + + @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 + ) + 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, ) @@ -84,20 +191,23 @@ class MainActivity : ComponentActivity() { } } - setContent { - AuthUITheme { - Surface( - modifier = Modifier.fillMaxSize(), - color = MaterialTheme.colorScheme.background - ) { - MainScreen( - 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/MainScreen.kt b/composeapp/src/main/java/com/firebase/composeapp/ui/screens/EmailAuthMain.kt similarity index 96% 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..33938a0ff 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,11 +28,10 @@ import com.firebase.ui.auth.compose.ui.screens.SignUpUI import kotlinx.coroutines.launch @Composable -fun MainScreen( +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 new file mode 100644 index 000000000..47e7bb8c7 --- /dev/null +++ b/composeapp/src/main/java/com/firebase/composeapp/ui/screens/PhoneAuthMain.kt @@ -0,0 +1,128 @@ +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, +) { + 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, + 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 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 000000000..b03b18b53 Binary files /dev/null and b/composeapp/src/main/res/drawable-hdpi/firebase_auth_120dp.png differ 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 000000000..820ba76f9 Binary files /dev/null and b/composeapp/src/main/res/drawable-mdpi/firebase_auth_120dp.png differ 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 000000000..351fa59fe Binary files /dev/null and b/composeapp/src/main/res/drawable-xhdpi/firebase_auth_120dp.png differ diff --git a/composeapp/src/main/res/drawable-xxhdpi/firebase_auth_120dp.png b/composeapp/src/main/res/drawable-xxhdpi/firebase_auth_120dp.png new file mode 100644 index 000000000..6a6374e30 Binary files /dev/null and b/composeapp/src/main/res/drawable-xxhdpi/firebase_auth_120dp.png differ 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 000000000..9b68e8ade Binary files /dev/null and b/composeapp/src/main/res/drawable-xxxhdpi/firebase_auth_120dp.png differ 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 new file mode 100644 index 000000000..aff906c85 --- /dev/null +++ b/e2eTest/src/test/java/com/firebase/ui/auth/compose/testutil/EmulatorApi.kt @@ -0,0 +1,97 @@ +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) + + /** + * 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 + 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") + } + + 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 + 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..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,8 @@ 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 import com.google.firebase.FirebaseApp @@ -29,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 @@ -40,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) @@ -92,7 +88,7 @@ class EmailAuthScreenTest { ) // Clear emulator data - clearEmulatorData() + emulatorApi.clearEmulatorData() } @After @@ -101,7 +97,7 @@ class EmailAuthScreenTest { FirebaseAuthUI.clearInstanceCache() // Clear emulator data - clearEmulatorData() + emulatorApi.clearEmulatorData() } @Test @@ -524,7 +520,7 @@ class EmailAuthScreenTest { configuration: AuthUIConfiguration, onSuccess: ((AuthResult) -> Unit) = {}, onError: ((AuthException) -> Unit) = {}, - onCancel: (() -> Unit) = {} + onCancel: (() -> Unit) = {}, ) { EmailAuthScreen( context = applicationContext, @@ -582,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. @@ -671,86 +650,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..7e1da70b5 --- /dev/null +++ b/e2eTest/src/test/java/com/firebase/ui/auth/compose/ui/screens/PhoneAuthScreenTest.kt @@ -0,0 +1,412 @@ +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 +import org.robolectric.annotation.LooperMode + +@Config(sdk = [34]) +@RunWith(RobolectricTestRunner::class) +@LooperMode(LooperMode.Mode.PAUSED) +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() + + // 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() + } + + @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, + ) + } + } + } + } +}