Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions auth/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import com.android.build.gradle.internal.dsl.TestOptions

plugins {
id("com.android.library")
id("com.vanniktech.maven.publish")
Expand All @@ -13,7 +11,7 @@ android {

defaultConfig {
minSdk = Config.SdkVersions.min
targetSdk =Config.SdkVersions.target
targetSdk = Config.SdkVersions.target

buildConfigField("String", "VERSION_NAME", "\"${Config.version}\"")

Expand All @@ -27,8 +25,8 @@ android {
consumerProguardFiles("auth-proguard.pro")
}
}
compileOptions {

compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -404,13 +406,15 @@ abstract class AuthProvider(open val providerId: String) {
*/
internal suspend fun verifyPhoneNumberAwait(
auth: FirebaseAuth,
activity: Activity?,
phoneNumber: String,
multiFactorSession: MultiFactorSession? = null,
forceResendingToken: PhoneAuthProvider.ForceResendingToken?,
verifier: Verifier = DefaultVerifier(),
): VerifyPhoneNumberResult {
return verifier.verifyPhoneNumber(
auth,
activity,
phoneNumber,
timeout,
forceResendingToken,
Expand All @@ -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
}

Expand All @@ -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))
}
Expand All @@ -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())
}
Expand All @@ -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)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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

Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -167,13 +169,47 @@ 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
get() = localizedContext.getString(R.string.fui_incorrect_code_dialog_body)
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
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -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
}
}
Loading