From 490f90c7f27e39bc34d9e411b414b7a077bbfb6d Mon Sep 17 00:00:00 2001 From: rohitarer Date: Fri, 8 Nov 2024 03:20:45 +0530 Subject: [PATCH] 1. Implemented Skip button functionality to redirect to the login page. 2. Added adaptive WillPopScope to exit the app instead of returning to the onboarding screen. 3. Enhanced UI of Signup and Login pages for better user experience. --- README.md | 39 +++ android/app/build.gradle | 4 + android/app/google-services.json | 32 ++- android/build.gradle | 39 ++- lib/main.dart | 95 ++++--- .../onboarding/onboarding_controller.dart | 75 +++-- lib/views/pages/login/login.dart | 130 +++++++-- lib/views/pages/main_home/homepage.dart | 163 ++++++----- lib/views/pages/onboarding/onboarding.dart | 44 +-- .../widgets/onboarding_dot_navigation.dart | 17 +- .../widgets/onboarding_next_button.dart | 23 +- .../onboarding/widgets/onboarding_page.dart | 52 ++-- .../onboarding/widgets/onboarding_skip.dart | 30 +- lib/views/pages/register/signup.dart | 257 ++++++++++++++---- pubspec.lock | 8 + pubspec.yaml | 2 + 16 files changed, 687 insertions(+), 323 deletions(-) diff --git a/README.md b/README.md index 7b86cd9..2bc665a 100644 --- a/README.md +++ b/README.md @@ -212,3 +212,42 @@ To know more [Click Here](LICENSE.md) +

Firebase Setup Guide

+
    +
  1. + Login to Firebase Console +

    Go to the Firebase Console and log in with your Google account.

    +
  2. +
  3. + Setup for Android Version +

    In the Firebase Console, create a new project and follow the instructions to set up Firebase for Android.

    +
  4. +
  5. + Download google-services.json +

    Download the google-services.json file from the Firebase console and add it under the android/app folder in your Flutter project.

    +
  6. +
  7. + Configure build.gradle +

    Open the android/build.gradle file and add the following classpath under the dependencies section:

    +
    +classpath 'com.google.gms:google-services:4.3.15'  
    +            
    +

    Then, open the android/app/build.gradle file and add the following line at the bottom:

    +
    +apply plugin: 'com.google.gms.google-services'
    +            
    +
  8. +
  9. + Setup Firestore for Database +

    In the Firebase console, go to the Firestore section and create a Firestore database for your project. Follow the on-screen instructions to choose between production or test mode.

    +
  10. +
  11. + Setup Authentication +

    In the Firebase console, go to the Authentication section and enable the following sign-in methods:

    +
      +
    • Email/Password
    • +
    • Google
    • +
    +

    Make sure to configure the necessary credentials and settings for both sign-in methods.

    +
  12. +
\ No newline at end of file diff --git a/android/app/build.gradle b/android/app/build.gradle index 797315d..5283f1e 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -46,3 +46,7 @@ android { flutter { source = "../.." } + +dependencies { + implementation platform('com.google.firebase:firebase-bom:33.5.1') +} \ No newline at end of file diff --git a/android/app/google-services.json b/android/app/google-services.json index 8b7451f..269a029 100644 --- a/android/app/google-services.json +++ b/android/app/google-services.json @@ -1,26 +1,44 @@ { "project_info": { - "project_number": "445023469277", - "project_id": "donor-connect-project", - "storage_bucket": "donor-connect-project.appspot.com" + "project_number": "255205177926", + "project_id": "donorconnect-40490", + "storage_bucket": "donorconnect-40490.firebasestorage.app" }, "client": [ { "client_info": { - "mobilesdk_app_id": "1:445023469277:android:867d6fc40fb1d859a52534", + "mobilesdk_app_id": "1:255205177926:android:eae013c60c9efa933c6847", "android_client_info": { "package_name": "com.example.donorconnect" } }, - "oauth_client": [], + "oauth_client": [ + { + "client_id": "255205177926-q7chqmo3b2sh6pmcnnfketcledt3d9jb.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "com.example.donorconnect", + "certificate_hash": "c645004972e2e67b6e8fb81cdbff026bad48ab47" + } + }, + { + "client_id": "255205177926-hd9rrs0i3ti9s2s4bchij1tk8263k8u3.apps.googleusercontent.com", + "client_type": 3 + } + ], "api_key": [ { - "current_key": "AIzaSyDprpAsw0AkuQmFG1Iczpb9N2gghyAFmqo" + "current_key": "AIzaSyAaf-WvXzjym1yZ2umggxp5nuWyWM3OcJ0" } ], "services": { "appinvite_service": { - "other_platform_oauth_client": [] + "other_platform_oauth_client": [ + { + "client_id": "255205177926-hd9rrs0i3ti9s2s4bchij1tk8263k8u3.apps.googleusercontent.com", + "client_type": 3 + } + ] } } } diff --git a/android/build.gradle b/android/build.gradle index d2ffbff..b72df73 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,3 +1,39 @@ +// allprojects { +// repositories { +// google() +// mavenCentral() +// } +// dependencies { +// // classpath 'com.android.tools.build:gradle:7.0.0' // Use the correct Android Gradle Plugin version here +// classpath 'com.google.gms:google-services:4.4.2' +// } +// } + + + +// rootProject.buildDir = "../build" +// subprojects { +// project.buildDir = "${rootProject.buildDir}/${project.name}" +// } +// subprojects { +// project.evaluationDependsOn(":app") +// } + +// tasks.register("clean", Delete) { +// delete rootProject.buildDir +// } + +buildscript { + repositories { + google() + mavenCentral() + } + dependencies { + classpath 'com.android.tools.build:gradle:7.0.0' // Ensure you are using the correct version + classpath 'com.google.gms:google-services:4.4.2' + } +} + allprojects { repositories { google() @@ -5,11 +41,10 @@ allprojects { } } +// Configure root project build directory and tasks rootProject.buildDir = "../build" subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" -} -subprojects { project.evaluationDependsOn(":app") } diff --git a/lib/main.dart b/lib/main.dart index 0037f09..09f3768 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -9,6 +9,7 @@ import 'package:donorconnect/language/cubit/language_cubit.dart'; import 'package:donorconnect/language/helper/language.dart'; import 'package:donorconnect/language/services/language_repositoty.dart'; import 'package:donorconnect/services/blood_bank_service.dart'; +import 'package:donorconnect/views/pages/login/login.dart'; import 'package:donorconnect/views/pages/main_home/homepage.dart'; import 'package:donorconnect/views/pages/onboarding/onboarding.dart'; import 'package:donorconnect/views/pages/welcome/welcome_screen.dart'; @@ -18,36 +19,52 @@ import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:get/get_navigation/src/root/get_material_app.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:jwt_decoder/jwt_decoder.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -// import 'package:riverpod/riverpod.dart'; -import 'package:flutter_dotenv/flutter_dotenv.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); - await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); + + try { + await Firebase.initializeApp( + options: DefaultFirebaseOptions.currentPlatform, + ); + } catch (e) { + print("Firebase initialization error: $e"); + } + await LanguageRepository.init(); SharedPreferences prefs = await SharedPreferences.getInstance(); - ErrorWidget.builder = (FlutterErrorDetails details) { - return const Material(); - }; await dotenv.load(fileName: '.env'); + + // Check if the user has completed onboarding and if they are logged in + bool onboardingCompleted = prefs.getBool('onboardingCompleted') ?? false; + String? token = prefs.getString('token'); + bool isLoggedIn = token != null && !JwtDecoder.isExpired(token); + + // Lock orientation SystemChrome.setPreferredOrientations([ DeviceOrientation.portraitUp, DeviceOrientation.portraitDown, ]); + + // Initialize the app runApp(MyApp( - token: prefs.getString('token'), + token: isLoggedIn ? token : null, + onboardingCompleted: onboardingCompleted, )); } class MyApp extends StatelessWidget { final String? token; + final bool onboardingCompleted; const MyApp({ required this.token, + required this.onboardingCompleted, super.key, }); @@ -55,49 +72,39 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MultiBlocProvider( providers: [ + BlocProvider(create: (context) => ProfileCubit()), BlocProvider( - create: (context) => ProfileCubit(), - ), - BlocProvider( - create: (context) => AuthCubit( - FirebaseAuth.instance, - FirebaseFirestore.instance, - ), - ), - BlocProvider( - create: (context) => LocateBloodBanksCubit(BloodBankService()), - ), - BlocProvider( - create: (context) => LanguageCubit()..initilize(), - ), + create: (context) => + AuthCubit(FirebaseAuth.instance, FirebaseFirestore.instance)), BlocProvider( - create: (context) => ThemeCubit()..setInitialTheme(), - ), + create: (context) => LocateBloodBanksCubit(BloodBankService())), + BlocProvider(create: (context) => LanguageCubit()..initilize()), + BlocProvider(create: (context) => ThemeCubit()..setInitialTheme()), ], child: BlocBuilder( builder: (context, themeState) { return BlocBuilder( - builder: (context, languageState) { - return GetMaterialApp( - localizationsDelegates: AppLocalizations.localizationsDelegates, - supportedLocales: AppLocalizations.supportedLocales, - locale: Locale(languageState.languageCode), - //theme - // themeMode: themeState.themeData, - theme: themeState.themeData, - darkTheme: ThemeData.dark(), - debugShowCheckedModeBanner: false, - // Main route selection - home: (token != null && !JwtDecoder.isExpired(token!)) - ? HomePage(token: token!) - : const OnBoardingScreen(), - // You can add routes for the verification form - routes: { - '/verification': (context) => - const VerificationForm(), // Add route for verification form - }, - ); - }); + builder: (context, languageState) { + return GetMaterialApp( + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, + locale: Locale(languageState.languageCode), + theme: themeState.themeData, + darkTheme: ThemeData.dark(), + debugShowCheckedModeBanner: false, + home: onboardingCompleted + ? (token != null + ? HomePage( + token: + token!) // Directly navigate to home page if logged in + : const LoginPage()) // Navigate to login if not logged in + : const OnBoardingScreen(), // Navigate to onboarding if not completed + routes: { + '/verification': (context) => const VerificationForm(), + }, + ); + }, + ); }, ), ); diff --git a/lib/views/controllers/onboarding/onboarding_controller.dart b/lib/views/controllers/onboarding/onboarding_controller.dart index 9f61987..df21b97 100644 --- a/lib/views/controllers/onboarding/onboarding_controller.dart +++ b/lib/views/controllers/onboarding/onboarding_controller.dart @@ -1,55 +1,52 @@ - -import 'package:flutter/cupertino.dart'; -import 'package:flutter/foundation.dart'; import 'package:get/get.dart'; +import 'package:flutter/material.dart'; +import 'package:donorconnect/views/pages/login/login.dart'; // Import LoginPage import 'package:get_storage/get_storage.dart'; - -import '../../pages/login/login.dart'; - +import 'package:flutter/foundation.dart'; class OnBoardingController extends GetxController { - static OnBoardingController get instance => Get.find(); + // Page controller for the PageView + var pageController = PageController(); - /// Variable - final pageController = PageController(); - // final currentPageIndex = 0.obs; alternate method below + // Observable to track the current page index Rx currentPageIndex = 0.obs; -/// Update Current Index when page Scroll - void updatePageIndicator(index) => currentPageIndex.value = index; -/// Jump to the specific dot selected page. -void dotNavigationClick(index) { - currentPageIndex.value = index; - pageController.jumpTo(index); -} + // Update the page indicator when the page changes + void updatePageIndicator(int index) { + currentPageIndex.value = index; + } -/// Update Current Index & jump to next page -void nextPage() { - if(currentPageIndex.value == 2){ - final storage = GetStorage(); + // Method to navigate to a specific page based on dot navigation + void dotNavigationClick(int index) { + currentPageIndex.value = index; + pageController.jumpToPage(index); + } - if(kDebugMode){ - print('===================== GET STORAGE =============='); - print(storage.read('IsFirstTime')); - } + // Method to navigate to the next page or to the login page if it's the last page + void nextPage() { + if (currentPageIndex.value == 2) { + final storage = GetStorage(); - storage.write('IsFirstTime', false); + // Update the storage to indicate the user has completed onboarding + storage.write('IsFirstTime', false); - if(kDebugMode){ - print('===================== GET STORAGE =============='); - print(storage.read('IsFirstTime')); + // Navigate to the LoginPage + Get.offAll(const LoginPage()); + } else { + // Move to the next page + int nextPage = currentPageIndex.value + 1; + pageController.jumpToPage(nextPage); } + } - Get.offAll(const LoginPage()); - } else{ - int page = currentPageIndex.value + 1; - pageController.jumpToPage(page); + // Skip the onboarding process and jump to the last page (LoginPage) + void skipPage() { + currentPageIndex.value = 2; + pageController.jumpToPage(2); } -} -/// Update current index & jump to the last Page -void skipPage() { - currentPageIndex.value = 2; - pageController.jumpTo(2); + // Method to go to the login page directly + void goToLoginPage() { + Get.to(() => const LoginPage()); // Navigate to LoginPage + } } -} \ No newline at end of file diff --git a/lib/views/pages/login/login.dart b/lib/views/pages/login/login.dart index 0bce747..4ed9b54 100644 --- a/lib/views/pages/login/login.dart +++ b/lib/views/pages/login/login.dart @@ -137,47 +137,117 @@ class _LoginPageState extends State { // Name Column( children: [ - Textbox( - controller: emailController, - obscureText: false, - icons: Icons.email, - name: _text.email, - errormsg: _isValidate - ? _text.email_field_error_text - : null, + Container( + decoration: BoxDecoration( + color: Colors.white, // White background + borderRadius: + BorderRadius.circular(10), // Rounded corners + boxShadow: [ + BoxShadow( + color: Colors.black + .withOpacity(0.1), // Subtle shadow effect + blurRadius: 8, // Soft shadow + offset: Offset(0, 4), // Shadow position + ), + ], + ), + child: TextField( + controller: emailController, + obscureText: false, + decoration: InputDecoration( + prefixIcon: Icon(Icons.email, + color: Colors.green.shade900), + hintText: 'Email', + hintStyle: + TextStyle(color: Colors.green.shade900), + errorText: _isValidate + ? _text.email_field_error_text + : null, + border: + InputBorder.none, // Remove default border + contentPadding: EdgeInsets.symmetric( + horizontal: 16, + vertical: 12), // Padding inside the field + ), + ), ), // PASSWORD SizedBox( height: screenHeight * 0.02, ), - Textbox( - controller: passwordController, - obscureText: !_isPasswordVisible, - icons: Icons.lock, - name: _text.password, - errormsg: - _isValidate ? _text.password_error_text : null, - suffixIcon: IconButton( - icon: Icon( - _isPasswordVisible - ? Icons.visibility - : Icons.visibility_off, + + Container( + decoration: BoxDecoration( + color: Colors.white, // White background + borderRadius: + BorderRadius.circular(10), // Rounded corners + boxShadow: [ + BoxShadow( + color: Colors.black + .withOpacity(0.1), // Subtle shadow effect + blurRadius: 8, // Soft shadow + offset: Offset(0, 4), // Shadow position + ), + ], + ), + child: TextField( + controller: passwordController, + obscureText: + !_isPasswordVisible, // Password visibility control + decoration: InputDecoration( + prefixIcon: Icon(Icons.lock, + color: Colors.green.shade900), + hintText: 'Password', + hintStyle: + TextStyle(color: Colors.green.shade900), + errorText: _isValidate + ? _text.password_error_text + : null, + border: + InputBorder.none, // Remove default border + contentPadding: EdgeInsets.symmetric( + horizontal: 16, + vertical: 12), // Padding inside the field + suffixIcon: IconButton( + icon: Icon( + _isPasswordVisible + ? Icons.visibility + : Icons.visibility_off, + color: Colors.black, + ), + onPressed: () { + setState(() { + _isPasswordVisible = + !_isPasswordVisible; // Toggle password visibility + }); + }, + ), ), - onPressed: () { - setState(() { - _isPasswordVisible = !_isPasswordVisible; - }); - }, ), ), ], ), // FORGOT PASSWORD BUTTON + + SizedBox( + height: screenHeight * 0.01, + ), Padding( - padding: EdgeInsets.only(left: screenWidth * 0.45), + padding: EdgeInsets.only(left: screenWidth * 0.42), child: TextButton( + style: TextButton.styleFrom( + backgroundColor: const Color.fromARGB(225, 200, 230, + 201), // Background color for the button + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(50), // Rounded corners + ), + padding: EdgeInsets.symmetric( + vertical: 12, + horizontal: 24), // Padding inside the button + ), onPressed: () { Navigator.push( context, @@ -193,9 +263,11 @@ class _LoginPageState extends State { child: Text( _text.forget_password, style: const TextStyle( - color: Color(0xff092414), - fontSize: 16, - fontWeight: FontWeight.w500), + color: + Colors.black, // White text color for contrast + fontSize: 15, + fontWeight: FontWeight.w500, + ), ), ), ), diff --git a/lib/views/pages/main_home/homepage.dart b/lib/views/pages/main_home/homepage.dart index cd8f3de..91c9ee9 100644 --- a/lib/views/pages/main_home/homepage.dart +++ b/lib/views/pages/main_home/homepage.dart @@ -5,6 +5,9 @@ import 'package:flutter/material.dart'; import '../profile/profile_screen.dart'; import 'home_pages/home_screen.dart'; import 'package:donorconnect/language/helper/language_extention.dart'; +import 'package:adaptive_will_pop_scope/adaptive_will_pop_scope.dart'; // Import the adaptive will pop scope +import 'package:flutter/cupertino.dart'; +import 'package:flutter/services.dart'; class HomePage extends StatefulWidget { final token; @@ -18,27 +21,7 @@ class HomePage extends StatefulWidget { } class _HomePageState extends State { - // @override - // void dispose() { - // Get.delete(); - // super.dispose(); - // } - // late ScrollController controller; - // - /// variables int _currentIndex = 0; - // - // @override - // void initState() { - // super.initState(); - // controller = ScrollController(); - // } - // - // @override - // void dispose() { - // controller.dispose; - // super.dispose(); - // } @override Widget build(BuildContext context) { @@ -54,56 +37,75 @@ class _HomePageState extends State { ), ]; - return Scaffold( - bottomNavigationBar: CustomNavigationBar( - scaleFactor: 0.2, - strokeColor: Colors.blueGrey, - iconSize: 24, - elevation: 0, - backgroundColor: Colors.transparent, - selectedColor: Colors.blue, - unSelectedColor: Colors.blue.withOpacity(0.4), - isFloating: false, - currentIndex: _currentIndex, - scaleCurve: Curves.bounceOut, - bubbleCurve: Curves.easeInOut, - onTap: (int newIndex) { + final screenWidth = MediaQuery.of(context).size.width; + + return AdaptiveWillPopScope( + onWillPop: () async { + if (_currentIndex == 0) { + // If we are on the HomeScreen, show the exit confirmation dialog + bool exitApp = await _showExitConfirmationDialog(context) ?? false; + return exitApp; // Return true to exit the app, false to do nothing + } else { + // If we are not on the HomeScreen, navigate to HomeScreen setState(() { - _currentIndex = newIndex; - pageController.animateToPage(newIndex, - duration: const Duration(milliseconds: 500), - curve: Curves.fastOutSlowIn); + _currentIndex = 0; // Navigate to the HomeScreen }); - }, - items: [ - CustomNavigationBarItem( - icon: const Icon(Icons.home), - title: Text( - _text.home, - style: const TextStyle(fontSize: 12), - ), - ), - CustomNavigationBarItem( - icon: const Icon(Icons.search), - title: Text( - _text.search, - style: const TextStyle(fontSize: 12), - )), - CustomNavigationBarItem( - icon: const Icon(Icons.event), - title: Text( - _text.camps, - style: const TextStyle(fontSize: 12), - )), - CustomNavigationBarItem( - icon: const Icon(Icons.person), + pageController.jumpToPage(0); // Jump to HomeScreen + return false; // Don't pop the page + } + }, + swipeWidth: screenWidth, + swipeThreshold: screenWidth / 2, + child: Scaffold( + bottomNavigationBar: CustomNavigationBar( + scaleFactor: 0.2, + strokeColor: Colors.blueGrey, + iconSize: 24, + elevation: 0, + backgroundColor: Colors.transparent, + selectedColor: Colors.blue, + unSelectedColor: Colors.blue.withOpacity(0.4), + isFloating: false, + currentIndex: _currentIndex, + scaleCurve: Curves.bounceOut, + bubbleCurve: Curves.easeInOut, + onTap: (int newIndex) { + setState(() { + _currentIndex = newIndex; + pageController.animateToPage(newIndex, + duration: const Duration(milliseconds: 500), + curve: Curves.fastOutSlowIn); + }); + }, + items: [ + CustomNavigationBarItem( + icon: const Icon(Icons.home), title: Text( - _text.profile, + _text.home, style: const TextStyle(fontSize: 12), - )), - ], - ), - body: PageView.builder( + ), + ), + CustomNavigationBarItem( + icon: const Icon(Icons.search), + title: Text( + _text.search, + style: const TextStyle(fontSize: 12), + )), + CustomNavigationBarItem( + icon: const Icon(Icons.event), + title: Text( + _text.camps, + style: const TextStyle(fontSize: 12), + )), + CustomNavigationBarItem( + icon: const Icon(Icons.person), + title: Text( + _text.profile, + style: const TextStyle(fontSize: 12), + )), + ], + ), + body: PageView.builder( controller: pageController, onPageChanged: (int newIndex) { setState(() { @@ -113,7 +115,34 @@ class _HomePageState extends State { itemCount: 4, itemBuilder: (BuildContext context, int index) { return pages[index]; - }), + }, + ), + ), ); } + + Future _showExitConfirmationDialog(BuildContext context) => + showCupertinoModalPopup( + context: context, + builder: (_) => CupertinoAlertDialog( + title: const Text('Are you sure'), + content: const Text('Do you really want to exit the app?'), + actions: [ + CupertinoDialogAction( + isDefaultAction: true, + onPressed: () => Navigator.pop(context, false), // Don't exit + child: const Text('No'), + ), + CupertinoDialogAction( + isDestructiveAction: true, + onPressed: () { + Navigator.pop(context, true); // Exit the app + // Ensure the app closes after exit confirmation + SystemNavigator.pop(); + }, + child: const Text('Yes'), + ), + ], + ), + ); } diff --git a/lib/views/pages/onboarding/onboarding.dart b/lib/views/pages/onboarding/onboarding.dart index 1593a2c..8df71ad 100644 --- a/lib/views/pages/onboarding/onboarding.dart +++ b/lib/views/pages/onboarding/onboarding.dart @@ -1,54 +1,54 @@ -import 'package:donorconnect/views/pages/onboarding/widgets/onboarding_dot_navigation.dart'; -import 'package:donorconnect/views/pages/onboarding/widgets/onboarding_next_button.dart'; -import 'package:donorconnect/views/pages/onboarding/widgets/onboarding_skip.dart'; +import 'package:donorconnect/Utils/constants/images_string.dart'; +import 'package:donorconnect/Utils/constants/text_string.dart'; +import 'package:donorconnect/views/controllers/onboarding/onboarding_controller.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import '../../../Utils/constants/images_string.dart'; -import '../../../Utils/constants/text_string.dart'; -import '../../controllers/onboarding/onboarding_controller.dart'; import 'widgets/onboarding_page.dart'; - +import 'widgets/onboarding_skip.dart'; +import 'widgets/onboarding_dot_navigation.dart'; +import 'widgets/onboarding_next_button.dart'; class OnBoardingScreen extends StatelessWidget { const OnBoardingScreen({super.key}); @override Widget build(BuildContext context) { + // Initialize the controller final controller = Get.put(OnBoardingController()); return Scaffold( body: Stack( children: [ - /// Horizontal Scrollable Pages + // Horizontal Scrollable Pages (PageView) PageView( controller: controller.pageController, onPageChanged: controller.updatePageIndicator, children: const [ OnBoardingPage( - image: TImages.onBoardingImage1, - title: TTexts.onBoardingTitle1, - subtitle:TTexts.onBoardingSubTitle1, + image: TImages.onBoardingImage1, + title: TTexts.onBoardingTitle1, + subtitle: TTexts.onBoardingSubTitle1, ), OnBoardingPage( - image: TImages.onBoardingImage2, - title: TTexts.onBoardingTitle2, - subtitle:TTexts.onBoardingSubTitle2, + image: TImages.onBoardingImage2, + title: TTexts.onBoardingTitle2, + subtitle: TTexts.onBoardingSubTitle2, ), OnBoardingPage( - image: TImages.onBoardingImage3, - title: TTexts.onBoardingTitle3, - subtitle:TTexts.onBoardingSubTitle3, + image: TImages.onBoardingImage3, + title: TTexts.onBoardingTitle3, + subtitle: TTexts.onBoardingSubTitle3, ), ], - ), + ), - /// Skip Button - const OnBoardingSkip(), + // Skip Button (Fixed at the bottom-left) + const OnBoardingSkip(), - /// Dot Navigation SmoothPageIndicator + // Dot Navigation (SmoothPageIndicator) const OnBoardingDotNavigation(), - /// Circular Button + // Next Button (Circular button at the bottom-right) const OnBoardingNextButton(), ], ), diff --git a/lib/views/pages/onboarding/widgets/onboarding_dot_navigation.dart b/lib/views/pages/onboarding/widgets/onboarding_dot_navigation.dart index aa73181..b66b7d3 100644 --- a/lib/views/pages/onboarding/widgets/onboarding_dot_navigation.dart +++ b/lib/views/pages/onboarding/widgets/onboarding_dot_navigation.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:get/get.dart'; import 'package:smooth_page_indicator/smooth_page_indicator.dart'; import '../../../controllers/onboarding/onboarding_controller.dart'; @@ -9,18 +10,20 @@ class OnBoardingDotNavigation extends StatelessWidget { @override Widget build(BuildContext context) { - final controller = OnBoardingController.instance; + // Use Get.find to access the OnBoardingController instance + final controller = Get.find(); return Positioned( - bottom: kBottomNavigationBarHeight + 25, + bottom: kBottomNavigationBarHeight + 25, left: 154, child: SmoothPageIndicator( count: 3, controller: controller.pageController, - onDotClicked: controller.dotNavigationClick, effect: const ExpandingDotsEffect( - activeDotColor: Color.fromARGB(255, 194, 4, 4), - dotHeight: 6), - ),); + activeDotColor: Color.fromARGB(255, 194, 4, 4), + dotHeight: 6, + ), + ), + ); } - } \ No newline at end of file +} diff --git a/lib/views/pages/onboarding/widgets/onboarding_next_button.dart b/lib/views/pages/onboarding/widgets/onboarding_next_button.dart index 70e8b1c..6ba3628 100644 --- a/lib/views/pages/onboarding/widgets/onboarding_next_button.dart +++ b/lib/views/pages/onboarding/widgets/onboarding_next_button.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:get/get.dart'; import 'package:iconsax/iconsax.dart'; import '../../../controllers/onboarding/onboarding_controller.dart'; @@ -9,15 +10,23 @@ class OnBoardingNextButton extends StatelessWidget { @override Widget build(BuildContext context) { + // Get the controller using Get.find() or Get.put() if it's not already created + final controller = Get.find(); return Positioned( - right: 24, - bottom: kBottomNavigationBarHeight, - child: ElevatedButton( - onPressed:() => OnBoardingController.instance.nextPage(), - style: ElevatedButton.styleFrom(shape: const CircleBorder(), backgroundColor: Color.fromARGB(255, 194, 4, 4),) , - child: const Icon(Iconsax.arrow_right_3,color: Colors.white,) , - ) + right: 24, + bottom: kBottomNavigationBarHeight, + child: ElevatedButton( + onPressed: () => controller.nextPage(), // Call nextPage() method + style: ElevatedButton.styleFrom( + shape: const CircleBorder(), + backgroundColor: Color.fromARGB(255, 194, 4, 4), + ), + child: const Icon( + Iconsax.arrow_right_3, + color: Colors.white, + ), + ), ); } } diff --git a/lib/views/pages/onboarding/widgets/onboarding_page.dart b/lib/views/pages/onboarding/widgets/onboarding_page.dart index e1e2495..f6a6f7b 100644 --- a/lib/views/pages/onboarding/widgets/onboarding_page.dart +++ b/lib/views/pages/onboarding/widgets/onboarding_page.dart @@ -1,6 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:get/get.dart'; - class OnBoardingPage extends StatelessWidget { const OnBoardingPage({ @@ -15,33 +13,27 @@ class OnBoardingPage extends StatelessWidget { @override Widget build(BuildContext context) { return Padding( - padding: const EdgeInsets.all(24), - child: Column( - children: [ - Image( - width:double.infinity, - height: 500, - image: AssetImage(image), - ), - Text( - title, - style: Theme - .of(context) - .textTheme - .headlineMedium, - textAlign: TextAlign.center, - ), - const SizedBox(height: 24), - Text( - subtitle, - style: Theme - .of(context) - .textTheme - .bodyMedium, - textAlign: TextAlign.center, - ), - ], - ) + padding: const EdgeInsets.all(24), + child: Column( + children: [ + Image.asset( + image, + width: double.infinity, + height: 500, + ), + Text( + title, + style: Theme.of(context).textTheme.headlineMedium, + textAlign: TextAlign.center, + ), + const SizedBox(height: 24), + Text( + subtitle, + style: Theme.of(context).textTheme.bodyMedium, + textAlign: TextAlign.center, + ), + ], + ), ); } -} \ No newline at end of file +} diff --git a/lib/views/pages/onboarding/widgets/onboarding_skip.dart b/lib/views/pages/onboarding/widgets/onboarding_skip.dart index bebb66e..29feecc 100644 --- a/lib/views/pages/onboarding/widgets/onboarding_skip.dart +++ b/lib/views/pages/onboarding/widgets/onboarding_skip.dart @@ -1,7 +1,6 @@ - +import 'package:donorconnect/views/controllers/onboarding/onboarding_controller.dart'; import 'package:flutter/material.dart'; - -import '../../../controllers/onboarding/onboarding_controller.dart'; +import 'package:get/get.dart'; class OnBoardingSkip extends StatelessWidget { const OnBoardingSkip({ @@ -11,15 +10,20 @@ class OnBoardingSkip extends StatelessWidget { @override Widget build(BuildContext context) { return Positioned( - bottom: 60, - left: 24, - child: TextButton( - onPressed: () => OnBoardingController.instance.skipPage(), - child: Text('Skip', style: TextStyle( - color: Theme.of(context).colorScheme.onSurface, - fontWeight: FontWeight.bold, - fontSize: 16 - ), ), - )); + bottom: 60, + left: 24, + child: TextButton( + onPressed: () => Get.find() + .goToLoginPage(), // Access controller using Get.find + child: Text( + 'Skip', + style: TextStyle( + color: Theme.of(context).colorScheme.onSurface, + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), + ), + ); } } diff --git a/lib/views/pages/register/signup.dart b/lib/views/pages/register/signup.dart index 6bc0691..e78e475 100644 --- a/lib/views/pages/register/signup.dart +++ b/lib/views/pages/register/signup.dart @@ -31,7 +31,7 @@ class _SignuppageState extends State { bool _isConfirmPasswordValid = false; //variable to control password visibility - bool _isPasswordVisible =false; + bool _isPasswordVisible = false; bool _isConfirmPasswordVisible = false; bool isValidEmail(String email) { @@ -94,7 +94,8 @@ class _SignuppageState extends State { title: Center( child: Text( context.localizedString.password_dont_match, - style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500), + style: const TextStyle( + fontSize: 16, fontWeight: FontWeight.w500), )), ); }); @@ -237,79 +238,223 @@ class _SignuppageState extends State { SizedBox(height: screenHeight * 0.02), // EMAIL TEXTBOX - Textbox( - controller: emailController, - obscureText: false, - icons: Icons.mail, - name: _text.email, - errormsg: - _isEmailValid ? _text.email_field_error_text : null, + Container( + decoration: BoxDecoration( + color: Colors.white, // White background + borderRadius: + BorderRadius.circular(10), // Rounded corners + boxShadow: [ + BoxShadow( + color: Colors.black + .withOpacity(0.1), // Subtle shadow effect + blurRadius: 8, // Soft shadow + offset: Offset(0, 4), // Shadow position + ), + ], + ), + child: TextField( + controller: emailController, + obscureText: false, // Email is not obscured + decoration: InputDecoration( + prefixIcon: Icon(Icons.mail, + color: Colors.green.shade900), // Mail icon + hintText: 'Email', // Placeholder text + hintStyle: TextStyle( + color: Colors.green.shade900), // Hint style + errorText: _isEmailValid + ? _text.email_field_error_text + : null, // Error text if validation fails + border: InputBorder.none, // Remove default border + contentPadding: EdgeInsets.symmetric( + horizontal: 16, + vertical: 12), // Padding inside the field + ), + ), ), + SizedBox(height: screenHeight * 0.02), // FULL NAME TEXTBOX - Textbox( - controller: nameController, - obscureText: false, - icons: Icons.person, - name: _text.full_name, - errormsg: - _isNameValid ? _text.name_field_error_text : null, + Container( + decoration: BoxDecoration( + color: Colors.white, // White background + borderRadius: + BorderRadius.circular(10), // Rounded corners + boxShadow: [ + BoxShadow( + color: Colors.black + .withOpacity(0.1), // Subtle shadow effect + blurRadius: 8, // Soft shadow + offset: Offset(0, 4), // Shadow position + ), + ], + ), + child: TextField( + controller: nameController, + obscureText: false, // Name is not obscured + decoration: InputDecoration( + prefixIcon: Icon(Icons.person, + color: Colors.green.shade900), // Person icon + hintText: _text + .full_name, // Placeholder text for full name + hintStyle: TextStyle( + color: Colors.green.shade900), // Hint style + errorText: _isNameValid + ? _text.name_field_error_text + : null, // Error text if validation fails + border: InputBorder.none, // Remove default border + contentPadding: EdgeInsets.symmetric( + horizontal: 16, + vertical: 12), // Padding inside the field + ), + ), ), + SizedBox(height: screenHeight * 0.02), // PHONE NUMBER TEXTBOX - Textbox( - controller: numberController, - obscureText: false, - icons: Icons.call, - name: _text.phone_number, - errormsg: _isPhoneValid - ? _text.phone_number_error_text - : null, + Container( + decoration: BoxDecoration( + color: Colors.white, // White background + borderRadius: + BorderRadius.circular(10), // Rounded corners + boxShadow: [ + BoxShadow( + color: Colors.black + .withOpacity(0.1), // Subtle shadow effect + blurRadius: 8, // Soft shadow + offset: Offset(0, 4), // Shadow position + ), + ], + ), + child: TextField( + controller: numberController, + obscureText: false, // Phone number is not obscured + keyboardType: TextInputType + .phone, // Keyboard type for phone numbers + decoration: InputDecoration( + prefixIcon: Icon(Icons.call, + color: Colors.green.shade900), // Call icon + hintText: _text + .phone_number, // Placeholder text for phone number + hintStyle: TextStyle( + color: Colors.green.shade900), // Hint style + errorText: _isPhoneValid + ? _text.phone_number_error_text + : null, // Error text if validation fails + border: InputBorder.none, // Remove default border + contentPadding: EdgeInsets.symmetric( + horizontal: 16, + vertical: 12), // Padding inside the field + ), + ), ), + SizedBox(height: screenHeight * 0.02), // PASSWORD TEXTBOX - Textbox( - controller: passwordController, - obscureText: ! _isPasswordVisible, - icons: Icons.lock, - name: _text.create_password, - errormsg: - _isPasswordValid ? _text.password_error_text : null, - suffixIcon: IconButton( - icon: Icon( - _isPasswordVisible ? Icons.visibility : Icons.visibility_off, + Container( + decoration: BoxDecoration( + color: Colors.white, // White background + borderRadius: + BorderRadius.circular(10), // Rounded corners + boxShadow: [ + BoxShadow( + color: Colors.black + .withOpacity(0.1), // Subtle shadow effect + blurRadius: 8, // Soft shadow + offset: Offset(0, 4), // Shadow position + ), + ], + ), + child: TextField( + controller: passwordController, + obscureText: + !_isPasswordVisible, // Toggles password visibility + decoration: InputDecoration( + prefixIcon: Icon(Icons.lock, + color: Colors.green.shade900), // Lock icon + hintText: _text + .create_password, // Placeholder text for password + hintStyle: TextStyle( + color: Colors.green.shade900), // Hint style + errorText: _isPasswordValid + ? _text.password_error_text + : null, // Error text if validation fails + border: InputBorder.none, // Remove default border + contentPadding: EdgeInsets.symmetric( + horizontal: 16, + vertical: 12), // Padding inside the field + suffixIcon: IconButton( + icon: Icon( + _isPasswordVisible + ? Icons.visibility + : Icons + .visibility_off, // Visibility toggle icon + color: Colors.green.shade900, + ), + onPressed: () { + setState(() { + _isPasswordVisible = + !_isPasswordVisible; // Toggle visibility + }); + }, + ), ), - onPressed: () { - setState(() { - _isPasswordVisible = !_isPasswordVisible; - }); - }, ), - ), + SizedBox(height: screenHeight * 0.02), // CONFIRM PASSWORD - Textbox( - controller: confirmPasswordController, - obscureText: ! _isConfirmPasswordVisible, - icons: Icons.lock, - name: _text.confirm_password, - errormsg: _isConfirmPasswordValid - ? _text.password_dont_match - : null, - suffixIcon: IconButton( - icon: Icon( - _isConfirmPasswordVisible ? Icons.visibility : Icons.visibility_off, + Container( + decoration: BoxDecoration( + color: Colors.white, // White background + borderRadius: + BorderRadius.circular(10), // Rounded corners + boxShadow: [ + BoxShadow( + color: Colors.black + .withOpacity(0.1), // Subtle shadow effect + blurRadius: 8, // Soft shadow + offset: Offset(0, 4), // Shadow position + ), + ], + ), + child: TextField( + controller: confirmPasswordController, + obscureText: + !_isConfirmPasswordVisible, // Toggles confirm password visibility + decoration: InputDecoration( + prefixIcon: Icon(Icons.lock, + color: Colors.green.shade900), // Lock icon + hintText: _text + .confirm_password, // Placeholder text for confirm password + hintStyle: TextStyle( + color: Colors.green.shade900), // Hint style + errorText: _isConfirmPasswordValid + ? _text.password_dont_match + : null, // Error text if validation fails + border: InputBorder.none, // Remove default border + contentPadding: EdgeInsets.symmetric( + horizontal: 16, + vertical: 12), // Padding inside the field + suffixIcon: IconButton( + icon: Icon( + _isConfirmPasswordVisible + ? Icons.visibility + : Icons + .visibility_off, // Visibility toggle icon + color: Colors.green.shade900, + ), + onPressed: () { + setState(() { + _isConfirmPasswordVisible = + !_isConfirmPasswordVisible; // Toggle visibility + }); + }, + ), ), - onPressed: () { - setState(() { - _isConfirmPasswordVisible = !_isConfirmPasswordVisible; - }); - }, ), ), diff --git a/pubspec.lock b/pubspec.lock index c818f96..f6b9d9d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -22,6 +22,14 @@ packages: description: dart source: sdk version: "0.3.2" + adaptive_will_pop_scope: + dependency: "direct main" + description: + name: adaptive_will_pop_scope + sha256: fbf03d8032b4a6fa0c28155a565e0c7ccde3ea2bcad8cc3069906afd1a929842 + url: "https://pub.dev" + source: hosted + version: "1.0.1" analyzer: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 0c30349..4c7ead0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -61,6 +61,8 @@ dependencies: google_generative_ai: ^0.4.6 flutter_dotenv: ^5.2.1 + adaptive_will_pop_scope: ^1.0.1 + dev_dependencies: flutter_test: sdk: flutter