@@ -28,7 +28,6 @@ import androidx.compose.material3.MaterialTheme
2828import androidx.compose.material3.Scaffold
2929import androidx.compose.material3.Surface
3030import androidx.compose.material3.Text
31- import androidx.compose.material3.TextButton
3231import androidx.compose.runtime.Composable
3332import androidx.compose.runtime.LaunchedEffect
3433import androidx.compose.runtime.collectAsState
@@ -50,10 +49,10 @@ import com.firebase.ui.auth.compose.FirebaseAuthUI
5049import com.firebase.ui.auth.compose.configuration.AuthUIConfiguration
5150import com.firebase.ui.auth.compose.configuration.MfaConfiguration
5251import com.firebase.ui.auth.compose.configuration.auth_provider.AuthProvider
52+ import com.firebase.ui.auth.compose.configuration.auth_provider.rememberAnonymousSignInHandler
5353import com.firebase.ui.auth.compose.configuration.auth_provider.rememberSignInWithFacebookLauncher
5454import com.firebase.ui.auth.compose.configuration.auth_provider.signInWithEmailLink
5555import com.firebase.ui.auth.compose.configuration.string_provider.DefaultAuthUIStringProvider
56- import com.firebase.ui.auth.compose.configuration.theme.AuthUIAsset
5756import com.firebase.ui.auth.compose.ui.components.ErrorRecoveryDialog
5857import com.firebase.ui.auth.compose.ui.method_picker.AuthMethodPicker
5958import com.firebase.ui.auth.compose.ui.screens.phone.PhoneAuthScreen
@@ -97,9 +96,14 @@ fun FirebaseAuthScreen(
9796 val pendingLinkingCredential = remember { mutableStateOf<AuthCredential ?>(null ) }
9897 val pendingResolver = remember { mutableStateOf<MultiFactorResolver ?>(null ) }
9998
99+ val anonymousProvider = configuration.providers.filterIsInstance<AuthProvider .Anonymous >().firstOrNull()
100100 val emailProvider = configuration.providers.filterIsInstance<AuthProvider .Email >().firstOrNull()
101101 val facebookProvider = configuration.providers.filterIsInstance<AuthProvider .Facebook >().firstOrNull()
102- val logoAsset = configuration.logo?.let { AuthUIAsset .Vector (it) }
102+ val logoAsset = configuration.logo
103+
104+ val onSignInAnonymously = anonymousProvider?.let {
105+ authUI.rememberAnonymousSignInHandler()
106+ }
103107
104108 val onSignInWithFacebook = facebookProvider?.let {
105109 authUI.rememberSignInWithFacebookLauncher(
@@ -109,103 +113,6 @@ fun FirebaseAuthScreen(
109113 )
110114 }
111115
112- // Handle email link sign-in (deep links)
113- LaunchedEffect (emailLink) {
114- if (emailLink != null && emailProvider != null ) {
115- try {
116- EmailLinkPersistenceManager .retrieveSessionRecord(context)?.email?.let { email ->
117- authUI.signInWithEmailLink(
118- context = context,
119- config = configuration,
120- provider = emailProvider,
121- email = email,
122- emailLink = emailLink
123- )
124- }
125- } catch (e: Exception ) {
126- Log .e(" FirebaseAuthScreen" , " Failed to complete email link sign-in" , e)
127- }
128-
129- if (navController.currentBackStackEntry?.destination?.route != AuthRoute .Email .route) {
130- navController.navigate(AuthRoute .Email .route)
131- }
132- }
133- }
134-
135- // Synchronise auth state changes with navigation stack.
136- LaunchedEffect (authState) {
137- val state = authState
138- val currentRoute = navController.currentBackStackEntry?.destination?.route
139- when (state) {
140- is AuthState .Success -> {
141- pendingResolver.value = null
142- pendingLinkingCredential.value = null
143-
144- state.result?.let { result ->
145- if (state.user.uid != lastSuccessfulUserId.value) {
146- onSignInSuccess(result)
147- lastSuccessfulUserId.value = state.user.uid
148- }
149- }
150-
151- if (currentRoute != AuthRoute .Success .route) {
152- navController.navigate(AuthRoute .Success .route) {
153- popUpTo(AuthRoute .MethodPicker .route) { inclusive = true }
154- launchSingleTop = true
155- }
156- }
157- }
158-
159- is AuthState .RequiresEmailVerification ,
160- is AuthState .RequiresProfileCompletion -> {
161- pendingResolver.value = null
162- pendingLinkingCredential.value = null
163- if (currentRoute != AuthRoute .Success .route) {
164- navController.navigate(AuthRoute .Success .route) {
165- popUpTo(AuthRoute .MethodPicker .route) { inclusive = true }
166- launchSingleTop = true
167- }
168- }
169- }
170-
171- is AuthState .RequiresMfa -> {
172- pendingResolver.value = state.resolver
173- if (currentRoute != AuthRoute .MfaChallenge .route) {
174- navController.navigate(AuthRoute .MfaChallenge .route) {
175- launchSingleTop = true
176- }
177- }
178- }
179-
180- is AuthState .Cancelled -> {
181- pendingResolver.value = null
182- pendingLinkingCredential.value = null
183- lastSuccessfulUserId.value = null
184- if (currentRoute != AuthRoute .MethodPicker .route) {
185- navController.navigate(AuthRoute .MethodPicker .route) {
186- popUpTo(AuthRoute .MethodPicker .route) { inclusive = true }
187- launchSingleTop = true
188- }
189- }
190- onSignInCancelled()
191- }
192-
193- is AuthState .Idle -> {
194- pendingResolver.value = null
195- pendingLinkingCredential.value = null
196- lastSuccessfulUserId.value = null
197- if (currentRoute != AuthRoute .MethodPicker .route) {
198- navController.navigate(AuthRoute .MethodPicker .route) {
199- popUpTo(AuthRoute .MethodPicker .route) { inclusive = true }
200- launchSingleTop = true
201- }
202- }
203- }
204-
205- else -> Unit
206- }
207- }
208-
209116 Scaffold (modifier = modifier) { innerPadding ->
210117 Surface (
211118 modifier = Modifier
@@ -224,6 +131,8 @@ fun FirebaseAuthScreen(
224131 privacyPolicyUrl = configuration.privacyPolicyUrl,
225132 onProviderSelected = { provider ->
226133 when (provider) {
134+ is AuthProvider .Anonymous -> onSignInAnonymously?.invoke()
135+
227136 is AuthProvider .Email -> {
228137 navController.navigate(AuthRoute .Email .route)
229138 }
@@ -379,6 +288,103 @@ fun FirebaseAuthScreen(
379288 }
380289 }
381290
291+ // Handle email link sign-in (deep links)
292+ LaunchedEffect (emailLink) {
293+ if (emailLink != null && emailProvider != null ) {
294+ try {
295+ EmailLinkPersistenceManager .retrieveSessionRecord(context)?.email?.let { email ->
296+ authUI.signInWithEmailLink(
297+ context = context,
298+ config = configuration,
299+ provider = emailProvider,
300+ email = email,
301+ emailLink = emailLink
302+ )
303+ }
304+ } catch (e: Exception ) {
305+ Log .e(" FirebaseAuthScreen" , " Failed to complete email link sign-in" , e)
306+ }
307+
308+ if (navController.currentBackStackEntry?.destination?.route != AuthRoute .Email .route) {
309+ navController.navigate(AuthRoute .Email .route)
310+ }
311+ }
312+ }
313+
314+ // Synchronise auth state changes with navigation stack.
315+ LaunchedEffect (authState) {
316+ val state = authState
317+ val currentRoute = navController.currentBackStackEntry?.destination?.route
318+ when (state) {
319+ is AuthState .Success -> {
320+ pendingResolver.value = null
321+ pendingLinkingCredential.value = null
322+
323+ state.result?.let { result ->
324+ if (state.user.uid != lastSuccessfulUserId.value) {
325+ onSignInSuccess(result)
326+ lastSuccessfulUserId.value = state.user.uid
327+ }
328+ }
329+
330+ if (currentRoute != AuthRoute .Success .route) {
331+ navController.navigate(AuthRoute .Success .route) {
332+ popUpTo(AuthRoute .MethodPicker .route) { inclusive = true }
333+ launchSingleTop = true
334+ }
335+ }
336+ }
337+
338+ is AuthState .RequiresEmailVerification ,
339+ is AuthState .RequiresProfileCompletion -> {
340+ pendingResolver.value = null
341+ pendingLinkingCredential.value = null
342+ if (currentRoute != AuthRoute .Success .route) {
343+ navController.navigate(AuthRoute .Success .route) {
344+ popUpTo(AuthRoute .MethodPicker .route) { inclusive = true }
345+ launchSingleTop = true
346+ }
347+ }
348+ }
349+
350+ is AuthState .RequiresMfa -> {
351+ pendingResolver.value = state.resolver
352+ if (currentRoute != AuthRoute .MfaChallenge .route) {
353+ navController.navigate(AuthRoute .MfaChallenge .route) {
354+ launchSingleTop = true
355+ }
356+ }
357+ }
358+
359+ is AuthState .Cancelled -> {
360+ pendingResolver.value = null
361+ pendingLinkingCredential.value = null
362+ lastSuccessfulUserId.value = null
363+ if (currentRoute != AuthRoute .MethodPicker .route) {
364+ navController.navigate(AuthRoute .MethodPicker .route) {
365+ popUpTo(AuthRoute .MethodPicker .route) { inclusive = true }
366+ launchSingleTop = true
367+ }
368+ }
369+ onSignInCancelled()
370+ }
371+
372+ is AuthState .Idle -> {
373+ pendingResolver.value = null
374+ pendingLinkingCredential.value = null
375+ lastSuccessfulUserId.value = null
376+ if (currentRoute != AuthRoute .MethodPicker .route) {
377+ navController.navigate(AuthRoute .MethodPicker .route) {
378+ popUpTo(AuthRoute .MethodPicker .route) { inclusive = true }
379+ launchSingleTop = true
380+ }
381+ }
382+ }
383+
384+ else -> Unit
385+ }
386+ }
387+
382388 val errorState = authState as ? AuthState .Error
383389 if (isErrorDialogVisible.value && errorState != null ) {
384390 ErrorRecoveryDialog (
0 commit comments