Skip to content

Commit 765592c

Browse files
committed
SignedInScreen
1 parent 44ba101 commit 765592c

File tree

4 files changed

+314
-64
lines changed

4 files changed

+314
-64
lines changed

app/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ dependencies {
104104
implementation("androidx.compose.material3:material3")
105105
implementation("androidx.activity:activity-compose:1.10.0")
106106
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.5")
107+
implementation("com.github.bumptech.glide:compose:1.0.0-beta01")
107108
debugImplementation("androidx.compose.ui:ui-tooling")
108109
releaseImplementation("androidx.compose.ui:ui-tooling-preview")
109110
}

app/src/main/java/com/firebase/uidemo/auth/compose/AuthScreen.kt

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@ import com.firebase.ui.auth.AuthUI.IdpConfig
1111
import com.firebase.ui.auth.data.model.FirebaseAuthUIAuthenticationResult
1212
import com.firebase.ui.auth.compose.FirebaseAuthUI
1313
import com.firebase.uidemo.R
14-
import com.google.firebase.auth.EmailAuthProvider
15-
import com.google.firebase.auth.GoogleAuthProvider
1614

1715
@Composable
1816
fun AuthScreen(
@@ -23,19 +21,18 @@ fun AuthScreen(
2321
IdpConfig.EmailBuilder().build(),
2422
)
2523

26-
Box(
27-
modifier = Modifier.fillMaxSize(),
28-
contentAlignment = Alignment.Center
29-
) {
30-
FirebaseAuthUI(
31-
providers = providers,
32-
onSignInResult = onSignInResult,
33-
theme = R.style.AppTheme,
34-
logo = R.drawable.firebase_auth_120dp,
35-
tosUrl = "https://www.google.com/policies/terms/",
36-
privacyPolicyUrl = "https://www.google.com/policies/privacy/",
37-
enableCredentials = true,
38-
enableAnonymousUpgrade = false
39-
)
40-
}
24+
FirebaseAuthUI(
25+
providers = providers,
26+
onSignInResult = { result -> /* optional logging */ },
27+
28+
signedInContent = {
29+
SignedInScreen(idpResponse = null) {
30+
}
31+
},
32+
33+
theme = R.style.AppTheme,
34+
logo = R.drawable.firebase_auth_120dp,
35+
tosUrl = "https://www.google.com/policies/terms/",
36+
privacyPolicyUrl = "https://www.google.com/policies/privacy/"
37+
)
4138
}
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
package com.firebase.uidemo.auth.compose
2+
3+
import android.content.Context
4+
import androidx.compose.foundation.layout.*
5+
import androidx.compose.foundation.shape.CircleShape
6+
import androidx.compose.material.icons.Icons
7+
import androidx.compose.material.icons.filled.Delete
8+
import androidx.compose.material.icons.filled.ExitToApp
9+
import androidx.compose.material3.*
10+
import androidx.compose.runtime.*
11+
import androidx.compose.ui.Alignment
12+
import androidx.compose.ui.Modifier
13+
import androidx.compose.ui.draw.clip
14+
import androidx.compose.ui.platform.LocalContext
15+
import androidx.compose.ui.res.stringResource
16+
import androidx.compose.ui.text.style.TextOverflow
17+
import androidx.compose.ui.unit.dp
18+
import com.bumptech.glide.integration.compose.GlideImage
19+
import com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi
20+
import com.firebase.ui.auth.AuthUI
21+
import com.firebase.ui.auth.IdpResponse
22+
import com.firebase.uidemo.R
23+
import com.google.firebase.auth.*
24+
import kotlinx.coroutines.launch
25+
26+
@OptIn(ExperimentalMaterial3Api::class)
27+
@Composable
28+
fun SignedInScreen(
29+
idpResponse: IdpResponse?,
30+
onSignedOut: () -> Unit
31+
) {
32+
val context = LocalContext.current
33+
val scope = rememberCoroutineScope()
34+
val snackbar = remember { SnackbarHostState() }
35+
36+
val firebaseUser by produceState(initialValue = FirebaseAuth.getInstance().currentUser) {
37+
val listener = FirebaseAuth.AuthStateListener { auth -> value = auth.currentUser }
38+
FirebaseAuth.getInstance().addAuthStateListener(listener)
39+
awaitDispose { FirebaseAuth.getInstance().removeAuthStateListener(listener) }
40+
}
41+
if (firebaseUser == null) {
42+
onSignedOut(); return
43+
}
44+
45+
var askDelete by remember { mutableStateOf(false) }
46+
47+
Scaffold(
48+
snackbarHost = { SnackbarHost(snackbar) },
49+
topBar = { CenterAlignedTopAppBar(title = { Text("Profile") }) },
50+
floatingActionButton = {
51+
FloatingActionButton(onClick = { askDelete = true }) {
52+
Icon(Icons.Default.Delete, contentDescription = null)
53+
}
54+
}
55+
) { padding ->
56+
ProfileContent(
57+
modifier = Modifier.padding(padding),
58+
user = firebaseUser!!,
59+
isNewUser = idpResponse?.isNewUser ?: false,
60+
idpToken = idpResponse?.idpToken,
61+
idpSecret = idpResponse?.idpSecret,
62+
onSignOut = {
63+
AuthUI.getInstance().signOut(context).addOnCompleteListener { task ->
64+
if (task.isSuccessful) onSignedOut()
65+
else scope.launch {
66+
snackbar.showSnackbar(
67+
context.getString(R.string.sign_out_failed)
68+
)
69+
}
70+
}
71+
}
72+
)
73+
}
74+
75+
if (askDelete) {
76+
AlertDialog(
77+
onDismissRequest = { askDelete = false },
78+
confirmButton = {
79+
TextButton(onClick = {
80+
askDelete = false
81+
AuthUI.getInstance().delete(context).addOnCompleteListener { task ->
82+
if (task.isSuccessful) onSignedOut()
83+
else scope.launch {
84+
snackbar.showSnackbar(
85+
context.getString(R.string.delete_account_failed)
86+
)
87+
}
88+
}
89+
}) { Text("Yes, nuke it!") }
90+
},
91+
dismissButton = { TextButton(onClick = { askDelete = false }) { Text("No") } },
92+
title = { Text("Delete account") },
93+
text = { Text("Are you sure you want to delete this account?") }
94+
)
95+
}
96+
}
97+
98+
/* ---------------- profile details ---------------- */
99+
100+
@OptIn(ExperimentalGlideComposeApi::class)
101+
@Composable
102+
private fun ProfileContent(
103+
modifier: Modifier = Modifier,
104+
user: FirebaseUser,
105+
isNewUser: Boolean,
106+
idpToken: String?,
107+
idpSecret: String?,
108+
onSignOut: () -> Unit
109+
) {
110+
val ctx = LocalContext.current
111+
val providers = remember(user) { user.enabledProviderNames(ctx) }
112+
113+
Column(
114+
modifier = modifier
115+
.fillMaxSize()
116+
.padding(24.dp),
117+
horizontalAlignment = Alignment.CenterHorizontally
118+
) {
119+
user.photoUrl?.let { url ->
120+
GlideImage(
121+
model = url,
122+
contentDescription = null,
123+
modifier = Modifier
124+
.size(96.dp)
125+
.clip(CircleShape)
126+
)
127+
Spacer(Modifier.height(16.dp))
128+
}
129+
130+
InfoRow(label = "Email", value = user.email)
131+
InfoRow(label = "Phone", value = user.phoneNumber)
132+
InfoRow(label = "Display name", value = user.displayName)
133+
134+
if (isNewUser) {
135+
Text(
136+
"New user",
137+
style = MaterialTheme.typography.labelMedium,
138+
color = MaterialTheme.colorScheme.primary,
139+
modifier = Modifier.padding(top = 8.dp)
140+
)
141+
}
142+
143+
Spacer(Modifier.height(12.dp))
144+
145+
Text(
146+
text = stringResource(R.string.used_providers, providers),
147+
style = MaterialTheme.typography.bodySmall
148+
)
149+
150+
if (idpToken != null) {
151+
Spacer(Modifier.height(12.dp))
152+
TokenBlock(label = "IDP Token", value = idpToken)
153+
}
154+
if (idpSecret != null) {
155+
Spacer(Modifier.height(8.dp))
156+
TokenBlock(label = "IDP Secret", value = idpSecret)
157+
}
158+
159+
Spacer(Modifier.weight(1f))
160+
161+
Button(
162+
onClick = onSignOut,
163+
modifier = Modifier.fillMaxWidth()
164+
) {
165+
Icon(Icons.Default.ExitToApp, contentDescription = null)
166+
Spacer(Modifier.width(8.dp))
167+
Text(stringResource(R.string.sign_out))
168+
}
169+
}
170+
}
171+
172+
@Composable
173+
private fun InfoRow(label: String, value: String?) {
174+
Row(
175+
modifier = Modifier.fillMaxWidth(),
176+
horizontalArrangement = Arrangement.SpaceBetween
177+
) {
178+
Text(label)
179+
Text(
180+
text = value.takeUnless { it.isNullOrBlank() } ?: "",
181+
overflow = TextOverflow.Ellipsis,
182+
maxLines = 1
183+
)
184+
}
185+
}
186+
187+
@Composable
188+
private fun TokenBlock(label: String, value: String) {
189+
Column(Modifier.fillMaxWidth()) {
190+
Text(label, style = MaterialTheme.typography.labelSmall)
191+
Text(value, style = MaterialTheme.typography.bodySmall, overflow = TextOverflow.Ellipsis)
192+
}
193+
}
194+
195+
private fun FirebaseUser.enabledProviderNames(ctx: Context): String {
196+
if (providerData.isEmpty()) return ctx.getString(R.string.providers_anonymous)
197+
198+
val names = providerData.mapNotNull { info ->
199+
when (info.providerId) {
200+
GoogleAuthProvider.PROVIDER_ID -> ctx.getString(R.string.providers_google)
201+
FacebookAuthProvider.PROVIDER_ID -> ctx.getString(R.string.providers_facebook)
202+
TwitterAuthProvider.PROVIDER_ID -> ctx.getString(R.string.providers_twitter)
203+
EmailAuthProvider.PROVIDER_ID -> ctx.getString(R.string.providers_email)
204+
PhoneAuthProvider.PROVIDER_ID -> ctx.getString(R.string.providers_phone)
205+
AuthUI.EMAIL_LINK_PROVIDER -> ctx.getString(R.string.providers_email_link)
206+
FirebaseAuthProvider.PROVIDER_ID -> null // ignore
207+
else -> info.providerId
208+
}
209+
}
210+
return names.joinToString()
211+
}

0 commit comments

Comments
 (0)