Skip to content

Commit a4defea

Browse files
committed
add welcomebackscreen
1 parent fb5827a commit a4defea

File tree

5 files changed

+326
-46
lines changed

5 files changed

+326
-46
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,5 @@ google-services.json
1111
crashlytics-build.properties
1212
auth/src/main/res/values/com_crashlytics_export_strings.xml
1313
*.log
14+
internal/**
15+
lint/**

auth/src/main/java/com/firebase/ui/auth/ui/email/EmailActivity.kt

Lines changed: 42 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import com.firebase.ui.auth.util.data.EmailLinkPersistenceManager
4141
import com.firebase.ui.auth.util.data.ProviderUtils
4242
import com.firebase.ui.auth.viewmodel.RequestCodes
4343
import com.firebase.ui.auth.viewmodel.email.EmailProviderResponseHandler
44+
import com.firebase.ui.auth.viewmodel.email.WelcomeBackPasswordViewModel
4445
import com.google.android.material.textfield.TextInputLayout
4546
import com.google.firebase.auth.ActionCodeSettings
4647
import com.google.firebase.auth.EmailAuthProvider
@@ -66,6 +67,7 @@ class EmailActivity : AppCompatBase(), RegisterEmailFragment.AnonymousUpgradeLis
6667

6768
private var emailLayout: TextInputLayout? = null
6869
private lateinit var mHandler: EmailProviderResponseHandler
70+
private lateinit var mPasswordHandler: WelcomeBackPasswordViewModel
6971

7072
companion object {
7173
@JvmStatic
@@ -97,6 +99,8 @@ class EmailActivity : AppCompatBase(), RegisterEmailFragment.AnonymousUpgradeLis
9799
emailLayout = findViewById(R.id.email_layout)
98100
mHandler = ViewModelProvider(this).get(EmailProviderResponseHandler::class.java)
99101
mHandler.init(getFlowParams())
102+
mPasswordHandler = ViewModelProvider(this).get(WelcomeBackPasswordViewModel::class.java)
103+
mPasswordHandler.init(getFlowParams())
100104

101105
if (savedInstanceState != null) {
102106
return
@@ -168,37 +172,13 @@ class EmailActivity : AppCompatBase(), RegisterEmailFragment.AnonymousUpgradeLis
168172
flowParameters = getFlowParams(),
169173
user = user,
170174
onRegisterSuccess = { newUser, password ->
171-
val response = IdpResponse.Builder(newUser).build()
172-
startSaveCredentials(
173-
mHandler.getCurrentUser(),
174-
response,
175+
mHandler.startSignIn(
176+
IdpResponse.Builder(newUser).build(),
175177
password
176178
)
179+
finish()
177180
},
178181
onRegisterError = { e ->
179-
when (e) {
180-
is FirebaseAuthWeakPasswordException -> {
181-
// Handle weak password error
182-
val minLength = resources.getInteger(R.integer.fui_min_password_length)
183-
emailLayout?.error = resources.getQuantityString(
184-
R.plurals.fui_error_weak_password,
185-
minLength,
186-
minLength
187-
)
188-
}
189-
is FirebaseAuthInvalidCredentialsException -> {
190-
// Handle invalid credentials error
191-
emailLayout?.error = getString(R.string.fui_invalid_email_address)
192-
}
193-
is FirebaseAuthAnonymousUpgradeException -> {
194-
// Handle anonymous upgrade error
195-
onMergeFailure(e.response)
196-
}
197-
else -> {
198-
// Handle general error
199-
emailLayout?.error = getString(R.string.fui_email_account_creation_error)
200-
}
201-
}
202182
}
203183
)
204184
}
@@ -241,11 +221,37 @@ class EmailActivity : AppCompatBase(), RegisterEmailFragment.AnonymousUpgradeLis
241221
}
242222
showRegisterEmailLinkFragment(emailConfig, email)
243223
} else {
244-
startActivityForResult(
245-
WelcomeBackPasswordPrompt.createIntent(this, getFlowParams(), IdpResponse.Builder(user).build()),
246-
RequestCodes.WELCOME_BACK_EMAIL_FLOW
247-
)
248-
setSlideAnimation()
224+
setContent {
225+
WelcomeBackPasswordPrompt(
226+
flowParameters = getFlowParams(),
227+
email = user.email ?: "",
228+
idpResponse = IdpResponse.Builder(user).build(),
229+
onSignInSuccess = {
230+
finish(RESULT_OK, IdpResponse.Builder(user).build().toIntent())
231+
},
232+
onSignInError = { exception ->
233+
when (exception) {
234+
is FirebaseAuthAnonymousUpgradeException -> {
235+
finish(ErrorCodes.ANONYMOUS_UPGRADE_MERGE_CONFLICT, exception.response.toIntent())
236+
}
237+
is FirebaseAuthInvalidCredentialsException -> {
238+
// Error is already handled in the UI
239+
}
240+
else -> {
241+
finish(RESULT_CANCELED, IdpResponse.getErrorIntent(exception))
242+
}
243+
}
244+
},
245+
onForgotPassword = {
246+
startActivityForResult(
247+
RecoverPasswordActivity.createIntent(this, getFlowParams(), user.email),
248+
RequestCodes.RECOVER_PASSWORD
249+
)
250+
setSlideAnimation()
251+
},
252+
viewModel = mPasswordHandler
253+
)
254+
}
249255
}
250256
}
251257

@@ -280,24 +286,14 @@ class EmailActivity : AppCompatBase(), RegisterEmailFragment.AnonymousUpgradeLis
280286
flowParameters = getFlowParams(),
281287
user = user,
282288
onRegisterSuccess = { newUser, password ->
283-
val response = IdpResponse.Builder(newUser).build()
284-
startSaveCredentials(
285-
mHandler.getCurrentUser(),
286-
response,
289+
mHandler.startSignIn(
290+
IdpResponse.Builder(newUser).build(),
287291
password
288292
)
293+
finish()
289294
},
290295
onRegisterError = { e ->
291-
if (e is FirebaseAuthWeakPasswordException) {
292-
// Handle weak password error
293-
} else if (e is FirebaseAuthInvalidCredentialsException) {
294-
// Handle invalid credentials error
295-
} else if (e is FirebaseAuthAnonymousUpgradeException) {
296-
val response = e.response
297-
onMergeFailure(response)
298-
} else {
299-
// Handle general error
300-
}
296+
301297
}
302298
)
303299
}
Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
package com.firebase.ui.auth.ui.email
2+
3+
import android.os.Bundle
4+
import androidx.compose.foundation.layout.*
5+
import androidx.compose.foundation.rememberScrollState
6+
import androidx.compose.foundation.text.KeyboardActions
7+
import androidx.compose.foundation.text.KeyboardOptions
8+
import androidx.compose.foundation.verticalScroll
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.platform.LocalContext
14+
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
15+
import androidx.compose.ui.res.stringResource
16+
import androidx.compose.ui.text.SpanStyle
17+
import androidx.compose.ui.text.buildAnnotatedString
18+
import androidx.compose.ui.text.input.ImeAction
19+
import androidx.compose.ui.text.input.KeyboardType
20+
import androidx.compose.ui.text.input.PasswordVisualTransformation
21+
import androidx.compose.ui.text.style.TextAlign
22+
import androidx.compose.ui.text.withStyle
23+
import androidx.compose.ui.unit.dp
24+
import androidx.compose.foundation.layout.systemBarsPadding
25+
import com.firebase.ui.auth.R
26+
import com.firebase.ui.auth.data.model.FlowParameters
27+
import com.firebase.ui.auth.data.model.Resource
28+
import com.firebase.ui.auth.data.model.State
29+
import com.firebase.ui.auth.ui.idp.TermsAndPrivacyText
30+
import com.firebase.ui.auth.util.data.PrivacyDisclosureUtils
31+
import com.firebase.ui.auth.viewmodel.email.WelcomeBackPasswordViewModel
32+
import com.google.firebase.auth.FirebaseAuthException
33+
import com.google.firebase.auth.FirebaseAuthInvalidCredentialsException
34+
import com.firebase.ui.auth.FirebaseAuthAnonymousUpgradeException
35+
36+
private fun validateAndSignIn(
37+
password: String,
38+
context: android.content.Context,
39+
viewModel: WelcomeBackPasswordViewModel,
40+
email: String,
41+
idpResponse: com.firebase.ui.auth.IdpResponse,
42+
onError: (String) -> Unit
43+
) {
44+
if (password.isBlank()) {
45+
onError(context.getString(R.string.fui_error_invalid_password))
46+
return
47+
}
48+
49+
viewModel.signIn(email, password, idpResponse)
50+
}
51+
52+
@Composable
53+
fun WelcomeBackPasswordPrompt(
54+
modifier: Modifier = Modifier,
55+
flowParameters: FlowParameters,
56+
email: String,
57+
idpResponse: com.firebase.ui.auth.IdpResponse,
58+
onSignInSuccess: () -> Unit,
59+
onSignInError: (Exception) -> Unit,
60+
onForgotPassword: () -> Unit,
61+
viewModel: WelcomeBackPasswordViewModel
62+
) {
63+
var password by remember { mutableStateOf("") }
64+
var isPasswordError by remember { mutableStateOf(false) }
65+
var passwordErrorText by remember { mutableStateOf("") }
66+
67+
val keyboardController = LocalSoftwareKeyboardController.current
68+
val context = LocalContext.current
69+
70+
LaunchedEffect(flowParameters) {
71+
viewModel.init(flowParameters)
72+
}
73+
74+
val signInState by viewModel.signInState.collectAsState()
75+
val isLoading = signInState?.getState() == State.LOADING
76+
77+
LaunchedEffect(signInState) {
78+
when (signInState?.getState()) {
79+
State.SUCCESS -> {
80+
onSignInSuccess()
81+
}
82+
State.FAILURE -> {
83+
signInState?.getException()?.let { error ->
84+
when (error) {
85+
is FirebaseAuthInvalidCredentialsException -> {
86+
isPasswordError = true
87+
passwordErrorText = context.getString(R.string.fui_error_invalid_password)
88+
}
89+
else -> {
90+
onSignInError(error)
91+
}
92+
}
93+
}
94+
}
95+
else -> {}
96+
}
97+
}
98+
99+
val welcomeText = buildAnnotatedString {
100+
val baseText = context.getString(R.string.fui_welcome_back_password_prompt_body, email)
101+
val emailIndex = baseText.indexOf(email)
102+
append(baseText.substring(0, emailIndex))
103+
withStyle(SpanStyle(fontWeight = androidx.compose.ui.text.font.FontWeight.Bold)) {
104+
append(email)
105+
}
106+
append(baseText.substring(emailIndex + email.length))
107+
}
108+
109+
Scaffold(
110+
modifier = modifier
111+
.fillMaxSize()
112+
.systemBarsPadding(),
113+
containerColor = MaterialTheme.colorScheme.background
114+
) { innerPadding ->
115+
Column(
116+
modifier = Modifier
117+
.padding(innerPadding)
118+
.fillMaxSize()
119+
.verticalScroll(rememberScrollState())
120+
.padding(horizontal = 24.dp),
121+
horizontalAlignment = Alignment.CenterHorizontally
122+
) {
123+
if (isLoading) {
124+
LinearProgressIndicator(
125+
modifier = Modifier
126+
.fillMaxWidth()
127+
.height(4.dp)
128+
)
129+
Spacer(Modifier.height(16.dp))
130+
} else {
131+
Spacer(Modifier.height(24.dp))
132+
}
133+
134+
Text(
135+
text = welcomeText,
136+
style = MaterialTheme.typography.bodyLarge,
137+
textAlign = TextAlign.Start,
138+
modifier = Modifier.fillMaxWidth()
139+
)
140+
141+
Spacer(Modifier.height(32.dp))
142+
143+
OutlinedTextField(
144+
value = password,
145+
onValueChange = {
146+
password = it
147+
isPasswordError = false
148+
},
149+
label = { Text(stringResource(R.string.fui_password_hint)) },
150+
isError = isPasswordError,
151+
supportingText = if (isPasswordError) {
152+
{ Text(passwordErrorText) }
153+
} else null,
154+
visualTransformation = PasswordVisualTransformation(),
155+
keyboardOptions = KeyboardOptions(
156+
keyboardType = KeyboardType.Password,
157+
imeAction = ImeAction.Done
158+
),
159+
keyboardActions = KeyboardActions(
160+
onDone = {
161+
keyboardController?.hide()
162+
validateAndSignIn(
163+
password = password,
164+
context = context,
165+
viewModel = viewModel,
166+
email = email,
167+
idpResponse = idpResponse,
168+
onError = { error ->
169+
isPasswordError = true
170+
passwordErrorText = error
171+
}
172+
)
173+
}
174+
),
175+
shape = MaterialTheme.shapes.medium,
176+
colors = OutlinedTextFieldDefaults.colors(
177+
focusedBorderColor = MaterialTheme.colorScheme.primary,
178+
unfocusedBorderColor = MaterialTheme.colorScheme.outline,
179+
focusedLabelColor = MaterialTheme.colorScheme.primary,
180+
unfocusedLabelColor = MaterialTheme.colorScheme.onSurfaceVariant,
181+
cursorColor = MaterialTheme.colorScheme.primary,
182+
errorBorderColor = MaterialTheme.colorScheme.error,
183+
errorLabelColor = MaterialTheme.colorScheme.error,
184+
errorSupportingTextColor = MaterialTheme.colorScheme.error
185+
),
186+
modifier = Modifier
187+
.fillMaxWidth()
188+
.padding(bottom = if (isPasswordError) 8.dp else 0.dp)
189+
)
190+
191+
Spacer(Modifier.height(24.dp))
192+
193+
Button(
194+
onClick = {
195+
validateAndSignIn(
196+
password = password,
197+
context = context,
198+
viewModel = viewModel,
199+
email = email,
200+
idpResponse = idpResponse,
201+
onError = { error ->
202+
isPasswordError = true
203+
passwordErrorText = error
204+
}
205+
)
206+
},
207+
enabled = !isLoading,
208+
modifier = Modifier
209+
.fillMaxWidth()
210+
.height(48.dp)
211+
) {
212+
Text(stringResource(R.string.fui_sign_in_default))
213+
}
214+
215+
Spacer(Modifier.height(16.dp))
216+
217+
TextButton(
218+
onClick = onForgotPassword,
219+
modifier = Modifier.fillMaxWidth()
220+
) {
221+
Text(stringResource(R.string.fui_trouble_signing_in))
222+
}
223+
224+
Spacer(Modifier.weight(1f))
225+
226+
if (flowParameters.isPrivacyPolicyUrlProvided() &&
227+
flowParameters.isTermsOfServiceUrlProvided()
228+
) {
229+
TermsAndPrivacyText(
230+
tosUrl = flowParameters.termsOfServiceUrl!!,
231+
ppUrl = flowParameters.privacyPolicyUrl!!,
232+
modifier = Modifier
233+
.fillMaxWidth()
234+
.padding(vertical = 16.dp)
235+
)
236+
}
237+
}
238+
}
239+
}

auth/src/main/java/com/firebase/ui/auth/viewmodel/RequestCodes.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ public final class RequestCodes {
5858
/** Request code for starter a generic IDP sign-in flow */
5959
public static final int GENERIC_IDP_SIGN_IN_FLOW = 117;
6060

61+
/** Request code for recover password */
62+
public static final int RECOVER_PASSWORD = 118;
63+
6164
private RequestCodes() {
6265
throw new AssertionError("No instance for you!");
6366
}

0 commit comments

Comments
 (0)