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:

    + +

    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