From a2e22d2f41bcbf3843ddb4d22ef780be6ccaef68 Mon Sep 17 00:00:00 2001 From: SharkyBytes Date: Wed, 19 Mar 2025 15:00:42 +0530 Subject: [PATCH 1/4] feat(auth): Integrated Google OAuth with Supabase, dual-app routing, session management & CORS config --- patient/lib/main.dart | 3 +- .../lib/presentation/auth/auth_screen.dart | 127 +++++++-- patient/lib/presentation/splash_screen.dart | 18 +- patient/test/widget_test.dart | 61 +++-- supabase/package-lock.json | 6 + therapist/assets/google_logo.png | Bin 0 -> 20607 bytes therapist/assets/illustration.png | Bin 0 -> 12782 bytes therapist/assets/illustration1.png | Bin 0 -> 11885 bytes therapist/assets/illustration2.png | Bin 0 -> 17351 bytes therapist/assets/logo.png | Bin 0 -> 5961 bytes therapist/assets/meditation.png | Bin 0 -> 11885 bytes therapist/lib/main.dart | 59 ++-- .../lib/presentation/auth/auth_screen.dart | 254 +++++++++++++++++ .../auth/personal_details_screen.dart | 8 +- therapist/lib/presentation/splash_screen.dart | 53 ++++ .../widgets/google_signin_button.dart | 50 ++++ .../presentation/widgets/welcome_header.dart | 42 +++ therapist/lib/provider/auth_provider.dart | 19 +- .../Flutter/GeneratedPluginRegistrant.swift | 2 + therapist/pubspec.lock | 258 +++++++++++++++--- therapist/pubspec.yaml | 7 + therapist/test/widget_test.dart | 60 ++-- 22 files changed, 876 insertions(+), 151 deletions(-) create mode 100644 supabase/package-lock.json create mode 100644 therapist/assets/google_logo.png create mode 100644 therapist/assets/illustration.png create mode 100644 therapist/assets/illustration1.png create mode 100644 therapist/assets/illustration2.png create mode 100644 therapist/assets/logo.png create mode 100644 therapist/assets/meditation.png create mode 100644 therapist/lib/presentation/splash_screen.dart create mode 100644 therapist/lib/presentation/widgets/google_signin_button.dart create mode 100644 therapist/lib/presentation/widgets/welcome_header.dart diff --git a/patient/lib/main.dart b/patient/lib/main.dart index 7ec110d..847aa81 100644 --- a/patient/lib/main.dart +++ b/patient/lib/main.dart @@ -4,6 +4,7 @@ import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:patient/core/theme/theme.dart'; import 'package:patient/presentation/splash_screen.dart'; import 'package:patient/provider/assessment_provider.dart'; +import 'package:patient/provider/auth_provider.dart'; import 'package:provider/provider.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; @@ -28,11 +29,11 @@ Future main() async { MultiProvider( providers: [ ChangeNotifierProvider(create: (_) => AssessmentProvider()), + ChangeNotifierProvider(create: (_) => AuthProvider()), ], child: const MyApp(), ), ); - } class MyApp extends StatelessWidget { diff --git a/patient/lib/presentation/auth/auth_screen.dart b/patient/lib/presentation/auth/auth_screen.dart index fef3d34..7e2df48 100644 --- a/patient/lib/presentation/auth/auth_screen.dart +++ b/patient/lib/presentation/auth/auth_screen.dart @@ -1,8 +1,12 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; -import '../widgets/google_signin_button.dart'; +import 'package:google_sign_in/google_sign_in.dart'; import '../widgets/welcome_header.dart'; +import 'package:supabase_flutter/supabase_flutter.dart'; +import 'package:flutter/foundation.dart' show kIsWeb; +import 'package:patient/presentation/auth/personal_details_screen.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; class AuthScreen extends StatefulWidget { const AuthScreen({super.key}); @@ -15,6 +19,7 @@ class _AuthScreenState extends State { final PageController _pageController = PageController(initialPage: 0); int _currentPage = 0; late Timer _timer; + final supabase = Supabase.instance.client; final List _contents = [ OnboardingContent( @@ -24,8 +29,8 @@ class _AuthScreenState extends State { ), OnboardingContent( image: 'assets/illustration1.png', - title: 'Therapy Goals ', - description: 'Personalized Daily Activities, Tracked Effortlessly!', + title: 'Therapy Goals', + description: 'Set and achieve your therapy goals with ease!', ), OnboardingContent( image: 'assets/illustration2.png', @@ -38,6 +43,33 @@ class _AuthScreenState extends State { void initState() { super.initState(); _startAutoScroll(); + supabase.auth.onAuthStateChange.listen((data) { + final session = supabase.auth.currentSession; + if (session != null && mounted) { + final fullName = session.user.userMetadata?['full_name']; + final email = session.user.email ?? 'Unknown User'; + + print(fullName); + print(email); + debugPrint("User authenticated, navigating to PersonalDetailsScreen"); + debugPrint("User authenticated, Helllooooo"); + + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Signed in as ${fullName ?? email}'), + duration: const Duration(seconds: 2), + ), + ); + + + Navigator.of(context).pushReplacement( + MaterialPageRoute( + builder: (context) => const PersonalDetailsScreen(), + ), + ); + } + }); + } void _startAutoScroll() { @@ -45,7 +77,7 @@ class _AuthScreenState extends State { if (_currentPage < _contents.length - 1) { _currentPage++; } else { - _currentPage = 0; // Reset to first page when reaching the end + _currentPage = 0; } _pageController.animateToPage( _currentPage, @@ -55,6 +87,54 @@ class _AuthScreenState extends State { }); } + Future _handleGoogleSignIn() async { + try { + final supabaseUrl = dotenv.env['SUPABASE_URL']; + await supabase.auth.signInWithOAuth( + OAuthProvider.google, + redirectTo: kIsWeb + ? "$supabaseUrl/auth/v1/callback" + : 'com.mycompany.cbtdiary://login-callback/', + authScreenLaunchMode: kIsWeb + ? LaunchMode.platformDefault + : LaunchMode.externalApplication, + ); + + // Fetch the session immediately after signing in + final session = await supabase.auth.currentSession; + debugPrint("User authenticated, navigating to PersonalDetailsScreen"); + print(session); + + if (session != null && mounted) { + // Extract user name from Google OAuth metadata + final fullName = session.user.userMetadata?['full_name']; + final email = session.user.email ?? 'Unknown User'; + + print(fullName); + print(email); + // Show toast with user's name + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Signed in as ${fullName ?? email}'), + duration: const Duration(seconds: 2), + ), + ); + + Navigator.of(context).pushReplacement( + MaterialPageRoute( + builder: (context) => const PersonalDetailsScreen(), + ), + ); + } + } catch (error) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text('Sign in failed: $error'), + )); + } + } + } + @override void dispose() { _pageController.dispose(); @@ -67,14 +147,10 @@ class _AuthScreenState extends State { return Scaffold( body: Column( children: [ - // Custom Welcome Header const WelcomeHeader(), - - // Carousel and bottom content Expanded( child: Stack( children: [ - // PageView for carousel PageView.builder( controller: _pageController, itemCount: _contents.length, @@ -87,8 +163,6 @@ class _AuthScreenState extends State { return _buildCarouselItem(_contents[index]); }, ), - - // Pagination dots Positioned( bottom: 120, left: 0, @@ -101,13 +175,34 @@ class _AuthScreenState extends State { ), ), ), - - // Google Sign-in Button - const Positioned( + Positioned( bottom: 40, left: 0, right: 0, - child: GoogleSignInButton(), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: ElevatedButton( + // onPressed: () async{ + + // await supabase.auth. + // signInWithOAuth( + // OAuthProvider.google, + // // redirectTo: kIsWeb ? null : 'com.mycompany.cbtdiary://login-callback/', + // // authScreenLaunchMode: kIsWeb ? LaunchMode.platformDefault : LaunchMode.externalApplication, + // ); + + // }, + onPressed: _handleGoogleSignIn, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset('assets/google_logo.png', height: 24), + const SizedBox(width: 10), + const Text('Sign in with Google'), + ], + ), + ), + ), ), ], ), @@ -121,7 +216,6 @@ class _AuthScreenState extends State { return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - // Illustration Image.asset(content.image, height: 200), const SizedBox(height: 35), Text( @@ -145,7 +239,7 @@ class _AuthScreenState extends State { ), ), ), - const SizedBox(height: 60), // Space for dots and button + const SizedBox(height: 60), ], ); } @@ -164,7 +258,6 @@ class _AuthScreenState extends State { } } -// Model class for onboarding content class OnboardingContent { final String image; final String title; diff --git a/patient/lib/presentation/splash_screen.dart b/patient/lib/presentation/splash_screen.dart index d078307..3c537b7 100644 --- a/patient/lib/presentation/splash_screen.dart +++ b/patient/lib/presentation/splash_screen.dart @@ -14,14 +14,16 @@ class _SplashScreenState extends State { @override void initState() { super.initState(); - Future.delayed(const Duration(seconds: 3), () { - if (mounted) { - Navigator.pushReplacement( - context, - MaterialPageRoute(builder: (context) => const AuthScreen()), - ); - } - }); + _navigateToAuthScreen(); + } + + Future _navigateToAuthScreen() async { + await Future.delayed(const Duration(seconds: 3)); + if (mounted) { + Navigator.of(context).pushReplacement( + MaterialPageRoute(builder: (_) => const AuthScreen()), + ); + } } @override diff --git a/patient/test/widget_test.dart b/patient/test/widget_test.dart index 13f9aa4..5be4a43 100644 --- a/patient/test/widget_test.dart +++ b/patient/test/widget_test.dart @@ -1,30 +1,31 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility in the flutter_test package. For example, you can send tap and scroll -// gestures. You can also use WidgetTester to find child widgets in the widget -// tree, read text, and verify that the values of widget properties are correct. - -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import 'package:patient/main.dart'; - -void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(const MyApp()); - - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); - - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); - await tester.pump(); - - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); - }); -} +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:patient/main.dart'; + + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +} diff --git a/supabase/package-lock.json b/supabase/package-lock.json new file mode 100644 index 0000000..1e4413f --- /dev/null +++ b/supabase/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "supabase", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/therapist/assets/google_logo.png b/therapist/assets/google_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..d9d363556ea9396d9c1bc6afc21ac08c921498ed GIT binary patch literal 20607 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 A#sB~S literal 0 HcmV?d00001 diff --git a/therapist/assets/illustration1.png b/therapist/assets/illustration1.png new file mode 100644 index 0000000000000000000000000000000000000000..a716a70847d7f91c0ff8148a5238d4ede5daae68 GIT binary patch literal 11885 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 S#|CbPgJh+YBrC)}2mL=8hs~`3 literal 0 HcmV?d00001 diff --git a/therapist/assets/illustration2.png b/therapist/assets/illustration2.png new file mode 100644 index 0000000000000000000000000000000000000000..3f8596401cc2118013ee66129849bb67f722c678 GIT binary patch literal 17351 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 U$r9FUHUIzs07*qoM6N<$g15pOF8}}l literal 0 HcmV?d00001 diff --git a/therapist/assets/logo.png b/therapist/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..c17327987941ca530125056dc70fe7271eca4eaf GIT binary patch literal 5961 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 literal 0 HcmV?d00001 diff --git a/therapist/assets/meditation.png b/therapist/assets/meditation.png new file mode 100644 index 0000000000000000000000000000000000000000..a716a70847d7f91c0ff8148a5238d4ede5daae68 GIT binary patch literal 11885 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 S#|CbPgJh+YBrC)}2mL=8hs~`3 literal 0 HcmV?d00001 diff --git a/therapist/lib/main.dart b/therapist/lib/main.dart index 2b7cbd3..a0165fa 100644 --- a/therapist/lib/main.dart +++ b/therapist/lib/main.dart @@ -1,31 +1,54 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:provider/provider.dart'; -import 'package:therapist/core/theme/theme.dart'; -import 'provider/home_provider.dart'; -import 'provider/therapist_provider.dart'; -import 'presentation/home/home_screen.dart'; +import 'package:supabase_flutter/supabase_flutter.dart'; +import 'package:therapist/presentation/splash_screen.dart'; -void main() { - runApp(const MyApp()); +import './presentation/auth/auth_screen.dart'; +import './provider/auth_provider.dart'; +import './provider/home_provider.dart'; +import './provider/therapist_provider.dart'; + +Future main() async { + WidgetsFlutterBinding.ensureInitialized(); + await dotenv.load(fileName: ".env"); + await Supabase.initialize( + url: dotenv.env['SUPABASE_URL']!, + anonKey: dotenv.env['SUPABASE_ANON_KEY']!, + ); + + SystemChrome.setSystemUIOverlayStyle( + const SystemUiOverlayStyle( + statusBarColor: Colors.white, + statusBarIconBrightness: Brightness.dark, + systemNavigationBarColor: Colors.white, + systemNavigationBarIconBrightness: Brightness.dark, + ), + ); + + runApp( + MultiProvider( + providers: [ + ChangeNotifierProvider(create: (context) => AuthProvider()), + ChangeNotifierProvider(create: (context) => HomeProvider()), + ChangeNotifierProvider(create: (context) => TherapistDataProvider()) + ], + child: const MyApp(), + ), + ); } class MyApp extends StatelessWidget { const MyApp({super.key}); - // This widget is the root of your application. @override Widget build(BuildContext context) { - return MultiProvider( - providers: [ - ChangeNotifierProvider(create: (_) => HomeProvider()), - ChangeNotifierProvider(create: (_) => TherapistDataProvider()), - ], - child: MaterialApp( - title: 'Therapist App', - debugShowCheckedModeBanner: false, - theme: AppTheme.lightTheme(), - home: const HomeScreen(), - ), + return MaterialApp( + debugShowCheckedModeBanner: false, + title: 'Therapist App', + theme: ThemeData.light(), + home: const SplashScreen(), ); } } diff --git a/therapist/lib/presentation/auth/auth_screen.dart b/therapist/lib/presentation/auth/auth_screen.dart index e69de29..dd0633a 100644 --- a/therapist/lib/presentation/auth/auth_screen.dart +++ b/therapist/lib/presentation/auth/auth_screen.dart @@ -0,0 +1,254 @@ +import 'package:flutter/material.dart'; +import 'dart:async'; +import 'package:smooth_page_indicator/smooth_page_indicator.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:supabase_flutter/supabase_flutter.dart'; +import 'package:flutter/foundation.dart' show kIsWeb; +import '../home/home_screen.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; + +class AuthScreen extends StatefulWidget { + const AuthScreen({super.key}); + + @override + State createState() => _AuthScreenState(); +} + +class _AuthScreenState extends State { + final PageController _pageController = PageController(); + int _currentPage = 0; + Timer? _timer; + final supabase = Supabase.instance.client; + + + void _startAutoScroll() { + _timer = Timer.periodic(const Duration(seconds: 3), (Timer timer) { + if (_currentPage < 2) { + _currentPage++; + } else { + _currentPage = 0; + } + _pageController.animateToPage( + _currentPage, + duration: const Duration(milliseconds: 500), + curve: Curves.easeInOut, + ); + }); + } + + @override + void initState() { + super.initState(); + _startAutoScroll(); + supabase.auth.onAuthStateChange.listen((data) { + final session = supabase.auth.currentSession; + if (session != null && mounted) { + final fullName = session.user.userMetadata?['full_name']; + final email = session.user.email ?? 'Unknown User'; + + print(fullName); + print(email); + debugPrint("User authenticated, navigating to PersonalDetailsScreen"); + debugPrint("User authenticated, Helllooooo"); + + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Signed in as ${fullName ?? email}'), + duration: const Duration(seconds: 2), + ), + ); + + + Navigator.of(context).pushReplacement( + MaterialPageRoute( + builder: (context) => const HomeScreen(), + ), + ); + } + }); + + } + + + Future _handleGoogleSignIn() async { + try { + final supabaseUrl = dotenv.env['SUPABASE_URL']; + await supabase.auth.signInWithOAuth( + OAuthProvider.google, + redirectTo: kIsWeb + ? "$supabaseUrl/auth/v1/callback" + : 'com.mycompany.cbtdiary://login-callback/', + authScreenLaunchMode: + kIsWeb ? LaunchMode.platformDefault : LaunchMode.externalApplication, + ); + + final session = supabase.auth.currentSession; + debugPrint("User authenticated, navigating to HomeScreen"); + print(session); + + if (session != null && mounted) { + Navigator.of(context).pushReplacement( + MaterialPageRoute(builder: (context) => const HomeScreen()), + ); + } + } catch (error) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Sign in failed: $error')), + ); + } + } + } + + @override + void dispose() { + _timer?.cancel(); + _pageController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + double screenWidth = MediaQuery.of(context).size.width; + double screenHeight = MediaQuery.of(context).size.height; + + return Scaffold( + body: Column( + children: [ + Stack( + children: [ + Container( + width: double.infinity, + height: screenHeight * 0.2, + decoration: const BoxDecoration( + color: Color(0xFFB066E4), + borderRadius: BorderRadius.only( + bottomRight: Radius.circular(200), + ), + ), + ), + Positioned( + top: screenHeight * 0.07, + left: screenWidth * 0.08, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Welcome To,", + style: GoogleFonts.poppins( + fontSize: 16, + color: Colors.white, + ), + ), + const SizedBox(height: 2), + Text( + "Therapy App", + style: GoogleFonts.poppins( + fontSize: 26, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + ], + ), + ), + ], + ), + + // Carousel Section + Expanded( + child: PageView( + controller: _pageController, + children: [ + _buildPage( + title: "Welcome To", + appName: "Therapy App", + description: + "Effortlessly manage patients & enhance therapy progress.", + image: "assets/manage_patients.png", + ), + _buildPage( + title: "Daily Activities", + appName: "Stay on Track", + description: + "Personalized Daily Activities, Tracked Effortlessly!", + image: "assets/daily_activities.png", + ), + _buildPage( + title: "Health Tracking", + appName: "Monitor Progress", + description: + "Track your health and therapy goals effectively.", + image: "assets/health_tracking.png", + ), + ], + ), + ), + + // Page Indicator + SmoothPageIndicator( + controller: _pageController, + count: 3, + effect: const WormEffect( + dotHeight: 8, + dotWidth: 8, + activeDotColor: Colors.purple, + ), + ), + const SizedBox(height: 20), + + // Google Sign-In Button + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Colors.white, + padding: + const EdgeInsets.symmetric(vertical: 12, horizontal: 24), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(30), + ), + ), + onPressed: _handleGoogleSignIn, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset("assets/google_logo.png", height: 24), + const SizedBox(width: 10), + const Text( + "Continue with Google", + style: TextStyle(fontSize: 16, color: Colors.black), + ), + ], + ), + ), + ), + const SizedBox(height: 20), + ], + ), + ); + } + + Widget _buildPage({ + required String title, + required String appName, + required String description, + required String image, + }) { + return Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset(image, height: 250), + const SizedBox(height: 20), + Text( + description, + textAlign: TextAlign.center, + style: const TextStyle(fontSize: 16, color: Colors.black54), + ), + ], + ), + ); + } +} diff --git a/therapist/lib/presentation/auth/personal_details_screen.dart b/therapist/lib/presentation/auth/personal_details_screen.dart index 262ae8a..20b358b 100644 --- a/therapist/lib/presentation/auth/personal_details_screen.dart +++ b/therapist/lib/presentation/auth/personal_details_screen.dart @@ -116,12 +116,14 @@ class _PersonalDetailsScreenState extends State { const SizedBox(height: 20), _buildDropDown( headerText: ' Specialization', - dropdownItems: therapistDataProvider.specializationDropdownItems, + dropdownItems: + therapistDataProvider.specializationDropdownItems, ), const SizedBox(height: 20), _buildDropDown( headerText: ' Regulatory Body', - dropdownItems: therapistDataProvider.regulatoryBodyDropdownItems, + dropdownItems: + therapistDataProvider.regulatoryBodyDropdownItems, ), const SizedBox(height: 20), _buildTextField( @@ -227,7 +229,7 @@ class _PersonalDetailsScreenState extends State { color: Colors.grey.shade600, ), expandedInsets: const EdgeInsets.all(0), - dropdownMenuEntries: dropdownItems, + dropdownMenuEntries: dropdownItems, ), ], ); diff --git a/therapist/lib/presentation/splash_screen.dart b/therapist/lib/presentation/splash_screen.dart new file mode 100644 index 0000000..af27724 --- /dev/null +++ b/therapist/lib/presentation/splash_screen.dart @@ -0,0 +1,53 @@ +import 'dart:async'; +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:therapist/presentation/auth/auth_screen.dart'; + +class SplashScreen extends StatefulWidget { + const SplashScreen({super.key}); + + @override + State createState() => _SplashScreenState(); +} + +class _SplashScreenState extends State { + @override + + void initState() { + super.initState(); + _navigateToAuthScreen(); + } + + Future _navigateToAuthScreen() async { + await Future.delayed(const Duration(seconds: 3)); + if (mounted) { + Navigator.of(context).pushReplacement( + MaterialPageRoute(builder: (_) => AuthScreen()), + ); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset('assets/logo.png', width: 100), + const SizedBox(height: 20), + Text( + "Neurotrack", + style: GoogleFonts.poppins( + fontSize: 24, + fontWeight: FontWeight.bold, + color: Colors.blueAccent, + ), + ), + ], + ), + ), + ); + } +} diff --git a/therapist/lib/presentation/widgets/google_signin_button.dart b/therapist/lib/presentation/widgets/google_signin_button.dart new file mode 100644 index 0000000..0ce12c0 --- /dev/null +++ b/therapist/lib/presentation/widgets/google_signin_button.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; + +class GoogleSignInButton extends StatelessWidget { + const GoogleSignInButton({super.key}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: + const EdgeInsets.symmetric(horizontal: 20), // Add horizontal padding + child: SizedBox( + width: double.infinity, // Makes the button expand to full width + height: 50, // Adjusted height for better appearance + child: ElevatedButton( + onPressed: () { + // Implement Google Sign-in logic here + }, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(30), + ), + elevation: 2, // Small shadow effect + padding: const EdgeInsets.symmetric(vertical: 12), + ), + child: Row( + mainAxisSize: MainAxisSize.min, // Prevent unnecessary stretching + mainAxisAlignment: MainAxisAlignment.center, // Center elements + children: [ + Image.asset( + 'assets/google_logo.png', // Ensure the correct path + height: 24, + width: 24, + ), + const SizedBox(width: 10), // Space between icon and text + const Text( + 'Continue with Google', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Colors.black87, + ), + ), + ], + ), + ), + ), + ); + } +} \ No newline at end of file diff --git a/therapist/lib/presentation/widgets/welcome_header.dart b/therapist/lib/presentation/widgets/welcome_header.dart new file mode 100644 index 0000000..586d250 --- /dev/null +++ b/therapist/lib/presentation/widgets/welcome_header.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; + +class WelcomeHeader extends StatelessWidget { + const WelcomeHeader({super.key}); + + @override + Widget build(BuildContext context) { + return Container( + width: double.infinity, + decoration: const BoxDecoration( + color: Color(0xFF6A79F7), // Blue background color + borderRadius: BorderRadius.only( + bottomRight: Radius.circular(100), // Adjust this value as needed + ), + ), + padding: const EdgeInsets.only(left: 20, top: 60, bottom: 30), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Welcome To,', + style: GoogleFonts.poppins( + fontSize: 18, + fontWeight: FontWeight.w500, + color: Colors.white, + ), + ), + const SizedBox(height: 8), + Text( + 'Patient App', + style: GoogleFonts.poppins( + fontSize: 26, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + ], + ), + ); + } +} diff --git a/therapist/lib/provider/auth_provider.dart b/therapist/lib/provider/auth_provider.dart index 7a3221e..848c6b9 100644 --- a/therapist/lib/provider/auth_provider.dart +++ b/therapist/lib/provider/auth_provider.dart @@ -1,7 +1,12 @@ -import 'package:flutter/material.dart'; - -class AuthProvider extends ChangeNotifier { - // The provider will act like a combination of service and controller. - // It will make use of the repository to perform the actual authentication and then notify the listeners of the state of the authentication process. - // AuthProvider({ required this.authRepository }); -} +import 'package:flutter/material.dart'; + +class AuthProvider extends ChangeNotifier { + bool _isAuthenticated = false; + + bool get isAuthenticated => _isAuthenticated; + + void login() { + _isAuthenticated = true; + notifyListeners(); + } +} diff --git a/therapist/macos/Flutter/GeneratedPluginRegistrant.swift b/therapist/macos/Flutter/GeneratedPluginRegistrant.swift index 92b6497..2a889f0 100644 --- a/therapist/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/therapist/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,12 +6,14 @@ import FlutterMacOS import Foundation import app_links +import google_sign_in_ios import path_provider_foundation import shared_preferences_foundation import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin")) + FLTGoogleSignInPlugin.register(with: registry.registrar(forPlugin: "FLTGoogleSignInPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) diff --git a/therapist/pubspec.lock b/therapist/pubspec.lock index c5ec234..687a98e 100644 --- a/therapist/pubspec.lock +++ b/therapist/pubspec.lock @@ -57,6 +57,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.4" + archive: + dependency: transitive + description: + name: archive + sha256: "0c64e928dcbefddecd234205422bcfc2b5e6d31be0b86fef0d0dd48d7b4c9742" + url: "https://pub.dev" + source: hosted + version: "4.0.4" args: dependency: transitive description: @@ -69,18 +77,18 @@ packages: dependency: transitive description: name: async - sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 url: "https://pub.dev" source: hosted - version: "2.11.0" + version: "2.12.0" boolean_selector: dependency: transitive description: name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" build: dependency: transitive description: @@ -149,10 +157,10 @@ packages: dependency: transitive description: name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.0" checked_yaml: dependency: transitive description: @@ -165,10 +173,10 @@ packages: dependency: transitive description: name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" code_builder: dependency: transitive description: @@ -181,10 +189,10 @@ packages: dependency: transitive description: name: collection - sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" url: "https://pub.dev" source: hosted - version: "1.19.0" + version: "1.19.1" convert: dependency: transitive description: @@ -201,6 +209,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.6" + csslib: + dependency: transitive + description: + name: csslib + sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" + url: "https://pub.dev" + source: hosted + version: "1.0.2" cupertino_icons: dependency: "direct main" description: @@ -237,10 +253,10 @@ packages: dependency: transitive description: name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.2" ffi: dependency: transitive description: @@ -270,6 +286,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_dotenv: + dependency: "direct dev" + description: + name: flutter_dotenv + sha256: b7c7be5cd9f6ef7a78429cabd2774d3c4af50e79cb2b7593e3d5d763ef95c61b + url: "https://pub.dev" + source: hosted + version: "5.2.1" flutter_lints: dependency: "direct dev" description: @@ -278,6 +302,22 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.2" + flutter_native_splash: + dependency: "direct main" + description: + name: flutter_native_splash + sha256: edb09c35ee9230c4b03f13dd45bb3a276d0801865f0a4650b7e2a3bba61a803a + url: "https://pub.dev" + source: hosted + version: "2.4.5" + flutter_svg: + dependency: "direct main" + description: + name: flutter_svg + sha256: c200fd79c918a40c5cd50ea0877fa13f81bdaf6f0a5d3dbcc2a13e3285d6aa1b + url: "https://pub.dev" + source: hosted + version: "2.0.17" flutter_test: dependency: "direct dev" description: flutter @@ -312,6 +352,62 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.3" + google_fonts: + dependency: "direct main" + description: + name: google_fonts + sha256: b1ac0fe2832c9cc95e5e88b57d627c5e68c223b9657f4b96e1487aa9098c7b82 + url: "https://pub.dev" + source: hosted + version: "6.2.1" + google_identity_services_web: + dependency: transitive + description: + name: google_identity_services_web + sha256: "55580f436822d64c8ff9a77e37d61f5fb1e6c7ec9d632a43ee324e2a05c3c6c9" + url: "https://pub.dev" + source: hosted + version: "0.3.3" + google_sign_in: + dependency: "direct main" + description: + name: google_sign_in + sha256: d0a2c3bcb06e607bb11e4daca48bd4b6120f0bbc4015ccebbe757d24ea60ed2a + url: "https://pub.dev" + source: hosted + version: "6.3.0" + google_sign_in_android: + dependency: transitive + description: + name: google_sign_in_android + sha256: "4e52c64366bdb3fe758f683b088ee514cc7a95e69c52b5ee9fc5919e1683d21b" + url: "https://pub.dev" + source: hosted + version: "6.2.0" + google_sign_in_ios: + dependency: transitive + description: + name: google_sign_in_ios + sha256: "29cd125f58f50ceb40e8253d3c0209e321eee3e5df16cd6d262495f7cad6a2bd" + url: "https://pub.dev" + source: hosted + version: "5.8.1" + google_sign_in_platform_interface: + dependency: transitive + description: + name: google_sign_in_platform_interface + sha256: "5f6f79cf139c197261adb6ac024577518ae48fdff8e53205c5373b5f6430a8aa" + url: "https://pub.dev" + source: hosted + version: "2.5.0" + google_sign_in_web: + dependency: transitive + description: + name: google_sign_in_web + sha256: "460547beb4962b7623ac0fb8122d6b8268c951cf0b646dd150d60498430e4ded" + url: "https://pub.dev" + source: hosted + version: "0.12.4+4" gotrue: dependency: transitive description: @@ -336,6 +432,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.0" + html: + dependency: transitive + description: + name: html + sha256: "1fc58edeaec4307368c60d59b7e15b9d658b57d7f3125098b6294153c75337ec" + url: "https://pub.dev" + source: hosted + version: "0.15.5" http: dependency: transitive description: @@ -360,6 +464,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.2" + image: + dependency: transitive + description: + name: image + sha256: "13d3349ace88f12f4a0d175eb5c12dcdd39d35c4c109a8a13dfeb6d0bd9e31c3" + url: "https://pub.dev" + source: hosted + version: "4.5.3" io: dependency: transitive description: @@ -396,18 +508,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" + sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec url: "https://pub.dev" source: hosted - version: "10.0.7" + version: "10.0.8" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 url: "https://pub.dev" source: hosted - version: "3.0.8" + version: "3.0.9" leak_tracker_testing: dependency: transitive description: @@ -436,10 +548,10 @@ packages: dependency: transitive description: name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 url: "https://pub.dev" source: hosted - version: "0.12.16+1" + version: "0.12.17" material_color_utilities: dependency: transitive description: @@ -452,10 +564,10 @@ packages: dependency: transitive description: name: meta - sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.15.0" + version: "1.16.0" mime: dependency: transitive description: @@ -484,10 +596,18 @@ packages: dependency: transitive description: name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.9.1" + path_parsing: + dependency: transitive + description: + name: path_parsing + sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" + url: "https://pub.dev" + source: hosted + version: "1.1.0" path_provider: dependency: transitive description: @@ -536,6 +656,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.0" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646" + url: "https://pub.dev" + source: hosted + version: "6.1.0" platform: dependency: transitive description: @@ -560,6 +688,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.1" + posix: + dependency: transitive + description: + name: posix + sha256: a0117dc2167805aa9125b82eee515cc891819bac2f538c83646d355b16f58b9a + url: "https://pub.dev" + source: hosted + version: "6.0.1" postgrest: dependency: transitive description: @@ -693,6 +829,14 @@ packages: description: flutter source: sdk version: "0.0.0" + smooth_page_indicator: + dependency: "direct main" + description: + name: smooth_page_indicator + sha256: b21ebb8bc39cf72d11c7cfd809162a48c3800668ced1c9da3aade13a32cf6c1c + url: "https://pub.dev" + source: hosted + version: "1.2.1" source_gen: dependency: transitive description: @@ -705,18 +849,18 @@ packages: dependency: transitive description: name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.10.1" stack_trace: dependency: transitive description: name: stack_trace - sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.12.1" storage_client: dependency: transitive description: @@ -729,10 +873,10 @@ packages: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" stream_transform: dependency: transitive description: @@ -745,10 +889,10 @@ packages: dependency: transitive description: name: string_scanner - sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.1" supabase: dependency: transitive description: @@ -769,18 +913,18 @@ packages: dependency: transitive description: name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" test_api: dependency: transitive description: name: test_api - sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd url: "https://pub.dev" source: hosted - version: "0.7.3" + version: "0.7.4" timing: dependency: transitive description: @@ -805,6 +949,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + universal_io: + dependency: transitive + description: + name: universal_io + sha256: "1722b2dcc462b4b2f3ee7d188dad008b6eb4c40bbd03a3de451d82c78bba9aad" + url: "https://pub.dev" + source: hosted + version: "2.2.2" url_launcher: dependency: transitive description: @@ -869,6 +1021,30 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.4" + vector_graphics: + dependency: transitive + description: + name: vector_graphics + sha256: "44cc7104ff32563122a929e4620cf3efd584194eec6d1d913eb5ba593dbcf6de" + url: "https://pub.dev" + source: hosted + version: "1.1.18" + vector_graphics_codec: + dependency: transitive + description: + name: vector_graphics_codec + sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146" + url: "https://pub.dev" + source: hosted + version: "1.1.13" + vector_graphics_compiler: + dependency: transitive + description: + name: vector_graphics_compiler + sha256: "1b4b9e706a10294258727674a340ae0d6e64a7231980f9f9a3d12e4b42407aad" + url: "https://pub.dev" + source: hosted + version: "1.1.16" vector_math: dependency: transitive description: @@ -881,10 +1057,10 @@ packages: dependency: transitive description: name: vm_service - sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b + sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" url: "https://pub.dev" source: hosted - version: "14.3.0" + version: "14.3.1" watcher: dependency: transitive description: @@ -925,6 +1101,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + xml: + dependency: transitive + description: + name: xml + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 + url: "https://pub.dev" + source: hosted + version: "6.5.0" yaml: dependency: transitive description: @@ -942,5 +1126,5 @@ packages: source: hosted version: "2.0.3" sdks: - dart: ">=3.6.0 <4.0.0" + dart: ">=3.7.0-0 <4.0.0" flutter: ">=3.27.0" diff --git a/therapist/pubspec.yaml b/therapist/pubspec.yaml index 0e34efb..742a70d 100644 --- a/therapist/pubspec.yaml +++ b/therapist/pubspec.yaml @@ -39,6 +39,11 @@ dependencies: # Serialisation dart_mappable: ^4.4.0 + google_fonts: ^6.1.0 + flutter_native_splash: ^2.3.6 + google_sign_in: ^6.1.6 + flutter_svg: ^2.0.9 + smooth_page_indicator: ^1.1.0 # The following adds the Cupertino Icons font to your application. @@ -50,6 +55,7 @@ dev_dependencies: sdk: flutter build_runner: ^2.4.15 dart_mappable_builder: ^4.4.0 + flutter_dotenv: ^5.2.1 # The "flutter_lints" package below contains a set of recommended lints to # encourage good coding practices. The lint set provided by the package is @@ -70,6 +76,7 @@ flutter: uses-material-design: true assets: - assets/ + - .env # To add assets to your application, add an assets section, like this: # assets: diff --git a/therapist/test/widget_test.dart b/therapist/test/widget_test.dart index c7b50b3..52b4f62 100644 --- a/therapist/test/widget_test.dart +++ b/therapist/test/widget_test.dart @@ -1,30 +1,30 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility in the flutter_test package. For example, you can send tap and scroll -// gestures. You can also use WidgetTester to find child widgets in the widget -// tree, read text, and verify that the values of widget properties are correct. - -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import 'package:therapist/main.dart'; - -void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(const MyApp()); - - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); - - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); - await tester.pump(); - - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); - }); -} +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:therapist/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +} From 963b49775af3cdb2d49fd2452c5d23b3b6a66e75 Mon Sep 17 00:00:00 2001 From: SharkyBytes Date: Thu, 20 Mar 2025 14:58:00 +0530 Subject: [PATCH 2/4] feat(auth): implemented Supabase Google OAuth for patient and therapist apps with multi-platform support --- patient/.env.example | 3 +- patient/README.md | 59 ++++++--- .../android/app/src/debug/AndroidManifest.xml | 15 ++- .../android/app/src/main/AndroidManifest.xml | 91 ++++++------- patient/android/build.gradle | 37 +++--- .../gradle/wrapper/gradle-wrapper.properties | 4 +- patient/android/settings.gradle | 2 +- patient/assets/images/meditation.png | Bin 11885 -> 0 bytes patient/assets/meditation.png | Bin 11885 -> 0 bytes patient/ios/Runner/Info.plist | 110 +++++++++------- .../lib/presentation/auth/auth_screen.dart | 27 +--- .../widgets/google_signin_button.dart | 123 +++++++++++++++-- therapist/.env.example | 5 + therapist/README.md | 67 +++++++--- .../android/app/src/debug/AndroidManifest.xml | 15 ++- .../android/app/src/main/AndroidManifest.xml | 91 ++++++------- .../gradle/wrapper/gradle-wrapper.properties | 2 +- therapist/android/settings.gradle | 2 +- therapist/ios/Runner/Info.plist | 109 ++++++++------- .../lib/presentation/auth/auth_screen.dart | 56 +------- .../widgets/google_signin_button.dart | 124 ++++++++++++++++-- 21 files changed, 580 insertions(+), 362 deletions(-) delete mode 100644 patient/assets/images/meditation.png delete mode 100644 patient/assets/meditation.png create mode 100644 therapist/.env.example diff --git a/patient/.env.example b/patient/.env.example index 97ff5b8..a7e4bff 100644 --- a/patient/.env.example +++ b/patient/.env.example @@ -1,4 +1,5 @@ SUPABASE_URL="your-supabase-url" SUPABASE_ANON_KEY="your-supabase-key" +GOOGLE_WEB_CLIENT_ID="your-google-web-client-id" +GOOGLE_IOS_CLIENT_ID="your-google-ios-client-id" -a \ No newline at end of file diff --git a/patient/README.md b/patient/README.md index a01e83c..7c05da8 100644 --- a/patient/README.md +++ b/patient/README.md @@ -1,16 +1,43 @@ -# patient - -A new Flutter project. - -## Getting Started - -This project is a starting point for a Flutter application. - -A few resources to get you started if this is your first Flutter project: - -- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) -- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) - -For help getting started with Flutter development, view the -[online documentation](https://docs.flutter.dev/), which offers tutorials, -samples, guidance on mobile development, and a full API reference. +# patient + +A new Flutter project. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. + + +### How to setup + +# Google Cloud Console Configuration + +1. Go to the [Google Cloud Console](https://console.cloud.google.com/) and create a new project or select an existing one. +2. Navigate to **"APIs & Services" > "Credentials"**. +3. Click **"Create Credentials"** and select **"OAuth client ID"**. +4. Choose **"Web application"** as the application type. +5. Add your Supabase project's domain (`.supabase.co`) to the **"Authorized domains"** list. +6. Set the following authorized redirect URI: +7. After creation, note down the **Client ID** and **Client Secret**. + +Setting Up the Cloud Console for OAuth + +![image](https://github.com/user-attachments/assets/ba9f4927-4e5e-4a68-b1d3-a0a273a8713a) + + +--- + +# Supabase Configuration + +1. In your [Supabase project dashboard](https://app.supabase.io/), go to **"Authentication" > "Providers"**. +2. Find the **Google** section and enable **"Sign in with Google"**. +3. Enter the **Client ID** and **Client Secret** obtained from Google Cloud Console. +4. In **"Authentication" > "URL Configuration"**, set the **Site URL** and any additional redirect URLs for your application. \ No newline at end of file diff --git a/patient/android/app/src/debug/AndroidManifest.xml b/patient/android/app/src/debug/AndroidManifest.xml index 399f698..b1af30b 100644 --- a/patient/android/app/src/debug/AndroidManifest.xml +++ b/patient/android/app/src/debug/AndroidManifest.xml @@ -1,7 +1,8 @@ - - - - + + + + diff --git a/patient/android/app/src/main/AndroidManifest.xml b/patient/android/app/src/main/AndroidManifest.xml index ae16549..5529e36 100644 --- a/patient/android/app/src/main/AndroidManifest.xml +++ b/patient/android/app/src/main/AndroidManifest.xml @@ -1,45 +1,46 @@ - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + diff --git a/patient/android/build.gradle b/patient/android/build.gradle index d2ffbff..126335c 100644 --- a/patient/android/build.gradle +++ b/patient/android/build.gradle @@ -1,18 +1,19 @@ -allprojects { - repositories { - google() - mavenCentral() - } -} - -rootProject.buildDir = "../build" -subprojects { - project.buildDir = "${rootProject.buildDir}/${project.name}" -} -subprojects { - project.evaluationDependsOn(":app") -} - -tasks.register("clean", Delete) { - delete rootProject.buildDir -} +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = "../build" +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} + diff --git a/patient/android/gradle/wrapper/gradle-wrapper.properties b/patient/android/gradle/wrapper/gradle-wrapper.properties index 7bb2df6..55d061f 100644 --- a/patient/android/gradle/wrapper/gradle-wrapper.properties +++ b/patient/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-all.zip + + diff --git a/patient/android/settings.gradle b/patient/android/settings.gradle index b9e43bd..4133d39 100644 --- a/patient/android/settings.gradle +++ b/patient/android/settings.gradle @@ -18,7 +18,7 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" - id "com.android.application" version "8.1.0" apply false + sid "com.android.application" version "8.2.1" apply false id "org.jetbrains.kotlin.android" version "1.8.22" apply false } diff --git a/patient/assets/images/meditation.png b/patient/assets/images/meditation.png deleted file mode 100644 index a716a70847d7f91c0ff8148a5238d4ede5daae68..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11885 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 S#|CbPgJh+YBrC)}2mL=8hs~`3 diff --git a/patient/assets/meditation.png b/patient/assets/meditation.png deleted file mode 100644 index a716a70847d7f91c0ff8148a5238d4ede5daae68..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11885 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 S#|CbPgJh+YBrC)}2mL=8hs~`3 diff --git a/patient/ios/Runner/Info.plist b/patient/ios/Runner/Info.plist index 1c59623..81f40a7 100644 --- a/patient/ios/Runner/Info.plist +++ b/patient/ios/Runner/Info.plist @@ -1,49 +1,61 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleDisplayName - Patient - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - patient - CFBundlePackageType - APPL - CFBundleShortVersionString - $(FLUTTER_BUILD_NAME) - CFBundleSignature - ???? - CFBundleVersion - $(FLUTTER_BUILD_NUMBER) - LSRequiresIPhoneOS - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - CADisableMinimumFrameDurationOnPhone - - UIApplicationSupportsIndirectInputEvents - - - + + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Patient + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + patient + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + com.googleusercontent.apps.861823949799-vc35cprkp249096uujjn0vvnmcvjppkn + + + + + diff --git a/patient/lib/presentation/auth/auth_screen.dart b/patient/lib/presentation/auth/auth_screen.dart index 7e2df48..7bb4640 100644 --- a/patient/lib/presentation/auth/auth_screen.dart +++ b/patient/lib/presentation/auth/auth_screen.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:google_sign_in/google_sign_in.dart'; +import 'package:patient/presentation/widgets/google_signin_button.dart'; import '../widgets/welcome_header.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; import 'package:flutter/foundation.dart' show kIsWeb; @@ -179,30 +180,8 @@ class _AuthScreenState extends State { bottom: 40, left: 0, right: 0, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: ElevatedButton( - // onPressed: () async{ - - // await supabase.auth. - // signInWithOAuth( - // OAuthProvider.google, - // // redirectTo: kIsWeb ? null : 'com.mycompany.cbtdiary://login-callback/', - // // authScreenLaunchMode: kIsWeb ? LaunchMode.platformDefault : LaunchMode.externalApplication, - // ); - - // }, - onPressed: _handleGoogleSignIn, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Image.asset('assets/google_logo.png', height: 24), - const SizedBox(width: 10), - const Text('Sign in with Google'), - ], - ), - ), - ), + child: GoogleSignInButton(), + ), ], ), diff --git a/patient/lib/presentation/widgets/google_signin_button.dart b/patient/lib/presentation/widgets/google_signin_button.dart index 0ce12c0..f337d59 100644 --- a/patient/lib/presentation/widgets/google_signin_button.dart +++ b/patient/lib/presentation/widgets/google_signin_button.dart @@ -1,38 +1,133 @@ import 'package:flutter/material.dart'; +import 'package:google_sign_in/google_sign_in.dart'; +import 'package:supabase_flutter/supabase_flutter.dart'; +import 'package:flutter/foundation.dart' show kIsWeb; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:patient/presentation/auth/personal_details_screen.dart'; +import 'dart:io' show Platform; -class GoogleSignInButton extends StatelessWidget { +final supabase = Supabase.instance.client; + +class GoogleSignInButton extends StatefulWidget { const GoogleSignInButton({super.key}); + @override + _GoogleSignInButtonState createState() => _GoogleSignInButtonState(); +} + +class _GoogleSignInButtonState extends State { + Future _handleGoogleSignIn(BuildContext context) async { + try { + if (kIsWeb) { + await _handleWebSignIn(context); + } else { + await _handleMobileSignIn(context); + } + } catch (error) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Sign in failed: $error')), + ); + } + } + } + + Future _handleWebSignIn(BuildContext context) async { + final supabaseUrl = dotenv.env['SUPABASE_URL'] ?? + (throw Exception("Supabase URL not found in .env")); + + await supabase.auth.signInWithOAuth( + OAuthProvider.google, + redirectTo: kIsWeb + ? "$supabaseUrl/auth/v1/callback" + : 'com.mycompany.cbtdiary://login-callback/', + authScreenLaunchMode: + kIsWeb ? LaunchMode.platformDefault : LaunchMode.externalApplication, + ); + + _handlePostSignIn(context); + } + + Future _handleMobileSignIn(BuildContext context) async { + // Get client IDs from environment variables + debugPrint('I am here'); + final webClientId = dotenv.env['GOOGLE_WEB_CLIENT_ID'] ?? + (throw Exception("WEB_CLIENT_ID not found in .env")); + final iosClientId = dotenv.env['GOOGLE_IOS_CLIENT_ID']; + +final GoogleSignIn googleSignIn = GoogleSignIn( + clientId: Platform.isIOS ? iosClientId : null, + serverClientId: webClientId, + scopes: ['email', 'profile'], + ); + + final GoogleSignInAccount? googleUser = await googleSignIn.signIn(); + if (googleUser == null) throw 'Sign in cancelled'; + + final GoogleSignInAuthentication googleAuth = + await googleUser.authentication; + + if (googleAuth.idToken == null) throw 'No ID Token found'; + if (googleAuth.accessToken == null) throw 'No Access Token found'; + + await supabase.auth.signInWithIdToken( + provider: OAuthProvider.google, + idToken: googleAuth.idToken!, + accessToken: googleAuth.accessToken, + ); + + _handlePostSignIn(context); + } + + void _handlePostSignIn(BuildContext context) { + final session = supabase.auth.currentSession; + if (session == null || !mounted) return; + + final userMetadata = session.user.userMetadata; + final fullName = userMetadata?['full_name'] ?? 'User'; + final email = session.user.email ?? 'No email'; + + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Welcome $fullName!'), + duration: const Duration(seconds: 2), + ), + ); + + Navigator.of(context).pushReplacement( + MaterialPageRoute( + builder: (context) => const PersonalDetailsScreen(), + ), + ); + } + @override Widget build(BuildContext context) { return Padding( - padding: - const EdgeInsets.symmetric(horizontal: 20), // Add horizontal padding + padding: const EdgeInsets.symmetric(horizontal: 20), child: SizedBox( - width: double.infinity, // Makes the button expand to full width - height: 50, // Adjusted height for better appearance + width: double.infinity, + height: 50, child: ElevatedButton( - onPressed: () { - // Implement Google Sign-in logic here - }, + onPressed: () => _handleGoogleSignIn(context), style: ElevatedButton.styleFrom( backgroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(30), ), - elevation: 2, // Small shadow effect + elevation: 2, padding: const EdgeInsets.symmetric(vertical: 12), ), child: Row( - mainAxisSize: MainAxisSize.min, // Prevent unnecessary stretching - mainAxisAlignment: MainAxisAlignment.center, // Center elements + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, children: [ Image.asset( - 'assets/google_logo.png', // Ensure the correct path + 'assets/google_logo.png', height: 24, width: 24, ), - const SizedBox(width: 10), // Space between icon and text + const SizedBox(width: 10), const Text( 'Continue with Google', style: TextStyle( @@ -47,4 +142,4 @@ class GoogleSignInButton extends StatelessWidget { ), ); } -} \ No newline at end of file +} diff --git a/therapist/.env.example b/therapist/.env.example new file mode 100644 index 0000000..a7e4bff --- /dev/null +++ b/therapist/.env.example @@ -0,0 +1,5 @@ +SUPABASE_URL="your-supabase-url" +SUPABASE_ANON_KEY="your-supabase-key" +GOOGLE_WEB_CLIENT_ID="your-google-web-client-id" +GOOGLE_IOS_CLIENT_ID="your-google-ios-client-id" + diff --git a/therapist/README.md b/therapist/README.md index 2116397..89ee768 100644 --- a/therapist/README.md +++ b/therapist/README.md @@ -1,16 +1,51 @@ -# therapist - -A new Flutter project. - -## Getting Started - -This project is a starting point for a Flutter application. - -A few resources to get you started if this is your first Flutter project: - -- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) -- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) - -For help getting started with Flutter development, view the -[online documentation](https://docs.flutter.dev/), which offers tutorials, -samples, guidance on mobile development, and a full API reference. +# therapist + +A new Flutter project. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. + + +### How to setup + +# Google Cloud Console Configuration + +1. Go to the [Google Cloud Console](https://console.cloud.google.com/) and create a new project or select an existing one. +2. Navigate to **"APIs & Services" > "Credentials"**. +3. Click **"Create Credentials"** and select **"OAuth client ID"**. +4. Choose **"Web application"** as the application type. +5. Add your Supabase project's domain (`.supabase.co`) to the **"Authorized domains"** list. +6. Set the following authorized redirect URI: +7. After creation, note down the **Client ID** and **Client Secret**. + +Setting Up the Cloud Console for OAuth + +![image](https://github.com/user-attachments/assets/ba9f4927-4e5e-4a68-b1d3-a0a273a8713a) + + +--- + +# Supabase Configuration + +1. In your [Supabase project dashboard](https://app.supabase.io/), go to **"Authentication" > "Providers"**. +2. Find the **Google** section and enable **"Sign in with Google"**. +3. Enter the **Client ID** and **Client Secret** obtained from Google Cloud Console. +4. In **"Authentication" > "URL Configuration"**, set the **Site URL** and any additional redirect URLs for your application. + + +# env Setup + +1. SUPABASE_ANON_KEY= +2. SUPABASE_URL= + +Add the env variables in this \ No newline at end of file diff --git a/therapist/android/app/src/debug/AndroidManifest.xml b/therapist/android/app/src/debug/AndroidManifest.xml index 399f698..15b6651 100644 --- a/therapist/android/app/src/debug/AndroidManifest.xml +++ b/therapist/android/app/src/debug/AndroidManifest.xml @@ -1,7 +1,8 @@ - - - - + + + + diff --git a/therapist/android/app/src/main/AndroidManifest.xml b/therapist/android/app/src/main/AndroidManifest.xml index 4eaf7e1..ddc358a 100644 --- a/therapist/android/app/src/main/AndroidManifest.xml +++ b/therapist/android/app/src/main/AndroidManifest.xml @@ -1,45 +1,46 @@ - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + diff --git a/therapist/android/gradle/wrapper/gradle-wrapper.properties b/therapist/android/gradle/wrapper/gradle-wrapper.properties index 7bb2df6..ec915a8 100644 --- a/therapist/android/gradle/wrapper/gradle-wrapper.properties +++ b/therapist/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-all.zip diff --git a/therapist/android/settings.gradle b/therapist/android/settings.gradle index b9e43bd..a42444d 100644 --- a/therapist/android/settings.gradle +++ b/therapist/android/settings.gradle @@ -18,7 +18,7 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" - id "com.android.application" version "8.1.0" apply false + id "com.android.application" version "8.2.1" apply false id "org.jetbrains.kotlin.android" version "1.8.22" apply false } diff --git a/therapist/ios/Runner/Info.plist b/therapist/ios/Runner/Info.plist index 994e5be..bbee569 100644 --- a/therapist/ios/Runner/Info.plist +++ b/therapist/ios/Runner/Info.plist @@ -1,49 +1,60 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleDisplayName - Therapist - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - therapist - CFBundlePackageType - APPL - CFBundleShortVersionString - $(FLUTTER_BUILD_NAME) - CFBundleSignature - ???? - CFBundleVersion - $(FLUTTER_BUILD_NUMBER) - LSRequiresIPhoneOS - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - CADisableMinimumFrameDurationOnPhone - - UIApplicationSupportsIndirectInputEvents - - - + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Therapist + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + therapist + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + com.googleusercontent.apps.861823949799-vc35cprkp249096uujjn0vvnmcvjppkn + + + + + diff --git a/therapist/lib/presentation/auth/auth_screen.dart b/therapist/lib/presentation/auth/auth_screen.dart index dd0633a..418ab6a 100644 --- a/therapist/lib/presentation/auth/auth_screen.dart +++ b/therapist/lib/presentation/auth/auth_screen.dart @@ -4,6 +4,7 @@ import 'package:smooth_page_indicator/smooth_page_indicator.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; import 'package:flutter/foundation.dart' show kIsWeb; +import 'package:therapist/presentation/widgets/google_signin_button.dart'; import '../home/home_screen.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; @@ -69,37 +70,6 @@ class _AuthScreenState extends State { } - - Future _handleGoogleSignIn() async { - try { - final supabaseUrl = dotenv.env['SUPABASE_URL']; - await supabase.auth.signInWithOAuth( - OAuthProvider.google, - redirectTo: kIsWeb - ? "$supabaseUrl/auth/v1/callback" - : 'com.mycompany.cbtdiary://login-callback/', - authScreenLaunchMode: - kIsWeb ? LaunchMode.platformDefault : LaunchMode.externalApplication, - ); - - final session = supabase.auth.currentSession; - debugPrint("User authenticated, navigating to HomeScreen"); - print(session); - - if (session != null && mounted) { - Navigator.of(context).pushReplacement( - MaterialPageRoute(builder: (context) => const HomeScreen()), - ); - } - } catch (error) { - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Sign in failed: $error')), - ); - } - } - } - @override void dispose() { _timer?.cancel(); @@ -196,32 +166,12 @@ class _AuthScreenState extends State { ), ), const SizedBox(height: 20), + // Google Sign-In Button Padding( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), - child: ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: Colors.white, - padding: - const EdgeInsets.symmetric(vertical: 12, horizontal: 24), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(30), - ), - ), - onPressed: _handleGoogleSignIn, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Image.asset("assets/google_logo.png", height: 24), - const SizedBox(width: 10), - const Text( - "Continue with Google", - style: TextStyle(fontSize: 16, color: Colors.black), - ), - ], - ), - ), + child: GoogleSignInButton(), ), const SizedBox(height: 20), ], diff --git a/therapist/lib/presentation/widgets/google_signin_button.dart b/therapist/lib/presentation/widgets/google_signin_button.dart index 0ce12c0..ba67ca2 100644 --- a/therapist/lib/presentation/widgets/google_signin_button.dart +++ b/therapist/lib/presentation/widgets/google_signin_button.dart @@ -1,38 +1,134 @@ import 'package:flutter/material.dart'; +import 'package:google_sign_in/google_sign_in.dart'; +import 'package:supabase_flutter/supabase_flutter.dart'; +import 'package:flutter/foundation.dart' show kIsWeb; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'dart:io' show Platform; -class GoogleSignInButton extends StatelessWidget { +import 'package:therapist/presentation/home/home_screen.dart'; + +final supabase = Supabase.instance.client; + +class GoogleSignInButton extends StatefulWidget { const GoogleSignInButton({super.key}); + @override + _GoogleSignInButtonState createState() => _GoogleSignInButtonState(); +} + +class _GoogleSignInButtonState extends State { + Future _handleGoogleSignIn(BuildContext context) async { + try { + if (kIsWeb) { + await _handleWebSignIn(context); + } else { + await _handleMobileSignIn(context); + } + } catch (error) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Sign in failed: $error')), + ); + } + } + } + + Future _handleWebSignIn(BuildContext context) async { + final supabaseUrl = dotenv.env['SUPABASE_URL'] ?? + (throw Exception("Supabase URL not found in .env")); + + await supabase.auth.signInWithOAuth( + OAuthProvider.google, + redirectTo: kIsWeb + ? "$supabaseUrl/auth/v1/callback" + : 'com.mycompany.cbtdiary://login-callback/', + authScreenLaunchMode: + kIsWeb ? LaunchMode.platformDefault : LaunchMode.externalApplication, + ); + + _handlePostSignIn(context); + } + + Future _handleMobileSignIn(BuildContext context) async { + // Get client IDs from environment variables + debugPrint('I am here'); + final webClientId = dotenv.env['GOOGLE_WEB_CLIENT_ID'] ?? + (throw Exception("WEB_CLIENT_ID not found in .env")); + final iosClientId = dotenv.env['GOOGLE_IOS_CLIENT_ID']; + +final GoogleSignIn googleSignIn = GoogleSignIn( + clientId: Platform.isIOS ? iosClientId : null, + serverClientId: webClientId, + scopes: ['email', 'profile'], + ); + + final GoogleSignInAccount? googleUser = await googleSignIn.signIn(); + if (googleUser == null) throw 'Sign in cancelled'; + + final GoogleSignInAuthentication googleAuth = + await googleUser.authentication; + + if (googleAuth.idToken == null) throw 'No ID Token found'; + if (googleAuth.accessToken == null) throw 'No Access Token found'; + + await supabase.auth.signInWithIdToken( + provider: OAuthProvider.google, + idToken: googleAuth.idToken!, + accessToken: googleAuth.accessToken, + ); + + _handlePostSignIn(context); + } + + void _handlePostSignIn(BuildContext context) { + final session = supabase.auth.currentSession; + if (session == null || !mounted) return; + + final userMetadata = session.user.userMetadata; + final fullName = userMetadata?['full_name'] ?? 'User'; + final email = session.user.email ?? 'No email'; + + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Welcome $fullName!'), + duration: const Duration(seconds: 2), + ), + ); + + Navigator.of(context).pushReplacement( + MaterialPageRoute( + builder: (context) => const HomeScreen(), + ), + ); + } + @override Widget build(BuildContext context) { return Padding( - padding: - const EdgeInsets.symmetric(horizontal: 20), // Add horizontal padding + padding: const EdgeInsets.symmetric(horizontal: 20), child: SizedBox( - width: double.infinity, // Makes the button expand to full width - height: 50, // Adjusted height for better appearance + width: double.infinity, + height: 50, child: ElevatedButton( - onPressed: () { - // Implement Google Sign-in logic here - }, + onPressed: () => _handleGoogleSignIn(context), style: ElevatedButton.styleFrom( backgroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(30), ), - elevation: 2, // Small shadow effect + elevation: 2, padding: const EdgeInsets.symmetric(vertical: 12), ), child: Row( - mainAxisSize: MainAxisSize.min, // Prevent unnecessary stretching - mainAxisAlignment: MainAxisAlignment.center, // Center elements + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, children: [ Image.asset( - 'assets/google_logo.png', // Ensure the correct path + 'assets/google_logo.png', height: 24, width: 24, ), - const SizedBox(width: 10), // Space between icon and text + const SizedBox(width: 10), const Text( 'Continue with Google', style: TextStyle( @@ -47,4 +143,4 @@ class GoogleSignInButton extends StatelessWidget { ), ); } -} \ No newline at end of file +} From b1ec0d1be45032d7b7a67a7918f0f07d3856fbfe Mon Sep 17 00:00:00 2001 From: SharkyBytes Date: Fri, 21 Mar 2025 00:08:48 +0530 Subject: [PATCH 3/4] Fixed the typo error and moved the auth logic in the auth_provider --- patient/android/settings.gradle | 2 +- .../widgets/google_signin_button.dart | 106 +---------------- patient/lib/provider/auth_provider.dart | 94 +++++++++++++-- .../widgets/google_signin_button.dart | 108 +----------------- therapist/lib/provider/auth_provider.dart | 87 +++++++++++++- 5 files changed, 184 insertions(+), 213 deletions(-) diff --git a/patient/android/settings.gradle b/patient/android/settings.gradle index 4133d39..a42444d 100644 --- a/patient/android/settings.gradle +++ b/patient/android/settings.gradle @@ -18,7 +18,7 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" - sid "com.android.application" version "8.2.1" apply false + id "com.android.application" version "8.2.1" apply false id "org.jetbrains.kotlin.android" version "1.8.22" apply false } diff --git a/patient/lib/presentation/widgets/google_signin_button.dart b/patient/lib/presentation/widgets/google_signin_button.dart index f337d59..0df65bf 100644 --- a/patient/lib/presentation/widgets/google_signin_button.dart +++ b/patient/lib/presentation/widgets/google_signin_button.dart @@ -1,105 +1,9 @@ import 'package:flutter/material.dart'; -import 'package:google_sign_in/google_sign_in.dart'; -import 'package:supabase_flutter/supabase_flutter.dart'; -import 'package:flutter/foundation.dart' show kIsWeb; -import 'package:flutter_dotenv/flutter_dotenv.dart'; -import 'package:patient/presentation/auth/personal_details_screen.dart'; -import 'dart:io' show Platform; +import 'package:patient/provider/auth_provider.dart'; +import 'package:provider/provider.dart'; -final supabase = Supabase.instance.client; - -class GoogleSignInButton extends StatefulWidget { - const GoogleSignInButton({super.key}); - - @override - _GoogleSignInButtonState createState() => _GoogleSignInButtonState(); -} - -class _GoogleSignInButtonState extends State { - Future _handleGoogleSignIn(BuildContext context) async { - try { - if (kIsWeb) { - await _handleWebSignIn(context); - } else { - await _handleMobileSignIn(context); - } - } catch (error) { - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Sign in failed: $error')), - ); - } - } - } - - Future _handleWebSignIn(BuildContext context) async { - final supabaseUrl = dotenv.env['SUPABASE_URL'] ?? - (throw Exception("Supabase URL not found in .env")); - - await supabase.auth.signInWithOAuth( - OAuthProvider.google, - redirectTo: kIsWeb - ? "$supabaseUrl/auth/v1/callback" - : 'com.mycompany.cbtdiary://login-callback/', - authScreenLaunchMode: - kIsWeb ? LaunchMode.platformDefault : LaunchMode.externalApplication, - ); - - _handlePostSignIn(context); - } - - Future _handleMobileSignIn(BuildContext context) async { - // Get client IDs from environment variables - debugPrint('I am here'); - final webClientId = dotenv.env['GOOGLE_WEB_CLIENT_ID'] ?? - (throw Exception("WEB_CLIENT_ID not found in .env")); - final iosClientId = dotenv.env['GOOGLE_IOS_CLIENT_ID']; - -final GoogleSignIn googleSignIn = GoogleSignIn( - clientId: Platform.isIOS ? iosClientId : null, - serverClientId: webClientId, - scopes: ['email', 'profile'], - ); - - final GoogleSignInAccount? googleUser = await googleSignIn.signIn(); - if (googleUser == null) throw 'Sign in cancelled'; - - final GoogleSignInAuthentication googleAuth = - await googleUser.authentication; - - if (googleAuth.idToken == null) throw 'No ID Token found'; - if (googleAuth.accessToken == null) throw 'No Access Token found'; - - await supabase.auth.signInWithIdToken( - provider: OAuthProvider.google, - idToken: googleAuth.idToken!, - accessToken: googleAuth.accessToken, - ); - - _handlePostSignIn(context); - } - - void _handlePostSignIn(BuildContext context) { - final session = supabase.auth.currentSession; - if (session == null || !mounted) return; - - final userMetadata = session.user.userMetadata; - final fullName = userMetadata?['full_name'] ?? 'User'; - final email = session.user.email ?? 'No email'; - - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Welcome $fullName!'), - duration: const Duration(seconds: 2), - ), - ); - - Navigator.of(context).pushReplacement( - MaterialPageRoute( - builder: (context) => const PersonalDetailsScreen(), - ), - ); - } +class GoogleSignInButton extends StatelessWidget { + const GoogleSignInButton({Key? key}) : super(key: key); @override Widget build(BuildContext context) { @@ -109,7 +13,7 @@ final GoogleSignIn googleSignIn = GoogleSignIn( width: double.infinity, height: 50, child: ElevatedButton( - onPressed: () => _handleGoogleSignIn(context), + onPressed: () => context.read().signInWithGoogle(context), style: ElevatedButton.styleFrom( backgroundColor: Colors.white, shape: RoundedRectangleBorder( diff --git a/patient/lib/provider/auth_provider.dart b/patient/lib/provider/auth_provider.dart index 7a3221e..0ea617d 100644 --- a/patient/lib/provider/auth_provider.dart +++ b/patient/lib/provider/auth_provider.dart @@ -1,7 +1,87 @@ -import 'package:flutter/material.dart'; - -class AuthProvider extends ChangeNotifier { - // The provider will act like a combination of service and controller. - // It will make use of the repository to perform the actual authentication and then notify the listeners of the state of the authentication process. - // AuthProvider({ required this.authRepository }); -} +import 'package:flutter/material.dart'; +import 'package:google_sign_in/google_sign_in.dart'; +import 'package:patient/presentation/auth/personal_details_screen.dart'; +import 'package:supabase_flutter/supabase_flutter.dart'; +import 'package:flutter/foundation.dart' show kIsWeb; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'dart:io' show Platform; + +class AuthProvider extends ChangeNotifier { + final supabase = Supabase.instance.client; + Future signInWithGoogle(BuildContext context) async { + try { + if (kIsWeb) { + await _handleWebSignIn(context); + } else { + await _handleMobileSignIn(context); + } + } catch (error) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Sign in failed: $error')), + ); + } + } + + Future _handleWebSignIn(BuildContext context) async { + final supabaseUrl = dotenv.env['SUPABASE_URL'] ?? + (throw Exception("Supabase URL not found in .env")); + + await supabase.auth.signInWithOAuth( + OAuthProvider.google, + redirectTo: "$supabaseUrl/auth/v1/callback", + authScreenLaunchMode: LaunchMode.platformDefault, + ); + + _handlePostSignIn(context); + } + + Future _handleMobileSignIn(BuildContext context) async { + final webClientId = dotenv.env['GOOGLE_WEB_CLIENT_ID'] ?? + (throw Exception("WEB_CLIENT_ID not found in .env")); + final iosClientId = dotenv.env['GOOGLE_IOS_CLIENT_ID']; + + final GoogleSignIn googleSignIn = GoogleSignIn( + clientId: Platform.isIOS ? iosClientId : null, + serverClientId: webClientId, + scopes: ['email', 'profile'], + ); + + final GoogleSignInAccount? googleUser = await googleSignIn.signIn(); + if (googleUser == null) throw 'Sign in cancelled'; + + final GoogleSignInAuthentication googleAuth = + await googleUser.authentication; + + if (googleAuth.idToken == null) throw 'No ID Token found'; + if (googleAuth.accessToken == null) throw 'No Access Token found'; + + await supabase.auth.signInWithIdToken( + provider: OAuthProvider.google, + idToken: googleAuth.idToken!, + accessToken: googleAuth.accessToken, + ); + + _handlePostSignIn(context); + } + + void _handlePostSignIn(BuildContext context) { + final session = supabase.auth.currentSession; + if (session == null) return; + + final userMetadata = session.user.userMetadata; + final fullName = userMetadata?['full_name'] ?? 'User'; + + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Welcome $fullName!'), + duration: const Duration(seconds: 2), + ), + ); + + Navigator.of(context).pushReplacement( + MaterialPageRoute( + builder: (context) => const PersonalDetailsScreen(), + ), + ); + } +} diff --git a/therapist/lib/presentation/widgets/google_signin_button.dart b/therapist/lib/presentation/widgets/google_signin_button.dart index ba67ca2..8a5186c 100644 --- a/therapist/lib/presentation/widgets/google_signin_button.dart +++ b/therapist/lib/presentation/widgets/google_signin_button.dart @@ -1,106 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:google_sign_in/google_sign_in.dart'; -import 'package:supabase_flutter/supabase_flutter.dart'; -import 'package:flutter/foundation.dart' show kIsWeb; -import 'package:flutter_dotenv/flutter_dotenv.dart'; -import 'dart:io' show Platform; - -import 'package:therapist/presentation/home/home_screen.dart'; - -final supabase = Supabase.instance.client; - -class GoogleSignInButton extends StatefulWidget { - const GoogleSignInButton({super.key}); - - @override - _GoogleSignInButtonState createState() => _GoogleSignInButtonState(); -} - -class _GoogleSignInButtonState extends State { - Future _handleGoogleSignIn(BuildContext context) async { - try { - if (kIsWeb) { - await _handleWebSignIn(context); - } else { - await _handleMobileSignIn(context); - } - } catch (error) { - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Sign in failed: $error')), - ); - } - } - } - - Future _handleWebSignIn(BuildContext context) async { - final supabaseUrl = dotenv.env['SUPABASE_URL'] ?? - (throw Exception("Supabase URL not found in .env")); - - await supabase.auth.signInWithOAuth( - OAuthProvider.google, - redirectTo: kIsWeb - ? "$supabaseUrl/auth/v1/callback" - : 'com.mycompany.cbtdiary://login-callback/', - authScreenLaunchMode: - kIsWeb ? LaunchMode.platformDefault : LaunchMode.externalApplication, - ); - - _handlePostSignIn(context); - } - - Future _handleMobileSignIn(BuildContext context) async { - // Get client IDs from environment variables - debugPrint('I am here'); - final webClientId = dotenv.env['GOOGLE_WEB_CLIENT_ID'] ?? - (throw Exception("WEB_CLIENT_ID not found in .env")); - final iosClientId = dotenv.env['GOOGLE_IOS_CLIENT_ID']; - -final GoogleSignIn googleSignIn = GoogleSignIn( - clientId: Platform.isIOS ? iosClientId : null, - serverClientId: webClientId, - scopes: ['email', 'profile'], - ); - - final GoogleSignInAccount? googleUser = await googleSignIn.signIn(); - if (googleUser == null) throw 'Sign in cancelled'; - - final GoogleSignInAuthentication googleAuth = - await googleUser.authentication; - - if (googleAuth.idToken == null) throw 'No ID Token found'; - if (googleAuth.accessToken == null) throw 'No Access Token found'; - - await supabase.auth.signInWithIdToken( - provider: OAuthProvider.google, - idToken: googleAuth.idToken!, - accessToken: googleAuth.accessToken, - ); - - _handlePostSignIn(context); - } - - void _handlePostSignIn(BuildContext context) { - final session = supabase.auth.currentSession; - if (session == null || !mounted) return; - - final userMetadata = session.user.userMetadata; - final fullName = userMetadata?['full_name'] ?? 'User'; - final email = session.user.email ?? 'No email'; - - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Welcome $fullName!'), - duration: const Duration(seconds: 2), - ), - ); - - Navigator.of(context).pushReplacement( - MaterialPageRoute( - builder: (context) => const HomeScreen(), - ), - ); - } +import 'package:provider/provider.dart'; +import 'package:therapist/provider/auth_provider.dart'; +class GoogleSignInButton extends StatelessWidget { + const GoogleSignInButton({Key? key}) : super(key: key); @override Widget build(BuildContext context) { @@ -110,7 +12,7 @@ final GoogleSignIn googleSignIn = GoogleSignIn( width: double.infinity, height: 50, child: ElevatedButton( - onPressed: () => _handleGoogleSignIn(context), + onPressed: () => context.read().signInWithGoogle(context), style: ElevatedButton.styleFrom( backgroundColor: Colors.white, shape: RoundedRectangleBorder( diff --git a/therapist/lib/provider/auth_provider.dart b/therapist/lib/provider/auth_provider.dart index f5a2716..ef707d2 100644 --- a/therapist/lib/provider/auth_provider.dart +++ b/therapist/lib/provider/auth_provider.dart @@ -1,7 +1,15 @@ import 'package:flutter/material.dart'; +import 'package:google_sign_in/google_sign_in.dart'; +import 'package:supabase_flutter/supabase_flutter.dart'; +import 'package:flutter/foundation.dart' show kIsWeb; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'dart:io' show Platform; +import 'package:therapist/presentation/home/home_screen.dart'; class AuthProvider extends ChangeNotifier { - bool _isAuthenticated = false; + + final supabase = Supabase.instance.client; + bool _isAuthenticated = false; bool get isAuthenticated => _isAuthenticated; @@ -9,4 +17,81 @@ class AuthProvider extends ChangeNotifier { _isAuthenticated = true; notifyListeners(); } + + Future signInWithGoogle(BuildContext context) async { + try { + if (kIsWeb) { + await _handleWebSignIn(context); + } else { + await _handleMobileSignIn(context); + } + } catch (error) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Sign in failed: $error')), + ); + } + } + + Future _handleWebSignIn(BuildContext context) async { + final supabaseUrl = dotenv.env['SUPABASE_URL'] ?? + (throw Exception("Supabase URL not found in .env")); + + await supabase.auth.signInWithOAuth( + OAuthProvider.google, + redirectTo: "$supabaseUrl/auth/v1/callback", + authScreenLaunchMode: LaunchMode.platformDefault, + ); + + _handlePostSignIn(context); + } + + Future _handleMobileSignIn(BuildContext context) async { + final webClientId = dotenv.env['GOOGLE_WEB_CLIENT_ID'] ?? + (throw Exception("WEB_CLIENT_ID not found in .env")); + final iosClientId = dotenv.env['GOOGLE_IOS_CLIENT_ID']; + + final GoogleSignIn googleSignIn = GoogleSignIn( + clientId: Platform.isIOS ? iosClientId : null, + serverClientId: webClientId, + scopes: ['email', 'profile'], + ); + + final GoogleSignInAccount? googleUser = await googleSignIn.signIn(); + if (googleUser == null) throw 'Sign in cancelled'; + + final GoogleSignInAuthentication googleAuth = + await googleUser.authentication; + + if (googleAuth.idToken == null) throw 'No ID Token found'; + if (googleAuth.accessToken == null) throw 'No Access Token found'; + + await supabase.auth.signInWithIdToken( + provider: OAuthProvider.google, + idToken: googleAuth.idToken!, + accessToken: googleAuth.accessToken, + ); + + _handlePostSignIn(context); + } + + void _handlePostSignIn(BuildContext context) { + final session = supabase.auth.currentSession; + if (session == null) return; + + final userMetadata = session.user.userMetadata; + final fullName = userMetadata?['full_name'] ?? 'User'; + + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Welcome $fullName!'), + duration: const Duration(seconds: 2), + ), + ); + + Navigator.of(context).pushReplacement( + MaterialPageRoute( + builder: (context) => const HomeScreen(), + ), + ); + } } From 90ca0a5586986e57117628cbe90e55e50ceec147 Mon Sep 17 00:00:00 2001 From: SharkyBytes Date: Fri, 21 Mar 2025 10:20:56 +0530 Subject: [PATCH 4/4] Removed BuildContext usage in providers for better separation of concerns --- .../lib/presentation/auth/auth_screen.dart | 114 +++-------- .../widgets/google_signin_button.dart | 19 +- patient/lib/provider/auth_provider.dart | 55 +++--- .../lib/presentation/auth/auth_screen.dart | 182 +++++++++--------- .../widgets/google_signin_button.dart | 18 +- therapist/lib/provider/auth_provider.dart | 45 ++--- 6 files changed, 188 insertions(+), 245 deletions(-) diff --git a/patient/lib/presentation/auth/auth_screen.dart b/patient/lib/presentation/auth/auth_screen.dart index 7bb4640..c68e481 100644 --- a/patient/lib/presentation/auth/auth_screen.dart +++ b/patient/lib/presentation/auth/auth_screen.dart @@ -1,13 +1,11 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; -import 'package:google_sign_in/google_sign_in.dart'; import 'package:patient/presentation/widgets/google_signin_button.dart'; import '../widgets/welcome_header.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; -import 'package:flutter/foundation.dart' show kIsWeb; -import 'package:patient/presentation/auth/personal_details_screen.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:patient/presentation/auth/personal_details_screen.dart'; class AuthScreen extends StatefulWidget { const AuthScreen({super.key}); @@ -21,6 +19,7 @@ class _AuthScreenState extends State { int _currentPage = 0; late Timer _timer; final supabase = Supabase.instance.client; + StreamSubscription? _authSubscription; final List _contents = [ OnboardingContent( @@ -44,33 +43,36 @@ class _AuthScreenState extends State { void initState() { super.initState(); _startAutoScroll(); - supabase.auth.onAuthStateChange.listen((data) { + _initializeAuthListener(); + } + + void _initializeAuthListener() { + _authSubscription = supabase.auth.onAuthStateChange.listen((data) { final session = supabase.auth.currentSession; if (session != null && mounted) { - final fullName = session.user.userMetadata?['full_name']; - final email = session.user.email ?? 'Unknown User'; - - print(fullName); - print(email); - debugPrint("User authenticated, navigating to PersonalDetailsScreen"); - debugPrint("User authenticated, Helllooooo"); - - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Signed in as ${fullName ?? email}'), - duration: const Duration(seconds: 2), - ), - ); - - - Navigator.of(context).pushReplacement( - MaterialPageRoute( - builder: (context) => const PersonalDetailsScreen(), - ), - ); + WidgetsBinding.instance.addPostFrameCallback((_) { + _handleSuccessfulAuth(session); + }); } }); + } + void _handleSuccessfulAuth(Session session) { + final fullName = session.user.userMetadata?['full_name']; + final email = session.user.email ?? 'Unknown User'; + + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Signed in as ${fullName ?? email}'), + duration: const Duration(seconds: 2), + ), + ); + + Navigator.of(context).pushReplacement( + MaterialPageRoute( + builder: (context) => const PersonalDetailsScreen(), + ), + ); } void _startAutoScroll() { @@ -88,58 +90,11 @@ class _AuthScreenState extends State { }); } - Future _handleGoogleSignIn() async { - try { - final supabaseUrl = dotenv.env['SUPABASE_URL']; - await supabase.auth.signInWithOAuth( - OAuthProvider.google, - redirectTo: kIsWeb - ? "$supabaseUrl/auth/v1/callback" - : 'com.mycompany.cbtdiary://login-callback/', - authScreenLaunchMode: kIsWeb - ? LaunchMode.platformDefault - : LaunchMode.externalApplication, - ); - - // Fetch the session immediately after signing in - final session = await supabase.auth.currentSession; - debugPrint("User authenticated, navigating to PersonalDetailsScreen"); - print(session); - - if (session != null && mounted) { - // Extract user name from Google OAuth metadata - final fullName = session.user.userMetadata?['full_name']; - final email = session.user.email ?? 'Unknown User'; - - print(fullName); - print(email); - // Show toast with user's name - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Signed in as ${fullName ?? email}'), - duration: const Duration(seconds: 2), - ), - ); - - Navigator.of(context).pushReplacement( - MaterialPageRoute( - builder: (context) => const PersonalDetailsScreen(), - ), - ); - } - } catch (error) { - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text('Sign in failed: $error'), - )); - } - } - } - @override void dispose() { _pageController.dispose(); _timer.cancel(); + _authSubscription?.cancel(); super.dispose(); } @@ -156,13 +111,9 @@ class _AuthScreenState extends State { controller: _pageController, itemCount: _contents.length, onPageChanged: (int page) { - setState(() { - _currentPage = page; - }); - }, - itemBuilder: (context, index) { - return _buildCarouselItem(_contents[index]); + setState(() => _currentPage = page); }, + itemBuilder: (context, index) => _buildCarouselItem(_contents[index]), ), Positioned( bottom: 120, @@ -176,12 +127,11 @@ class _AuthScreenState extends State { ), ), ), - Positioned( + const Positioned( bottom: 40, left: 0, right: 0, child: GoogleSignInButton(), - ), ], ), @@ -242,7 +192,7 @@ class OnboardingContent { final String title; final String description; - OnboardingContent({ + const OnboardingContent({ required this.image, required this.title, required this.description, diff --git a/patient/lib/presentation/widgets/google_signin_button.dart b/patient/lib/presentation/widgets/google_signin_button.dart index 0df65bf..a7917b2 100644 --- a/patient/lib/presentation/widgets/google_signin_button.dart +++ b/patient/lib/presentation/widgets/google_signin_button.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:patient/presentation/auth/personal_details_screen.dart'; import 'package:patient/provider/auth_provider.dart'; import 'package:provider/provider.dart'; @@ -13,7 +14,7 @@ class GoogleSignInButton extends StatelessWidget { width: double.infinity, height: 50, child: ElevatedButton( - onPressed: () => context.read().signInWithGoogle(context), + onPressed: () => _handleGoogleSignIn(context), style: ElevatedButton.styleFrom( backgroundColor: Colors.white, shape: RoundedRectangleBorder( @@ -46,4 +47,20 @@ class GoogleSignInButton extends StatelessWidget { ), ); } + + + +Future _handleGoogleSignIn(BuildContext context) async { + final authProvider = context.read(); // Store provider reference + + try { + await authProvider.signInWithGoogle(); // Perform sign-in + final fullName = authProvider.getFullName(); + // print(fullName); + // Fetch full name after sign-in + } catch (error) { + // Handle error (you can log it or handle it elsewhere) + print('Sign-in failed: $error'); + } + } } diff --git a/patient/lib/provider/auth_provider.dart b/patient/lib/provider/auth_provider.dart index 0ea617d..9f1ed6e 100644 --- a/patient/lib/provider/auth_provider.dart +++ b/patient/lib/provider/auth_provider.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:google_sign_in/google_sign_in.dart'; -import 'package:patient/presentation/auth/personal_details_screen.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter_dotenv/flutter_dotenv.dart'; @@ -8,21 +7,30 @@ import 'dart:io' show Platform; class AuthProvider extends ChangeNotifier { final supabase = Supabase.instance.client; - Future signInWithGoogle(BuildContext context) async { + bool _isAuthenticated = false; + + bool get isAuthenticated => _isAuthenticated; + + void login() { + _isAuthenticated = true; + notifyListeners(); + } + + Future signInWithGoogle() async { try { if (kIsWeb) { - await _handleWebSignIn(context); + await _handleWebSignIn(); } else { - await _handleMobileSignIn(context); + await _handleMobileSignIn(); } + _isAuthenticated = true; + notifyListeners(); } catch (error) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Sign in failed: $error')), - ); + throw Exception('Sign in failed: $error'); } } - Future _handleWebSignIn(BuildContext context) async { + Future _handleWebSignIn() async { final supabaseUrl = dotenv.env['SUPABASE_URL'] ?? (throw Exception("Supabase URL not found in .env")); @@ -31,15 +39,13 @@ class AuthProvider extends ChangeNotifier { redirectTo: "$supabaseUrl/auth/v1/callback", authScreenLaunchMode: LaunchMode.platformDefault, ); - - _handlePostSignIn(context); } - Future _handleMobileSignIn(BuildContext context) async { + Future _handleMobileSignIn() async { final webClientId = dotenv.env['GOOGLE_WEB_CLIENT_ID'] ?? (throw Exception("WEB_CLIENT_ID not found in .env")); final iosClientId = dotenv.env['GOOGLE_IOS_CLIENT_ID']; - + final GoogleSignIn googleSignIn = GoogleSignIn( clientId: Platform.isIOS ? iosClientId : null, serverClientId: webClientId, @@ -49,7 +55,7 @@ class AuthProvider extends ChangeNotifier { final GoogleSignInAccount? googleUser = await googleSignIn.signIn(); if (googleUser == null) throw 'Sign in cancelled'; - final GoogleSignInAuthentication googleAuth = + final GoogleSignInAuthentication googleAuth = await googleUser.authentication; if (googleAuth.idToken == null) throw 'No ID Token found'; @@ -60,28 +66,11 @@ class AuthProvider extends ChangeNotifier { idToken: googleAuth.idToken!, accessToken: googleAuth.accessToken, ); - - _handlePostSignIn(context); } - void _handlePostSignIn(BuildContext context) { + String? getFullName() { final session = supabase.auth.currentSession; - if (session == null) return; - - final userMetadata = session.user.userMetadata; - final fullName = userMetadata?['full_name'] ?? 'User'; - - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Welcome $fullName!'), - duration: const Duration(seconds: 2), - ), - ); - - Navigator.of(context).pushReplacement( - MaterialPageRoute( - builder: (context) => const PersonalDetailsScreen(), - ), - ); + if (session == null) return null; + return session.user.userMetadata?['full_name'] ?? 'User'; } } diff --git a/therapist/lib/presentation/auth/auth_screen.dart b/therapist/lib/presentation/auth/auth_screen.dart index 418ab6a..476acbc 100644 --- a/therapist/lib/presentation/auth/auth_screen.dart +++ b/therapist/lib/presentation/auth/auth_screen.dart @@ -3,10 +3,8 @@ import 'dart:async'; import 'package:smooth_page_indicator/smooth_page_indicator.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; -import 'package:flutter/foundation.dart' show kIsWeb; import 'package:therapist/presentation/widgets/google_signin_button.dart'; import '../home/home_screen.dart'; -import 'package:flutter_dotenv/flutter_dotenv.dart'; class AuthScreen extends StatefulWidget { const AuthScreen({super.key}); @@ -20,7 +18,47 @@ class _AuthScreenState extends State { int _currentPage = 0; Timer? _timer; final supabase = Supabase.instance.client; + StreamSubscription? _authSubscription; + @override + void initState() { + super.initState(); + _startAutoScroll(); + _initializeAuthListener(); + } + + void _initializeAuthListener() { + _authSubscription = supabase.auth.onAuthStateChange.listen((data) { + final session = supabase.auth.currentSession; + if (session != null && mounted) { + WidgetsBinding.instance.addPostFrameCallback((_) { + _handleSuccessfulAuth(session); + }); + } + }); + } + + void _handleSuccessfulAuth(Session session) { + final fullName = session.user.userMetadata?['full_name']; + final email = session.user.email ?? 'Unknown User'; + print(fullName); + print(email); + debugPrint("User authenticated, navigating to HomeScreen"); + + + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Signed in as ${fullName ?? email}'), + duration: const Duration(seconds: 2), + ), + ); + + Navigator.of(context).pushReplacement( + MaterialPageRoute( + builder: (context) => const HomeScreen(), + ), + ); + } void _startAutoScroll() { _timer = Timer.periodic(const Duration(seconds: 3), (Timer timer) { @@ -37,43 +75,11 @@ class _AuthScreenState extends State { }); } - @override - void initState() { - super.initState(); - _startAutoScroll(); - supabase.auth.onAuthStateChange.listen((data) { - final session = supabase.auth.currentSession; - if (session != null && mounted) { - final fullName = session.user.userMetadata?['full_name']; - final email = session.user.email ?? 'Unknown User'; - - print(fullName); - print(email); - debugPrint("User authenticated, navigating to PersonalDetailsScreen"); - debugPrint("User authenticated, Helllooooo"); - - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Signed in as ${fullName ?? email}'), - duration: const Duration(seconds: 2), - ), - ); - - - Navigator.of(context).pushReplacement( - MaterialPageRoute( - builder: (context) => const HomeScreen(), - ), - ); - } - }); - - } - @override void dispose() { _timer?.cancel(); _pageController.dispose(); + _authSubscription?.cancel(); super.dispose(); } @@ -85,77 +91,26 @@ class _AuthScreenState extends State { return Scaffold( body: Column( children: [ - Stack( - children: [ - Container( - width: double.infinity, - height: screenHeight * 0.2, - decoration: const BoxDecoration( - color: Color(0xFFB066E4), - borderRadius: BorderRadius.only( - bottomRight: Radius.circular(200), - ), - ), - ), - Positioned( - top: screenHeight * 0.07, - left: screenWidth * 0.08, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Welcome To,", - style: GoogleFonts.poppins( - fontSize: 16, - color: Colors.white, - ), - ), - const SizedBox(height: 2), - Text( - "Therapy App", - style: GoogleFonts.poppins( - fontSize: 26, - fontWeight: FontWeight.bold, - color: Colors.white, - ), - ), - ], - ), - ), - ], - ), - - // Carousel Section + _buildHeader(screenWidth, screenHeight), Expanded( child: PageView( controller: _pageController, children: [ _buildPage( - title: "Welcome To", - appName: "Therapy App", - description: - "Effortlessly manage patients & enhance therapy progress.", + description: "Effortlessly manage patients & enhance therapy progress.", image: "assets/manage_patients.png", ), _buildPage( - title: "Daily Activities", - appName: "Stay on Track", - description: - "Personalized Daily Activities, Tracked Effortlessly!", + description: "Personalized Daily Activities, Tracked Effortlessly!", image: "assets/daily_activities.png", ), _buildPage( - title: "Health Tracking", - appName: "Monitor Progress", - description: - "Track your health and therapy goals effectively.", + description: "Track your health and therapy goals effectively.", image: "assets/health_tracking.png", ), ], ), ), - - // Page Indicator SmoothPageIndicator( controller: _pageController, count: 3, @@ -166,9 +121,6 @@ class _AuthScreenState extends State { ), ), const SizedBox(height: 20), - - - // Google Sign-In Button Padding( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), child: GoogleSignInButton(), @@ -179,9 +131,49 @@ class _AuthScreenState extends State { ); } + Widget _buildHeader(double screenWidth, double screenHeight) { + return Stack( + children: [ + Container( + width: double.infinity, + height: screenHeight * 0.2, + decoration: const BoxDecoration( + color: Color(0xFFB066E4), + borderRadius: BorderRadius.only( + bottomRight: Radius.circular(200), + ), + ), + ), + Positioned( + top: screenHeight * 0.07, + left: screenWidth * 0.08, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Welcome To,", + style: GoogleFonts.poppins( + fontSize: 16, + color: Colors.white, + ), + ), + const SizedBox(height: 2), + Text( + "Therapy App", + style: GoogleFonts.poppins( + fontSize: 26, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + ], + ), + ), + ], + ); + } + Widget _buildPage({ - required String title, - required String appName, required String description, required String image, }) { diff --git a/therapist/lib/presentation/widgets/google_signin_button.dart b/therapist/lib/presentation/widgets/google_signin_button.dart index 8a5186c..f7538a8 100644 --- a/therapist/lib/presentation/widgets/google_signin_button.dart +++ b/therapist/lib/presentation/widgets/google_signin_button.dart @@ -12,7 +12,7 @@ class GoogleSignInButton extends StatelessWidget { width: double.infinity, height: 50, child: ElevatedButton( - onPressed: () => context.read().signInWithGoogle(context), + onPressed: () => _handleGoogleSignIn(context), style: ElevatedButton.styleFrom( backgroundColor: Colors.white, shape: RoundedRectangleBorder( @@ -45,4 +45,20 @@ class GoogleSignInButton extends StatelessWidget { ), ); } + +Future _handleGoogleSignIn(BuildContext context) async { + final authProvider = context.read(); // Store provider reference + + try { + await authProvider.signInWithGoogle(); // Perform sign-in + final fullName = authProvider.getFullName(); + // print(fullName); + // Fetch full name after sign-in + } catch (error) { + // Handle error (you can log it or handle it elsewhere) + print('Sign-in failed: $error'); + } + } } + + diff --git a/therapist/lib/provider/auth_provider.dart b/therapist/lib/provider/auth_provider.dart index ef707d2..a994ce8 100644 --- a/therapist/lib/provider/auth_provider.dart +++ b/therapist/lib/provider/auth_provider.dart @@ -4,12 +4,10 @@ import 'package:supabase_flutter/supabase_flutter.dart'; import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'dart:io' show Platform; -import 'package:therapist/presentation/home/home_screen.dart'; class AuthProvider extends ChangeNotifier { - final supabase = Supabase.instance.client; - bool _isAuthenticated = false; + bool _isAuthenticated = false; bool get isAuthenticated => _isAuthenticated; @@ -18,21 +16,21 @@ class AuthProvider extends ChangeNotifier { notifyListeners(); } - Future signInWithGoogle(BuildContext context) async { + Future signInWithGoogle() async { try { if (kIsWeb) { - await _handleWebSignIn(context); + await _handleWebSignIn(); } else { - await _handleMobileSignIn(context); + await _handleMobileSignIn(); } + _isAuthenticated = true; + notifyListeners(); } catch (error) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Sign in failed: $error')), - ); + throw Exception('Sign in failed: $error'); } } - Future _handleWebSignIn(BuildContext context) async { + Future _handleWebSignIn() async { final supabaseUrl = dotenv.env['SUPABASE_URL'] ?? (throw Exception("Supabase URL not found in .env")); @@ -41,11 +39,9 @@ class AuthProvider extends ChangeNotifier { redirectTo: "$supabaseUrl/auth/v1/callback", authScreenLaunchMode: LaunchMode.platformDefault, ); - - _handlePostSignIn(context); } - Future _handleMobileSignIn(BuildContext context) async { + Future _handleMobileSignIn() async { final webClientId = dotenv.env['GOOGLE_WEB_CLIENT_ID'] ?? (throw Exception("WEB_CLIENT_ID not found in .env")); final iosClientId = dotenv.env['GOOGLE_IOS_CLIENT_ID']; @@ -70,28 +66,11 @@ class AuthProvider extends ChangeNotifier { idToken: googleAuth.idToken!, accessToken: googleAuth.accessToken, ); - - _handlePostSignIn(context); } - void _handlePostSignIn(BuildContext context) { + String? getFullName() { final session = supabase.auth.currentSession; - if (session == null) return; - - final userMetadata = session.user.userMetadata; - final fullName = userMetadata?['full_name'] ?? 'User'; - - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Welcome $fullName!'), - duration: const Duration(seconds: 2), - ), - ); - - Navigator.of(context).pushReplacement( - MaterialPageRoute( - builder: (context) => const HomeScreen(), - ), - ); + if (session == null) return null; + return session.user.userMetadata?['full_name'] ?? 'User'; } }