1
+ import 'dart:async' ;
2
+ import 'dart:math' ;
3
+
4
+ import 'package:app_links/app_links.dart' ;
5
+ import 'package:flutter/foundation.dart' ;
1
6
import 'package:flutter/material.dart' ;
2
7
import 'package:firebase_auth/firebase_auth.dart' ;
8
+ import 'package:google_sign_in/google_sign_in.dart' ;
9
+ import 'package:url_launcher/url_launcher.dart' ;
10
+
11
+ class GoogleSignInArgs {
12
+ const GoogleSignInArgs (
13
+ {required this .clientId,
14
+ required this .redirectUri,
15
+ required this .scope,
16
+ required this .responseType,
17
+ required this .prompt,
18
+ required this .nonce});
19
+
20
+ /// The OAuth client id of your Google app.
21
+ ///
22
+ /// See https://developers.google.com/identity/gsi/web/guides/get-google-api-clientid.
23
+ final String clientId;
24
+
25
+ // The authentication scopes.
26
+ ///
27
+ /// See https://developers.google.com/identity/protocols/oauth2/scopes.
28
+ final String scope;
29
+
30
+ /// The authentication response types (e.g. token, id_token, code).
31
+ final String responseType;
32
+
33
+ /// A list of prompts to present the user.
34
+ final String prompt;
35
+
36
+ /// Cryptographic nonce used to prevent replay attacks.
37
+ ///
38
+ /// It may be required when using an id_token as a response type.
39
+ /// The response from Google should include the same nonce inside the id_token.
40
+ final String nonce;
41
+
42
+ /// The URL where the user will be redirected after
43
+ /// completing the authentication in the browser.
44
+ final String redirectUri;
45
+ }
3
46
4
47
class LoginPage extends StatefulWidget {
5
48
const LoginPage ({super .key});
@@ -12,9 +55,66 @@ class _LoginPageState extends State<LoginPage> {
12
55
final TextEditingController _emailController = TextEditingController ();
13
56
final TextEditingController _passwordController = TextEditingController ();
14
57
final FirebaseAuth _auth = FirebaseAuth .instance;
15
- bool _isLoggedIn = false ;
58
+ // bool _isLoggedIn = false;
16
59
bool _isError = false ;
17
60
String _loggedInEmail = '' ;
61
+ late StreamSubscription <Uri > listener;
62
+ @override
63
+ void initState () {
64
+ super .initState ();
65
+ if (defaultTargetPlatform == TargetPlatform .macOS ||
66
+ defaultTargetPlatform == TargetPlatform .linux ||
67
+ defaultTargetPlatform == TargetPlatform .windows) {
68
+ listener = AppLinks ().uriLinkStream.listen ((uri) async {
69
+ if (uri.scheme != 'notease' ) return ;
70
+ if (uri.path != '/google-auth' ) return ;
71
+
72
+ final authenticationIdToken = uri.queryParameters['id_token' ];
73
+ final authenticationAccessToken = uri.queryParameters['access_token' ];
74
+
75
+ // Authentication completed, you may use the access token to
76
+ // access user-specific data from Google.
77
+ //
78
+ // At this step, you may want to verify that the nonce
79
+ // from the id token matches the one you generated previously.
80
+ //
81
+ // Example:
82
+ // Signing-in with Firebase Auth credentials using the retrieved
83
+ // id and access tokens.
84
+ // print("signing in...");
85
+ setState (() {
86
+ _loggedInEmail = "Signing you in..." ;
87
+ });
88
+ final credential = GoogleAuthProvider .credential (
89
+ idToken: authenticationIdToken,
90
+ accessToken: authenticationAccessToken,
91
+ );
92
+
93
+ await _auth.signInWithCredential (credential);
94
+ if (_auth.currentUser == null ) {
95
+ setState (() {
96
+ _loggedInEmail = "Something went wrong!" ;
97
+ });
98
+ }
99
+ });
100
+ }
101
+ _auth.authStateChanges ().listen ((User ? user) {
102
+ if (user != null ) {
103
+ // if (mounted) {
104
+ // setState(() {
105
+ // _isLoggedIn = true;
106
+ // _loggedInEmail = user.email ?? '';
107
+ // });
108
+ // }
109
+ if (defaultTargetPlatform == TargetPlatform .macOS ||
110
+ defaultTargetPlatform == TargetPlatform .linux ||
111
+ defaultTargetPlatform == TargetPlatform .windows) {
112
+ listener.cancel ();
113
+ }
114
+ Navigator .pop (context);
115
+ }
116
+ });
117
+ }
18
118
19
119
@override
20
120
Widget build (BuildContext context) {
@@ -48,35 +148,118 @@ class _LoginPageState extends State<LoginPage> {
48
148
onPressed: () async {
49
149
try {
50
150
if (_auth.currentUser == null ) {
51
- final UserCredential userCredential =
52
- await _auth.signInWithEmailAndPassword (
151
+ _auth.signInWithEmailAndPassword (
53
152
email: _emailController.text,
54
153
password: _passwordController.text,
55
154
);
56
- final User ? user = userCredential.user;
155
+ }
156
+ } on Exception catch (e) {
157
+ if (mounted) {
57
158
setState (() {
58
- _isLoggedIn = true ;
59
- _loggedInEmail = user? .email ?? '' ;
60
- Navigator .pop (context);
159
+ _isError = true ;
160
+ _loggedInEmail = 'Error: ${e .toString ()}' ;
61
161
});
62
162
}
163
+ }
164
+ },
165
+ child: const Text ('Login' ),
166
+ ),
167
+ if (_isError) Text (_loggedInEmail) else const Text ('Not logged in' ),
168
+
169
+ // Add a Google sign-in button
170
+ ElevatedButton (
171
+ onPressed: () async {
172
+ try {
173
+ await signInWithGoogle ();
63
174
} on Exception catch (e) {
64
175
setState (() {
65
176
_isError = true ;
66
- _loggedInEmail = 'Error: ${ e .toString ()}' ;
177
+ _loggedInEmail = e.toString ();
67
178
});
68
179
}
69
180
},
70
- child: const Text ('Login ' ),
181
+ child: const Text ('Sign in with Google ' ),
71
182
),
72
- if (_isLoggedIn)
73
- Text ('Logged in as $_loggedInEmail ' )
74
- else if (_isError)
75
- Text (_loggedInEmail)
76
- else
77
- const Text ('Not logged in' )
78
183
],
79
184
),
80
185
);
81
186
}
187
+
188
+ Future <bool > signInWithGoogle () async {
189
+ if (kIsWeb) {
190
+ // Create a new provider
191
+ GoogleAuthProvider googleProvider = GoogleAuthProvider ();
192
+
193
+ // googleProvider
194
+ // .addScope('https://www.googleapis.com/auth/contacts.readonly');
195
+ googleProvider.
setCustomParameters ({
'login_hint' : '[email protected] ' });
196
+
197
+ // Once signed in, return the UserCredential
198
+ _auth.signInWithPopup (googleProvider);
199
+ return true ;
200
+ } else if (defaultTargetPlatform == TargetPlatform .android ||
201
+ defaultTargetPlatform == TargetPlatform .iOS) {
202
+ final GoogleSignInAccount ? googleUser = await GoogleSignIn ().signIn ();
203
+
204
+ // Obtain the auth details from the request
205
+ final GoogleSignInAuthentication ? googleAuth =
206
+ await googleUser? .authentication;
207
+
208
+ // Create a new credential
209
+ final credential = GoogleAuthProvider .credential (
210
+ accessToken: googleAuth? .accessToken,
211
+ idToken: googleAuth? .idToken,
212
+ );
213
+ // Once signed in, return the UserCredential
214
+ _auth.signInWithCredential (credential);
215
+ return true ;
216
+ } else if (defaultTargetPlatform == TargetPlatform .macOS ||
217
+ defaultTargetPlatform == TargetPlatform .linux ||
218
+ defaultTargetPlatform == TargetPlatform .windows) {
219
+ final signInArgs = GoogleSignInArgs (
220
+ // The OAuth client id of your Google app.
221
+ clientId:
222
+ '967077780131-g9t7g577rsafrl40ko75k1i5kc435ns6.apps.googleusercontent.com' ,
223
+ // The URI to your redirect web page.
224
+ redirectUri: 'https://notease-redirect.web.app' ,
225
+ // Basic scopes to retrieve the user's email and profile details.
226
+ scope: [
227
+ 'https://www.googleapis.com/auth/userinfo.email' ,
228
+ 'https://www.googleapis.com/auth/userinfo.profile' ,
229
+ ].join (' ' ),
230
+ responseType: 'token id_token' ,
231
+ // Prompts the user for consent and to select an account.
232
+ prompt: 'select_account consent' ,
233
+ // Random secure nonce to be checked after the sign-in flow completes.
234
+ nonce: generateNonce (),
235
+ );
236
+ final authUri = Uri (
237
+ scheme: 'https' ,
238
+ host: 'accounts.google.com' ,
239
+ path: '/o/oauth2/v2/auth' ,
240
+ queryParameters: {
241
+ 'scope' : signInArgs.scope,
242
+ 'response_type' : signInArgs.responseType,
243
+ 'redirect_uri' : signInArgs.redirectUri,
244
+ 'client_id' : signInArgs.clientId,
245
+ 'nonce' : signInArgs.nonce,
246
+ 'prompt' : signInArgs.prompt,
247
+ },
248
+ );
249
+ await launchUrl (authUri);
250
+ throw Exception ("Check your browser to login!" );
251
+ }
252
+ throw UnimplementedError ("Error!" );
253
+ }
254
+
255
+ String generateNonce ({int length = 32 }) {
256
+ const characters =
257
+ '0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._' ;
258
+ final random = Random .secure ();
259
+
260
+ return List .generate (
261
+ length,
262
+ (_) => characters[random.nextInt (characters.length)],
263
+ ).join ();
264
+ }
82
265
}
0 commit comments