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
+ }
0 commit comments