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..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" - 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/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/main.dart b/patient/lib/main.dart index cf2442a..82d99b1 100644 --- a/patient/lib/main.dart +++ b/patient/lib/main.dart @@ -2,11 +2,15 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; 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:patient/provider/reports_provider.dart'; + import 'package:provider/provider.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; -import 'presentation/splash_screen.dart'; -import 'provider/assessment_provider.dart'; import 'provider/task_provider.dart'; Future main() async { @@ -30,8 +34,10 @@ Future main() async { MultiProvider( providers: [ ChangeNotifierProvider(create: (_) => AssessmentProvider()), + ChangeNotifierProvider(create: (_) => AuthProvider()), ChangeNotifierProvider(create: (_) => ReportsProvider()), ChangeNotifierProvider(create: (_) => TaskProvider()), + ], child: const MyApp(), ), diff --git a/patient/lib/presentation/auth/auth_screen.dart b/patient/lib/presentation/auth/auth_screen.dart index c001afc..f66f0f0 100644 --- a/patient/lib/presentation/auth/auth_screen.dart +++ b/patient/lib/presentation/auth/auth_screen.dart @@ -3,8 +3,11 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; -import '../widgets/google_signin_button.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_dotenv/flutter_dotenv.dart'; +import 'package:patient/presentation/auth/personal_details_screen.dart'; class AuthScreen extends StatefulWidget { @@ -18,6 +21,8 @@ class _AuthScreenState extends State { final PageController _pageController = PageController(initialPage: 0); int _currentPage = 0; late Timer _timer; + final supabase = Supabase.instance.client; + StreamSubscription? _authSubscription; final List _contents = [ OnboardingContent( @@ -27,8 +32,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', @@ -41,6 +46,36 @@ class _AuthScreenState extends State { 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'; + + 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() { @@ -62,6 +97,7 @@ class _AuthScreenState extends State { void dispose() { _pageController.dispose(); _timer.cancel(); + _authSubscription?.cancel(); super.dispose(); } @@ -70,9 +106,7 @@ class _AuthScreenState extends State { return Scaffold( body: Column( children: [ - const WelcomeHeader(), - Expanded( child: Stack( children: [ @@ -80,13 +114,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( @@ -143,7 +173,8 @@ class _AuthScreenState extends State { ), ), ), - const SizedBox(height: 60), + const SizedBox(height: 60), + ], ); } @@ -167,7 +198,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/splash_screen.dart b/patient/lib/presentation/splash_screen.dart index 8a1bac5..f0d4af7 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/lib/presentation/widgets/google_signin_button.dart b/patient/lib/presentation/widgets/google_signin_button.dart index 6cdafae..7ba64ad 100644 --- a/patient/lib/presentation/widgets/google_signin_button.dart +++ b/patient/lib/presentation/widgets/google_signin_button.dart @@ -1,44 +1,44 @@ 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'; + import '../auth/personal_details_screen.dart'; + class GoogleSignInButton extends StatelessWidget { - const GoogleSignInButton({super.key}); + const GoogleSignInButton({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Padding( - padding: - const EdgeInsets.symmetric(horizontal: 20), + padding: const EdgeInsets.symmetric(horizontal: 20), child: SizedBox( - width: double.infinity, - height: 50, + width: double.infinity, + height: 50, child: ElevatedButton( - onPressed: () { - // Implement Google Sign-in logic here - Navigator.push( - context, - MaterialPageRoute(builder: (context) => const PersonalDetailsScreen()), - ); - }, + onPressed: () => _handleGoogleSignIn(context), + style: ElevatedButton.styleFrom( backgroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(30), ), - elevation: 2, + elevation: 2, padding: const EdgeInsets.symmetric(vertical: 12), ), child: Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ Image.asset( 'assets/google_logo.png', height: 24, width: 24, ), - const SizedBox(width: 10), + const SizedBox(width: 10), const Text( 'Continue with Google', style: TextStyle( @@ -53,4 +53,20 @@ class GoogleSignInButton extends StatelessWidget { ), ); } -} \ No newline at end of file + + + +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 7a3221e..9f1ed6e 100644 --- a/patient/lib/provider/auth_provider.dart +++ b/patient/lib/provider/auth_provider.dart @@ -1,7 +1,76 @@ -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: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; + bool _isAuthenticated = false; + + bool get isAuthenticated => _isAuthenticated; + + void login() { + _isAuthenticated = true; + notifyListeners(); + } + + Future signInWithGoogle() async { + try { + if (kIsWeb) { + await _handleWebSignIn(); + } else { + await _handleMobileSignIn(); + } + _isAuthenticated = true; + notifyListeners(); + } catch (error) { + throw Exception('Sign in failed: $error'); + } + } + + Future _handleWebSignIn() 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, + ); + } + + 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, + 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, + ); + } + + String? getFullName() { + final session = supabase.auth.currentSession; + if (session == null) return null; + return session.user.userMetadata?['full_name'] ?? 'User'; + } +} 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/.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/assets/google_logo.png b/therapist/assets/google_logo.png index 9eb00f8..d9d3635 100644 Binary files a/therapist/assets/google_logo.png and b/therapist/assets/google_logo.png differ diff --git a/therapist/assets/illustration.png b/therapist/assets/illustration.png new file mode 100644 index 0000000..594b31e Binary files /dev/null and b/therapist/assets/illustration.png differ diff --git a/therapist/assets/illustration2.png b/therapist/assets/illustration2.png new file mode 100644 index 0000000..3f85964 Binary files /dev/null and b/therapist/assets/illustration2.png differ diff --git a/therapist/assets/logo.png b/therapist/assets/logo.png new file mode 100644 index 0000000..c173279 Binary files /dev/null and b/therapist/assets/logo.png differ 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/main.dart b/therapist/lib/main.dart index 00892ca..ca39e5e 100644 --- a/therapist/lib/main.dart +++ b/therapist/lib/main.dart @@ -1,8 +1,17 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; + +import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:provider/provider.dart'; + +import 'package:supabase_flutter/supabase_flutter.dart'; +import 'package:therapist/presentation/splash_screen.dart'; + +import './presentation/auth/auth_screen.dart'; + import 'package:therapist/core/theme/theme.dart'; import './provider/session_provider.dart'; + import './provider/auth_provider.dart'; import './provider/home_provider.dart'; import './provider/therapist_provider.dart'; @@ -11,11 +20,31 @@ import './repository/supabase_consultation_repository.dart'; import './presentation/home/home_screen.dart'; import './presentation/widget/splash_screen.dart'; -void main() { +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.transparent, + 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(), ), ); @@ -32,35 +61,12 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { - return MultiProvider( - providers: [ - ChangeNotifierProvider(create: (context) => SessionProvider()), - ChangeNotifierProvider(create: (context) => AuthProvider()), - ChangeNotifierProvider(create: (context) => HomeProvider()), - ChangeNotifierProvider(create: (context) => TherapistDataProvider()), - // Add the consultation provider - ChangeNotifierProvider( - create: (context) => ConsultationProvider( - SupabaseConsultationRepository(supabaseClient: null), - )..fetchConsultationRequests(), - ), - ], - child: TherapyApp(), - ); - ); -} - -class TherapyApp extends StatelessWidget { - @override - Widget build(BuildContext context) { - // Get authentication state - final authProvider = Provider.of(context); - return MaterialApp( - title: 'Therapist Dashboard', debugShowCheckedModeBanner: false, - theme: AppTheme.lightTheme(), // Using your theme - home: authProvider.isAuthenticated ? const HomeScreen() : SplashScreen(), + title: 'Therapist App', + theme: ThemeData.light(), + home: const SplashScreen(), ); - } -} \ No newline at end of file + +} + diff --git a/therapist/lib/presentation/auth/auth_screen.dart b/therapist/lib/presentation/auth/auth_screen.dart index 181175b..476acbc 100644 --- a/therapist/lib/presentation/auth/auth_screen.dart +++ b/therapist/lib/presentation/auth/auth_screen.dart @@ -1,196 +1,196 @@ -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:flutter_svg/flutter_svg.dart'; -import 'package:provider/provider.dart'; -import '../home/home_screen.dart'; - -class AuthScreen extends StatefulWidget { - @override - _AuthScreenState createState() => _AuthScreenState(); -} - -class _AuthScreenState extends State { - final PageController _pageController = PageController(); - int _currentPage = 0; - Timer? _timer; - - @override - void initState() { - super.initState(); - _startAutoScroll(); // Start auto-scrolling animation - } - - void _startAutoScroll() { - _timer = Timer.periodic(Duration(seconds: 3), (Timer timer) { - if (_currentPage < 3) { - _currentPage++; - } else { - _currentPage = 0; // Loop back to the first page - } - _pageController.animateToPage( - _currentPage, - duration: Duration(milliseconds: 500), - curve: Curves.easeInOut, - ); - }); - } - - @override - void dispose() { - _timer?.cancel(); // Cancel the timer when leaving the screen - _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( - bottomLeft: Radius.circular(0), - 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: WormEffect( - dotHeight: 8, dotWidth: 8, activeDotColor: Colors.purple), - ), - 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: EdgeInsets.symmetric(vertical: 12, horizontal: 24), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(30), - ), - ), - onPressed: () { - Navigator.pushReplacement( - context, - MaterialPageRoute(builder: (context) => HomeScreen()), - ); - }, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Image.asset("assets/google_logo.png", height: 24), - SizedBox(width: 10), - Text( - "Continue with Google", - style: TextStyle(fontSize: 16, color: Colors.black), - ), - ], - ), - ), - ), - 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: [ - - - // Illustration - Image.asset(image, height: 250), - SizedBox(height: 20), - - // Description - Text( - description, - textAlign: TextAlign.center, - style: TextStyle(fontSize: 16, color: Colors.black54), - ), - ], - ), - ); - } -} +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:therapist/presentation/widgets/google_signin_button.dart'; +import '../home/home_screen.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; + 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) { + if (_currentPage < 2) { + _currentPage++; + } else { + _currentPage = 0; + } + _pageController.animateToPage( + _currentPage, + duration: const Duration(milliseconds: 500), + curve: Curves.easeInOut, + ); + }); + } + + @override + void dispose() { + _timer?.cancel(); + _pageController.dispose(); + _authSubscription?.cancel(); + 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: [ + _buildHeader(screenWidth, screenHeight), + Expanded( + child: PageView( + controller: _pageController, + children: [ + _buildPage( + description: "Effortlessly manage patients & enhance therapy progress.", + image: "assets/manage_patients.png", + ), + _buildPage( + description: "Personalized Daily Activities, Tracked Effortlessly!", + image: "assets/daily_activities.png", + ), + _buildPage( + description: "Track your health and therapy goals effectively.", + image: "assets/health_tracking.png", + ), + ], + ), + ), + SmoothPageIndicator( + controller: _pageController, + count: 3, + effect: const WormEffect( + dotHeight: 8, + dotWidth: 8, + activeDotColor: Colors.purple, + ), + ), + const SizedBox(height: 20), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + child: GoogleSignInButton(), + ), + const SizedBox(height: 20), + ], + ), + ); + } + + 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 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/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..f7538a8 --- /dev/null +++ b/therapist/lib/presentation/widgets/google_signin_button.dart @@ -0,0 +1,64 @@ +import 'package:flutter/material.dart'; +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) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: SizedBox( + width: double.infinity, + height: 50, + child: ElevatedButton( + onPressed: () => _handleGoogleSignIn(context), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(30), + ), + elevation: 2, + padding: const EdgeInsets.symmetric(vertical: 12), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset( + 'assets/google_logo.png', + height: 24, + width: 24, + ), + const SizedBox(width: 10), + const Text( + 'Continue with Google', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Colors.black87, + ), + ), + ], + ), + ), + ), + ); + } + +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/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 f5a2716..a994ce8 100644 --- a/therapist/lib/provider/auth_provider.dart +++ b/therapist/lib/provider/auth_provider.dart @@ -1,6 +1,12 @@ 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 AuthProvider extends ChangeNotifier { + final supabase = Supabase.instance.client; bool _isAuthenticated = false; bool get isAuthenticated => _isAuthenticated; @@ -9,4 +15,62 @@ class AuthProvider extends ChangeNotifier { _isAuthenticated = true; notifyListeners(); } + + Future signInWithGoogle() async { + try { + if (kIsWeb) { + await _handleWebSignIn(); + } else { + await _handleMobileSignIn(); + } + _isAuthenticated = true; + notifyListeners(); + } catch (error) { + throw Exception('Sign in failed: $error'); + } + } + + Future _handleWebSignIn() 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, + ); + } + + 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, + 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, + ); + } + + String? getFullName() { + final session = supabase.auth.currentSession; + if (session == null) return null; + return session.user.userMetadata?['full_name'] ?? 'User'; + } } 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 121b623..e2270c4 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: @@ -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: @@ -278,6 +294,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: @@ -286,8 +310,17 @@ 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 dev" + dependency: "direct main" description: name: flutter_svg sha256: c200fd79c918a40c5cd50ea0877fa13f81bdaf6f0a5d3dbcc2a13e3285d6aa1b @@ -329,13 +362,61 @@ packages: source: hosted version: "2.1.3" google_fonts: - dependency: "direct dev" + 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: @@ -360,6 +441,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: @@ -384,14 +473,16 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.2" - intl: + + image: dependency: transitive description: - name: intl - sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf + name: image + sha256: "13d3349ace88f12f4a0d175eb5c12dcdd39d35c4c109a8a13dfeb6d0bd9e31c3" url: "https://pub.dev" source: hosted - version: "0.19.0" + version: "4.5.3" + io: dependency: transitive description: @@ -608,6 +699,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: @@ -742,7 +841,7 @@ packages: source: sdk version: "0.0.0" smooth_page_indicator: - dependency: "direct dev" + dependency: "direct main" description: name: smooth_page_indicator sha256: b21ebb8bc39cf72d11c7cfd809162a48c3800668ced1c9da3aade13a32cf6c1c @@ -861,6 +960,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: diff --git a/therapist/pubspec.yaml b/therapist/pubspec.yaml index 82756a8..6c88051 100644 --- a/therapist/pubspec.yaml +++ b/therapist/pubspec.yaml @@ -40,6 +40,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 # intl intl: ^0.17.0 @@ -56,6 +61,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 @@ -76,6 +82,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); + }); +}