File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change 1616 the Android process has started. This theme is visible to the user
1717 while the Flutter UI initializes. After that, this theme continues
1818 to determine the Window background behind the Flutter UI. -->
19+ <intent-filter >
20+ <action android : name =" android.intent.action.VIEW" />
21+ <category android : name =" android.intent.category.DEFAULT" />
22+ <category android : name =" android.intent.category.BROWSABLE" />
23+ <data
24+ android : scheme =" taskapp"
25+ android : host =" login-callback" />
26+ </intent-filter >
1927 <meta-data
2028 android : name =" io.flutter.embedding.android.NormalTheme"
2129 android : resource =" @style/NormalTheme"
Original file line number Diff line number Diff line change @@ -14,6 +14,8 @@ class AuthLayoutTemplate extends StatelessWidget {
1414 final bool useCard;
1515 final Widget ? customHeaderIcon;
1616 final Widget ? footerContent;
17+ final VoidCallback ? onGoogleTap; // Login with Google
18+ final VoidCallback ? onFacebookTap; // Login with Facebook
1719
1820 const AuthLayoutTemplate ({
1921 super .key,
@@ -27,6 +29,8 @@ class AuthLayoutTemplate extends StatelessWidget {
2729 this .useCard = true ,
2830 this .customHeaderIcon,
2931 this .footerContent,
32+ this .onGoogleTap,
33+ this .onFacebookTap,
3034 });
3135
3236 @override
@@ -200,7 +204,7 @@ class AuthLayoutTemplate extends StatelessWidget {
200204 children: [
201205 Expanded (
202206 child: OutlinedButton .icon (
203- onPressed: () {} ,
207+ onPressed: onGoogleTap ,
204208 icon: const Icon (
205209 Icons .g_mobiledata,
206210 color: Colors .red,
@@ -212,7 +216,7 @@ class AuthLayoutTemplate extends StatelessWidget {
212216 const SizedBox (width: 16 ),
213217 Expanded (
214218 child: OutlinedButton .icon (
215- onPressed: () {} ,
219+ onPressed: onFacebookTap ,
216220 icon: const Icon (Icons .facebook, color: Color (0xFF1877F2 )),
217221 label: const Text ('Facebook' ),
218222 ),
Original file line number Diff line number Diff line change @@ -40,6 +40,30 @@ class AuthHelper {
4040 }
4141 }
4242
43+ Future <bool > loginWithGoogle () async {
44+ try {
45+ return await supabase.auth.signInWithOAuth (
46+ OAuthProvider .google,
47+ redirectTo: 'taskapp://login-callback' , // back to app
48+ );
49+ } on AuthException catch (e) {
50+ print ('Lỗi đăng nhập Google: ${e .message }' );
51+ return false ;
52+ }
53+ }
54+
55+ Future <bool > loginWithFacebook () async {
56+ try {
57+ return await supabase.auth.signInWithOAuth (
58+ OAuthProvider .facebook,
59+ redirectTo: 'taskapp://login-callback' , // back to app
60+ );
61+ } on AuthException catch (e) {
62+ print ('Lỗi đăng nhập Facebook: ${e .message }' );
63+ return false ;
64+ }
65+ }
66+
4367 Future <UserModel ?> register (String email, String password, String username) async {
4468 try {
4569 final timezoneObj = await FlutterTimezone .getLocalTimezone ();
@@ -106,4 +130,14 @@ class AuthHelper {
106130 rethrow ;
107131 }
108132 }
133+ }
134+
135+ // SignOut session
136+ Future <void > signOut () async {
137+ try {
138+ await supabase.auth.signOut ();
139+ } catch (e) {
140+ print ('Lỗi đăng xuất: $e ' );
141+ rethrow ;
142+ }
109143}
Original file line number Diff line number Diff line change 1+ import 'package:flutter/material.dart' ;
2+ import 'package:supabase_flutter/supabase_flutter.dart' ;
3+ import 'login_view.dart' ;
4+ import '../../../main/view/screens/main_screen.dart' ;
5+
6+ class AuthGate extends StatelessWidget {
7+ const AuthGate ({super .key});
8+
9+ @override
10+ Widget build (BuildContext context) {
11+ // StreamBuilder continously checks the auth state
12+ return StreamBuilder <AuthState >(
13+ stream: Supabase .instance.client.auth.onAuthStateChange,
14+ builder: (context, snapshot) {
15+ // Wait response from Supabase
16+ if (snapshot.connectionState == ConnectionState .waiting) {
17+ return const Scaffold (
18+ body: Center (child: CircularProgressIndicator ()),
19+ );
20+ }
21+
22+ // Check if there is an active session ( user logged in )
23+ final session = snapshot.data? .session;
24+
25+ // if session exists -> Navigate to MainScreen
26+ if (session != null ) {
27+ return const MainScreen ();
28+ }
29+
30+ // if session not exists -> Navigate to LoginView
31+ return const LoginView ();
32+ },
33+ );
34+ }
35+ }
Original file line number Diff line number Diff line change @@ -25,6 +25,18 @@ class _LoginViewState extends State<LoginView> {
2525 submitText: 'Đăng nhập' ,
2626 isLoading: _vm.isLoading,
2727 showSocial: true ,
28+ onGoogleTap: () async {
29+ final error = await _vm.loginWithGoogle ();
30+ if (error != null && context.mounted) {
31+ ScaffoldMessenger .of (context).showSnackBar (SnackBar (content: Text (error), backgroundColor: AppColors .error));
32+ }
33+ },
34+ onFacebookTap: () async {
35+ final error = await _vm.loginWithFacebook ();
36+ if (error != null && context.mounted) {
37+ ScaffoldMessenger .of (context).showSnackBar (SnackBar (content: Text (error), backgroundColor: AppColors .error));
38+ }
39+ },
2840 onSubmit: () async {
2941 FocusScope .of (context).unfocus ();
3042 final errorMessage = await _vm.login ();
Original file line number Diff line number Diff line change @@ -95,8 +95,37 @@ class LoginViewModel extends BaseViewModel {
9595 setLoading (false );
9696 }
9797 }
98+ // 1.1 LOGIN WITH GOOGLE
99+ Future <String ?> loginWithGoogle () async {
100+ setLoading (true );
101+ try {
102+ final success = await _authHelper.loginWithGoogle ();
103+ if (success) return null ; // Thành công (thường Supabase sẽ tự văng ra web browser)
104+ return 'Lỗi khi mở cổng đăng nhập Google!' ;
105+ } catch (e) {
106+ return handleError (e);
107+ } finally {
108+ setLoading (false );
109+ }
110+ }
111+
112+ // 1.2 LOGIN WITH FACEBOOK
113+
114+ Future <String ?> loginWithFacebook () async {
115+ setLoading (true );
116+ try {
117+ final success = await _authHelper.loginWithFacebook ();
118+ if (success) return null ;
119+ return 'Lỗi khi mở cổng đăng nhập Facebook!' ;
120+ } catch (e) {
121+ return handleError (e);
122+ } finally {
123+ setLoading (false );
124+ }
125+ }
98126}
99127
128+
100129// ==========================================
101130// 2. REGISTER VIEWMODEL
102131// ==========================================
Original file line number Diff line number Diff line change @@ -2,9 +2,11 @@ import 'package:flutter/material.dart';
22import 'package:flutter/services.dart' ;
33import 'core/theme/app_colors.dart' ;
44import 'features/auth/presentation/view/login_view.dart' ;
5+ import 'features/auth/presentation/view/auth_gate.dart' ;
56import 'package:flutter_dotenv/flutter_dotenv.dart' ;
67import 'package:supabase_flutter/supabase_flutter.dart' ;
78
9+
810Future <void > main () async {
911 WidgetsFlutterBinding .ensureInitialized ();
1012 await dotenv.load (fileName: ".env" );
@@ -42,7 +44,7 @@ class TaskApp extends StatelessWidget {
4244 labelLarge: TextStyle (fontSize: 16 , color: AppColors .primaryBlue),
4345 ),
4446 ),
45- home: const LoginView (),
47+ home: const AuthGate (),
4648 debugShowCheckedModeBanner: false ,
4749 );
4850 }
You can’t perform that action at this time.
0 commit comments