From 75a2f483b5dbfe6faf8ba63c40fc0de6bd9fcc80 Mon Sep 17 00:00:00 2001 From: LostRunes Date: Sat, 3 Jan 2026 00:27:37 +0530 Subject: [PATCH 1/2] register --- assets/images/welcome_illustration.png | Bin 0 -> 39985 bytes lib/main.dart | 52 +++++--- lib/view_models/home_view_model.dart | 2 +- lib/views/register_view.dart | 166 +++++++++++++++++++++++++ lib/views/welcome_view.dart | 103 +++++++++++++++ pubspec.yaml | 6 +- 6 files changed, 309 insertions(+), 20 deletions(-) create mode 100644 assets/images/welcome_illustration.png create mode 100644 lib/views/register_view.dart create mode 100644 lib/views/welcome_view.dart diff --git a/assets/images/welcome_illustration.png b/assets/images/welcome_illustration.png new file mode 100644 index 0000000000000000000000000000000000000000..a0bfd4220677d699d11e90d74203de79958e319d GIT binary patch literal 39985 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 HomeViewModel())], +// child: MaterialApp( +// title: 'Vector', +// debugShowCheckedModeBanner: false, +// theme: ThemeData( +// colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), +// useMaterial3: true, +// ), +// home: const Scaffold(body: Center(child: Text('Vector'))), +// ), +// ); +// } +// } + import 'package:flutter/material.dart'; +import 'views/welcome_view.dart'; import 'package:provider/provider.dart'; -import 'view_models/home_view_model.dart'; - void main() { - runApp(const MyApp()); + runApp(const VectorApp()); } -class MyApp extends StatelessWidget { - const MyApp({super.key}); +class VectorApp extends StatelessWidget { + const VectorApp({super.key}); @override Widget build(BuildContext context) { - return MultiProvider( - providers: [ChangeNotifierProvider(create: (_) => HomeViewModel())], - child: MaterialApp( - title: 'Vector', - debugShowCheckedModeBanner: false, - theme: ThemeData( - colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), - useMaterial3: true, - ), - home: const Scaffold(body: Center(child: Text('Vector'))), - ), + return const MaterialApp( + debugShowCheckedModeBanner: false, + home: WelcomeView(), ); } } diff --git a/lib/view_models/home_view_model.dart b/lib/view_models/home_view_model.dart index 8cafd5f..ff5cbf0 100644 --- a/lib/view_models/home_view_model.dart +++ b/lib/view_models/home_view_model.dart @@ -1,4 +1,4 @@ -import 'package:flutter/foundation.dart'; + import 'package:flutter/foundation.dart'; class HomeViewModel extends ChangeNotifier { bool _isLoading = false; diff --git a/lib/views/register_view.dart b/lib/views/register_view.dart new file mode 100644 index 0000000..954ba0f --- /dev/null +++ b/lib/views/register_view.dart @@ -0,0 +1,166 @@ +import 'package:flutter/material.dart'; + +class RegisterView extends StatelessWidget { + const RegisterView({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + resizeToAvoidBottomInset: true, + body: Container( + width: double.infinity, + height: double.infinity, + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Color(0xFF0F0F14), + Color(0xFF07070A), + ], + ), + ), + child: SafeArea( + child: LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + physics: const BouncingScrollPhysics(), + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, + ), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 28), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const SizedBox(height: 40), + + const Text( + 'Create your Account !', + style: TextStyle( + color: Colors.white, + fontSize: 24, + fontWeight: FontWeight.w600, + ), + ), + + const SizedBox(height: 30), + + const CircleAvatar( + radius: 45, + backgroundColor: Color(0xFFBDB6D0), + child: Icon( + Icons.person_outline, + size: 50, + color: Color(0xFF5C5470), + ), + ), + + const SizedBox(height: 40), + + _buildInput( + label: 'Email', + hint: 'user@mail.com', + ), + + const SizedBox(height: 20), + + _buildInput( + label: 'Username', + hint: 'user@name10', + ), + + const SizedBox(height: 20), + + _buildInput( + label: 'Password', + hint: 'Password', + isPassword: true, + ), + + const SizedBox(height: 40), + + SizedBox( + width: double.infinity, + height: 52, + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF7B3FE4), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(26), + ), + ), + onPressed: () {}, + child: const Text( + 'Create account', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Colors.white, + ), + ), + ), + ), + + const SizedBox(height: 20), + + GestureDetector( + onTap: () => Navigator.pop(context), + child: const Text( + 'already have an account? Log in!', + style: TextStyle( + color: Colors.white54, + fontSize: 13, + ), + ), + ), + + const SizedBox(height: 20), + ], + ), + ), + ), + ); + }, + ), + ), + ), + ); + } + + // 👇 THIS METHOD FIXES YOUR ERROR + Widget _buildInput({ + required String label, + required String hint, + bool isPassword = false, + }) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + label, + style: const TextStyle( + color: Colors.white70, + fontSize: 14, + ), + ), + const SizedBox(height: 8), + TextField( + obscureText: isPassword, + style: const TextStyle(color: Colors.white), + decoration: InputDecoration( + hintText: hint, + hintStyle: const TextStyle(color: Colors.white38), + filled: true, + fillColor: const Color(0xFF2B2738), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(14), + borderSide: BorderSide.none, + ), + ), + ), + ], + ); + } +} diff --git a/lib/views/welcome_view.dart b/lib/views/welcome_view.dart new file mode 100644 index 0000000..4a46a57 --- /dev/null +++ b/lib/views/welcome_view.dart @@ -0,0 +1,103 @@ +import 'package:flutter/material.dart'; +import 'register_view.dart'; + +class WelcomeView extends StatelessWidget { + const WelcomeView({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Container( + width: double.infinity, + height: double.infinity, + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Color(0xFF0F0F14), + Color(0xFF07070A), + ], + ), + ), + child: SafeArea( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // Illustration + Image.asset( + 'assets/images/welcome_illustration.png', + height: 240, + ), + + const SizedBox(height: 40), + + // Text + const Text( + 'Energize your lives with', + style: TextStyle( + color: Colors.white, + fontSize: 20, + ), + ), + + const SizedBox(height: 8), + + const Text( + 'Vector !', + style: TextStyle( + color: Color(0xFF9B5CF6), + fontSize: 28, + fontWeight: FontWeight.bold, + ), + ), + + const SizedBox(height: 50), + + // Button + SizedBox( + width: 220, + height: 52, + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF7B3FE4), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(26), + ), + elevation: 8, + ), + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => const RegisterView(), + ), + ); + }, + child: const Text( + 'Get Started', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Colors.white + ), + ), + ), + ), + + const SizedBox(height: 40), + + const Text( + 'Join us for a better lifestyle!', + style: TextStyle( + color: Colors.white54, + fontSize: 14, + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 2865480..4c661f9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -61,9 +61,9 @@ flutter: uses-material-design: true # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg + assets: + - assets/images/ + # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/to/resolution-aware-images From b974d2a996b06b7cc34f0fafc25f2e0bf3d4a270 Mon Sep 17 00:00:00 2001 From: LostRunes Date: Wed, 7 Jan 2026 18:37:33 +0530 Subject: [PATCH 2/2] auth --- android/app/build.gradle.kts | 3 +- android/app/google-services.json | 30 +++ android/build.gradle.kts | 4 + lib/core/config/env_config.dart | 8 + lib/core/constants.dart | 54 +++- lib/core/services/auth_service.dart | 41 +++ lib/core/utils/firebase_test_util.dart | 119 +++++++++ lib/main.dart | 20 +- lib/models/forget_password_model.dart | 133 ++++++++++ lib/models/login_model.dart | 219 ++++++++++++++++ lib/models/new_password_model.dart | 157 ++++++++++++ lib/models/onboarding_data_model.dart | 101 ++++++++ lib/models/verification_page_model.dart | 166 ++++++++++++ lib/view_models/auth_view_model.dart | 69 +++++ lib/view_models/onboarding_view_model.dart | 174 +++++++++++++ lib/views/age.dart | 196 +++++++++++++++ lib/views/gender.dart | 168 +++++++++++++ lib/views/goals.dart | 236 ++++++++++++++++++ lib/views/height.dart | 198 +++++++++++++++ lib/views/register_view.dart | 51 ++-- lib/views/weight.dart | 198 +++++++++++++++ lib/views/welcome.dart | 93 +++++++ lib/views/welcome_view.dart | 2 +- macos/Flutter/GeneratedPluginRegistrant.swift | 6 + pubspec.lock | 119 ++++++++- pubspec.yaml | 4 +- .../flutter/generated_plugin_registrant.cc | 6 + windows/flutter/generated_plugins.cmake | 2 + 28 files changed, 2552 insertions(+), 25 deletions(-) create mode 100644 android/app/google-services.json create mode 100644 lib/core/config/env_config.dart create mode 100644 lib/core/utils/firebase_test_util.dart create mode 100644 lib/models/forget_password_model.dart create mode 100644 lib/models/login_model.dart create mode 100644 lib/models/new_password_model.dart create mode 100644 lib/models/onboarding_data_model.dart create mode 100644 lib/models/verification_page_model.dart create mode 100644 lib/view_models/auth_view_model.dart create mode 100644 lib/view_models/onboarding_view_model.dart create mode 100644 lib/views/age.dart create mode 100644 lib/views/gender.dart create mode 100644 lib/views/goals.dart create mode 100644 lib/views/height.dart create mode 100644 lib/views/weight.dart create mode 100644 lib/views/welcome.dart diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index aeb9f36..8950d88 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -47,4 +47,5 @@ java { toolchain { languageVersion.set(JavaLanguageVersion.of(17)) } -} \ No newline at end of file +} +apply(plugin = "com.google.gms.google-services") diff --git a/android/app/google-services.json b/android/app/google-services.json new file mode 100644 index 0000000..e749f04 --- /dev/null +++ b/android/app/google-services.json @@ -0,0 +1,30 @@ +{ + "project_info": { + "project_number": "835298460246", + "firebase_url": "https://vector-7729b-default-rtdb.asia-southeast1.firebasedatabase.app", + "project_id": "vector-7729b", + "storage_bucket": "vector-7729b.firebasestorage.app" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:835298460246:android:6d4a4f9da1b2a5a69ab1f7", + "android_client_info": { + "package_name": "com.example.vector" + } + }, + "oauth_client": [], + "api_key": [ + { + "current_key": "AIzaSyA2414zGiQceTrFLSkyuympsmP8Qp2FrOs" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/android/build.gradle.kts b/android/build.gradle.kts index dbee657..14c3ed2 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -1,3 +1,7 @@ +plugins { + id("com.google.gms.google-services") version "4.4.2" apply false +} + allprojects { repositories { google() diff --git a/lib/core/config/env_config.dart b/lib/core/config/env_config.dart new file mode 100644 index 0000000..f128436 --- /dev/null +++ b/lib/core/config/env_config.dart @@ -0,0 +1,8 @@ +class EnvConfig { + static const String firebaseDatabaseUrl = String.fromEnvironment( + 'FIREBASE_DB_URL', + defaultValue: '', + ); + + static bool get isConfigured => firebaseDatabaseUrl.isNotEmpty; +} diff --git a/lib/core/constants.dart b/lib/core/constants.dart index bdf3dcf..b023c54 100644 --- a/lib/core/constants.dart +++ b/lib/core/constants.dart @@ -1 +1,53 @@ -// Add your API keys/colors here +import 'config/env_config.dart'; + +// App Info +const String appName = 'Vector'; + +// Firebase Database +const String firebaseDatabaseUrl = EnvConfig.firebaseDatabaseUrl; + +// Database Paths +const String usersPath = 'users'; +const String onboardingPath = 'onboarding'; +const String connectionTestPath = '_connection_test'; +const String connectedInfoPath = '.info/connected'; + +// Colors +const int colorBackground = 0xFF0B0B0F; +const int colorPrimary = 0xFF693298; +const int colorPrimaryDark = 0xFF562A7D; +const int colorSecondary = 0xFF2A2438; +const int colorAccent = 0xFFEE94FE; +const int colorTextPrimary = 0xFFF5F5F5; +const int colorTextSecondary = 0xFFFFFFFF; + +// Sizes +const double paddingSmall = 8.0; +const double paddingMedium = 16.0; +const double paddingLarge = 24.0; +const double borderRadius = 12.0; +const double borderRadiusButton = 26.0; +const double buttonHeight = 52.0; +const double iconSizeSmall = 16.0; +const double iconSizeMedium = 24.0; +const double iconSizeLarge = 32.0; + +// Text Sizes +const double textSizeSmall = 14.0; +const double textSizeMedium = 18.0; +const double textSizeLarge = 24.0; +const double textSizeXLarge = 28.0; + +// Age Picker +const int minAge = 13; +const int maxAge = 120; +const double agePickerHeight = 192.6; +const double agePickerItemExtent = 50.0; + +// Height Picker (in cm) +const int minHeight = 100; +const int maxHeight = 250; + +// Weight Picker (in kg) +const int minWeight = 30; +const int maxWeight = 200; diff --git a/lib/core/services/auth_service.dart b/lib/core/services/auth_service.dart index e69de29..1670b6d 100644 --- a/lib/core/services/auth_service.dart +++ b/lib/core/services/auth_service.dart @@ -0,0 +1,41 @@ +import 'package:firebase_auth/firebase_auth.dart'; + +class AuthService { + final FirebaseAuth _auth = FirebaseAuth.instance; + + // REGISTER + Future register({ + required String email, + required String password, + }) async { + final userCredential = await _auth.createUserWithEmailAndPassword( + email: email, + password: password, + ); + return userCredential.user; + } + + // LOGIN + Future login({ + required String email, + required String password, + }) async { + final userCredential = await _auth.signInWithEmailAndPassword( + email: email, + password: password, + ); + return userCredential.user; + } + + // FORGOT PASSWORD + Future resetPassword({ + required String email, + }) async { + await _auth.sendPasswordResetEmail(email: email); + } + + // LOGOUT (future use) + Future logout() async { + await _auth.signOut(); + } +} diff --git a/lib/core/utils/firebase_test_util.dart b/lib/core/utils/firebase_test_util.dart new file mode 100644 index 0000000..7a8a473 --- /dev/null +++ b/lib/core/utils/firebase_test_util.dart @@ -0,0 +1,119 @@ +import 'package:flutter/foundation.dart'; +import '../services/firebase_database_service.dart'; +import '../../models/onboarding_data_model.dart'; + +class FirebaseTestUtil { + static final FirebaseDatabaseService _service = FirebaseDatabaseService(); + + static Future runAllTests() async { + debugPrint('═══════════════════════════════════════════'); + debugPrint('🔥 Firebase Realtime Database Test Suite'); + debugPrint('═══════════════════════════════════════════\n'); + + await _testConnection(); + await _testWriteAndRead(); + await _testUpdate(); + await _testDelete(); + + debugPrint('═══════════════════════════════════════════'); + debugPrint('✅ All tests completed'); + debugPrint('═══════════════════════════════════════════'); + } + + static Future _testConnection() async { + debugPrint('📡 Test 1: Connection Test'); + debugPrint('───────────────────────────────────────────'); + + final result = await _service.testConnection(); + + if (result.success) { + debugPrint(' ✅ ${result.message}'); + } else { + debugPrint(' ❌ ${result.message}'); + } + debugPrint(''); + } + + static Future _testWriteAndRead() async { + debugPrint('📝 Test 2: Write and Read'); + debugPrint('───────────────────────────────────────────'); + + const testUserId = 'test_user_001'; + final testData = OnboardingDataModel( + gender: Gender.male, + age: 25, + height: 175, + heightUnit: 'cm', + weight: 70, + weightUnit: 'kg', + goals: [FitnessGoal.muscleGain, FitnessGoal.betterEndurance], + ); + + try { + await _service.saveOnboardingData(testUserId, testData); + debugPrint(' ✅ Write successful'); + + final readData = await _service.getOnboardingData(testUserId); + if (readData != null) { + debugPrint(' ✅ Read successful'); + debugPrint( + ' 📄 Data: Gender=${readData.gender?.name}, Age=${readData.age}', + ); + debugPrint(' 📄 Height=${readData.height}${readData.heightUnit}'); + debugPrint(' 📄 Weight=${readData.weight}${readData.weightUnit}'); + debugPrint( + ' 📄 Goals=${readData.goals?.map((g) => g.name).join(", ")}', + ); + } else { + debugPrint(' ❌ Read failed: No data returned'); + } + } catch (e) { + debugPrint(' ❌ Error: $e'); + } + debugPrint(''); + } + + static Future _testUpdate() async { + debugPrint('🔄 Test 3: Update'); + debugPrint('───────────────────────────────────────────'); + + const testUserId = 'test_user_001'; + + try { + await _service.updateOnboardingField(testUserId, {'age': 26}); + debugPrint(' ✅ Update successful'); + + final updated = await _service.getOnboardingData(testUserId); + if (updated?.age == 26) { + debugPrint(' ✅ Verified: Age updated to ${updated?.age}'); + } else { + debugPrint(' ❌ Update verification failed'); + } + } catch (e) { + debugPrint(' ❌ Error: $e'); + } + debugPrint(''); + } + + static Future _testDelete() async { + debugPrint('🗑️ Test 4: Delete'); + debugPrint('───────────────────────────────────────────'); + + const testUserId = 'test_user_001'; + + try { + await _service.deleteOnboardingData(testUserId); + debugPrint(' ✅ Delete successful'); + + final deleted = await _service.getOnboardingData(testUserId); + if (deleted == null) { + debugPrint(' ✅ Verified: Data removed'); + } else { + debugPrint(' ❌ Delete verification failed'); + } + } catch (e) { + debugPrint(' ❌ Error: $e'); + } + debugPrint(''); + } +} diff --git a/lib/main.dart b/lib/main.dart index 3a6dd6e..63962e4 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -30,19 +30,27 @@ import 'package:flutter/material.dart'; import 'views/welcome_view.dart'; import 'package:provider/provider.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'view_models/auth_view_model.dart'; -void main() { +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + await Firebase.initializeApp(); runApp(const VectorApp()); } - class VectorApp extends StatelessWidget { const VectorApp({super.key}); @override Widget build(BuildContext context) { - return const MaterialApp( - debugShowCheckedModeBanner: false, - home: WelcomeView(), - ); + return MultiProvider( + providers: [ + ChangeNotifierProvider(create: (_) => AuthViewModel()), + ], + child: const MaterialApp( + debugShowCheckedModeBanner: false, + home: WelcomeView(), + ), +); } } diff --git a/lib/models/forget_password_model.dart b/lib/models/forget_password_model.dart new file mode 100644 index 0000000..744740d --- /dev/null +++ b/lib/models/forget_password_model.dart @@ -0,0 +1,133 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import '../view_models/auth_view_model.dart'; +import '../models/login_model.dart'; + +class ForgetPasswordPage extends StatefulWidget { + const ForgetPasswordPage({super.key}); + + @override + State createState() => _ForgetPasswordPageState(); +} + +class _ForgetPasswordPageState extends State { + final TextEditingController _emailController = TextEditingController(); + + @override + Widget build(BuildContext context) { + final authVM = context.watch(); + + return Scaffold( + backgroundColor: const Color(0xFF0B0B0F), + body: SafeArea( + child: SingleChildScrollView( + child: Column( + children: [ + Align( + alignment: Alignment.centerLeft, + child: InkWell( + onTap: () => Navigator.pushReplacement( + context, + MaterialPageRoute(builder: (_) => const LoginView()), + ), + child: const Text( + ' createState() => _LoginViewState(); +} + +class _LoginViewState extends State { + final TextEditingController _emailController = TextEditingController(); + final TextEditingController _passwordController = TextEditingController(); + bool rememberMe = true; + + @override + Widget build(BuildContext context) { + final authVM = context.watch(); + + return Scaffold( + backgroundColor: const Color(0xFF0B0B0F), + body: SafeArea( + child: SingleChildScrollView( + child: Center( + child: Column( + children: [ + const SizedBox(height: 60), + + const Text( + 'Welcome Back!', + style: TextStyle( + color: Colors.white, + fontSize: 29, + fontWeight: FontWeight.w600, + fontFamily: 'Poppins', + ), + ), + + const SizedBox(height: 55), + + Padding( + padding: const EdgeInsets.symmetric(horizontal: 30), + child: Column( + children: [ + const Text( + '"Discipline is the bridge between goals and accomplishment" - Jim Rohn', + textAlign: TextAlign.center, + style: TextStyle( + color: Colors.white, + fontSize: 16, + fontFamily: 'Poppins', + ), + ), + + const SizedBox(height: 70), + + _label('Email'), + _input( + controller: _emailController, + hint: 'user@email.com', + ), + + const SizedBox(height: 20), + + _label('Password'), + _input( + controller: _passwordController, + hint: 'password', + isPassword: true, + ), + + const SizedBox(height: 15), + + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + InkWell( + onTap: () => setState(() => rememberMe = !rememberMe), + child: Row( + children: [ + Icon( + rememberMe + ? Icons.check_box + : Icons.check_box_outline_blank, + color: Colors.white, + ), + const SizedBox(width: 6), + const Text( + 'Remember me', + style: TextStyle(color: Colors.white), + ), + ], + ), + ), + InkWell( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => const ForgetPasswordPage(), + ), + ); + }, + child: const Text( + 'Forgot Password?', + style: TextStyle(color: Colors.white), + ), + ), + ], + ), + + const SizedBox(height: 40), + + ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF693298), + fixedSize: const Size(244, 63), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(50), + ), + ), + onPressed: authVM.isLoading + ? null + : () async { + await authVM.login( + email: _emailController.text.trim(), + password: + _passwordController.text.trim(), + ); + + if (authVM.error == null) { + Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (_) => const AgePage(), + ), + ); + } else { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(authVM.error!), + ), + ); + } + }, + child: authVM.isLoading + ? const CircularProgressIndicator( + color: Colors.white, + ) + : const Text( + 'Log-in', + style: TextStyle( + fontSize: 18, + color: Colors.white, + fontFamily: 'Poppins', + ), + ), + ), + + const SizedBox(height: 20), + + InkWell( + onTap: () => Navigator.pop(context), + child: const Text( + "Don't have an account ? Sign Up !", + style: TextStyle( + color: Colors.white, + fontSize: 16, + fontFamily: 'Poppins', + ), + ), + ), + ], + ), + ), + ], + ), + ), + ), + ), + ); + } + + Widget _label(String text) => Align( + alignment: Alignment.centerLeft, + child: Text( + text, + style: const TextStyle( + fontSize: 20, + color: Colors.white, + fontFamily: 'Poppins', + ), + ), + ); + + Widget _input({ + required TextEditingController controller, + required String hint, + bool isPassword = false, + }) { + return TextField( + controller: controller, + obscureText: isPassword, + style: const TextStyle(color: Colors.white), + decoration: InputDecoration( + hintText: hint, + filled: true, + fillColor: const Color(0xFF2A2438), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(20), + ), + ), + ); + } +} diff --git a/lib/models/new_password_model.dart b/lib/models/new_password_model.dart new file mode 100644 index 0000000..80bdca2 --- /dev/null +++ b/lib/models/new_password_model.dart @@ -0,0 +1,157 @@ +import 'package:flutter/material.dart'; +import '../Models/login_model.dart'; +import '../view_models/new_password_view_model.dart'; + +class NewPasswordPage extends StatefulWidget { + const NewPasswordPage({super.key}); + + @override + State createState() => _NewPasswordPageState(); +} + +class _NewPasswordPageState extends State { + final NewPasswordViewModel viewModel = NewPasswordViewModel(); + + @override + + + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: const Color(0xFF0B0B0F), + body: SafeArea( + child: Column( + children: [ + + // Back text + Align( + alignment: Alignment.centerLeft, + child: InkWell( + onTap: () { + Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) => const LoginView(), + ), + ); + }, + child: const Text( + ' const LoginView(), + ), + ); + }, + child: const Text( + 'Confirm', + style: TextStyle( + fontSize: 18, + color: Colors.white, + fontFamily: 'Poppins', + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + ), + ], + ), + ), + ); + } + + + InputDecoration _inputDecoration(String hint) { + return InputDecoration( + hintText: hint, + hintStyle: const TextStyle( + color: Color(0xFFAAA7AF), + fontSize: 15, + fontFamily: 'Poppins', + ), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(20), + ), + filled: true, + fillColor: const Color(0xFF2A2438), + contentPadding: const EdgeInsets.symmetric( + vertical: 20, + horizontal: 20, + ), + ); + } +} diff --git a/lib/models/onboarding_data_model.dart b/lib/models/onboarding_data_model.dart new file mode 100644 index 0000000..ec81143 --- /dev/null +++ b/lib/models/onboarding_data_model.dart @@ -0,0 +1,101 @@ +enum Gender { male, female, others } + +enum FitnessGoal { gainWeight, loseWeight, muscleGain, betterEndurance, others } + +class OnboardingDataModel { + final String? userId; + final Gender? gender; + final int? age; + final int? height; + final String? heightUnit; + final int? weight; + final String? weightUnit; + final List? goals; + final DateTime? createdAt; + final DateTime? updatedAt; + + OnboardingDataModel({ + this.userId, + this.gender, + this.age, + this.height, + this.heightUnit = 'cm', + this.weight, + this.weightUnit = 'kg', + this.goals, + this.createdAt, + this.updatedAt, + }); + + Map toJson() { + return { + 'userId': userId, + 'gender': gender?.name, + 'age': age, + 'height': height, + 'heightUnit': heightUnit, + 'weight': weight, + 'weightUnit': weightUnit, + 'goals': goals?.map((g) => g.name).toList(), + 'createdAt': createdAt?.toIso8601String(), + 'updatedAt': updatedAt?.toIso8601String(), + }; + } + + factory OnboardingDataModel.fromJson(Map json) { + return OnboardingDataModel( + userId: json['userId'] as String?, + gender: json['gender'] != null + ? Gender.values.firstWhere( + (g) => g.name == json['gender'], + orElse: () => Gender.others, + ) + : null, + age: json['age'] as int?, + height: json['height'] as int?, + heightUnit: json['heightUnit'] as String? ?? 'cm', + weight: json['weight'] as int?, + weightUnit: json['weightUnit'] as String? ?? 'kg', + goals: (json['goals'] as List?) + ?.map( + (g) => FitnessGoal.values.firstWhere( + (goal) => goal.name == g, + orElse: () => FitnessGoal.others, + ), + ) + .toList(), + createdAt: json['createdAt'] != null + ? DateTime.parse(json['createdAt'] as String) + : null, + updatedAt: json['updatedAt'] != null + ? DateTime.parse(json['updatedAt'] as String) + : null, + ); + } + + OnboardingDataModel copyWith({ + String? userId, + Gender? gender, + int? age, + int? height, + String? heightUnit, + int? weight, + String? weightUnit, + List? goals, + DateTime? createdAt, + DateTime? updatedAt, + }) { + return OnboardingDataModel( + userId: userId ?? this.userId, + gender: gender ?? this.gender, + age: age ?? this.age, + height: height ?? this.height, + heightUnit: heightUnit ?? this.heightUnit, + weight: weight ?? this.weight, + weightUnit: weightUnit ?? this.weightUnit, + goals: goals ?? this.goals, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + } +} diff --git a/lib/models/verification_page_model.dart b/lib/models/verification_page_model.dart new file mode 100644 index 0000000..4946227 --- /dev/null +++ b/lib/models/verification_page_model.dart @@ -0,0 +1,166 @@ +import 'package:flutter/material.dart'; +import '../Models/forget_password_model.dart'; +import '../Models/new_password_model.dart'; +import '../view_models/verification_page_view_model.dart'; +import 'package:pinput/pinput.dart'; + +class VerificationPage extends StatefulWidget { + const VerificationPage({super.key}); + + @override + State createState() => _VerificationPageState(); +} + +class _VerificationPageState extends State { + final VerificationViewModel viewModel = VerificationViewModel(); + @override + Widget build(BuildContext context) { + final defaultPinTheme = PinTheme( + width: 35, + height: 45, + textStyle: const TextStyle( + fontSize: 32, + color: Colors.white, + fontWeight: FontWeight.w400, + fontFamily: 'Poppins', + ), + decoration: BoxDecoration( + color: const Color(0xFF2A2438), + borderRadius: BorderRadius.circular(12), + ), + ); + + return Scaffold( + backgroundColor: const Color(0xFF0B0B0F), + body: SafeArea( + child: SingleChildScrollView( + child: Column( + children: [ + Align( + alignment: Alignment.centerLeft, + child:InkWell( + onTap: () { + Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) => const ForgetPasswordPage(), + ), + ); + }, + child: const Text( + ' const NewPasswordPage(), + ), + ); + }, + child: const Text( + 'verify', + style: TextStyle( + fontSize: 18, + color: Colors.white, + fontFamily: 'Poppins', + fontWeight: FontWeight.w500, + ), + ), + ), + + + ], + ), + ), + ], + ), + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/view_models/auth_view_model.dart b/lib/view_models/auth_view_model.dart new file mode 100644 index 0000000..b4cd00d --- /dev/null +++ b/lib/view_models/auth_view_model.dart @@ -0,0 +1,69 @@ +import 'package:flutter/material.dart'; +import '../core/services/auth_service.dart'; + +class AuthViewModel extends ChangeNotifier { + final AuthService _authService = AuthService(); + + bool _isLoading = false; + String? _error; + + bool get isLoading => _isLoading; + String? get error => _error; + + void _setLoading(bool value) { + _isLoading = value; + notifyListeners(); + } + + void _setError(String? message) { + _error = message; + notifyListeners(); + } + + // REGISTER + Future register({ + required String email, + required String password, + }) async { + try { + _setLoading(true); + _setError(null); + await _authService.register(email: email, password: password); + } catch (e) { + _setError(e.toString()); + } finally { + _setLoading(false); + } + } + + // LOGIN + Future login({ + required String email, + required String password, + }) async { + try { + _setLoading(true); + _setError(null); + await _authService.login(email: email, password: password); + } catch (e) { + _setError(e.toString()); + } finally { + _setLoading(false); + } + } + + // FORGOT PASSWORD + Future resetPassword({ + required String email, + }) async { + try { + _setLoading(true); + _setError(null); + await _authService.resetPassword(email: email); + } catch (e) { + _setError(e.toString()); + } finally { + _setLoading(false); + } + } +} diff --git a/lib/view_models/onboarding_view_model.dart b/lib/view_models/onboarding_view_model.dart new file mode 100644 index 0000000..6c8efae --- /dev/null +++ b/lib/view_models/onboarding_view_model.dart @@ -0,0 +1,174 @@ +import 'package:flutter/foundation.dart'; +import '../core/services/firebase_database_service.dart'; +import '../models/onboarding_data_model.dart'; + +enum OnboardingError { saveFailed, loadFailed, networkError } + +class OnboardingViewModel extends ChangeNotifier { + final FirebaseDatabaseService _db = FirebaseDatabaseService(); + + Gender? _gender; + int? _age; + int? _height; + String _heightUnit = 'cm'; + int? _weight; + String _weightUnit = 'kg'; + List _goals = []; + + bool _isSaving = false; + OnboardingError? _errorType; + + Gender? get gender => _gender; + int? get age => _age; + int? get height => _height; + String get heightUnit => _heightUnit; + int? get weight => _weight; + String get weightUnit => _weightUnit; + List get goals => List.unmodifiable(_goals); + bool get isSaving => _isSaving; + OnboardingError? get errorType => _errorType; + + bool get hasBasicInfo => _gender != null && _age != null; + bool get hasPhysicalInfo => _height != null && _weight != null; + bool get isComplete => hasBasicInfo && hasPhysicalInfo && _goals.isNotEmpty; + + String? get errorMessage { + if (_errorType == null) return null; + switch (_errorType!) { + case OnboardingError.saveFailed: + return 'Could not save your data. Please try again.'; + case OnboardingError.loadFailed: + return 'Could not load your profile.'; + case OnboardingError.networkError: + return 'Network error. Check your connection.'; + } + } + + void setGender(Gender gender) { + if (_gender == gender) return; + _gender = gender; + _clearError(); + notifyListeners(); + } + + void setAge(int age) { + if (!_isValidAge(age)) return; + _age = age; + notifyListeners(); + } + + void setHeight(int height, {String? unit}) { + _height = height; + if (unit != null) _heightUnit = unit; + notifyListeners(); + } + + void setWeight(int weight, {String? unit}) { + _weight = weight; + if (unit != null) _weightUnit = unit; + notifyListeners(); + } + + void toggleGoal(FitnessGoal goal) { + _goals.contains(goal) ? _goals.remove(goal) : _goals.add(goal); + notifyListeners(); + } + + bool _isValidAge(int age) => age >= 13 && age <= 120; + + String? validateStep(int step) { + switch (step) { + case 1: + return _gender == null ? 'Please select your gender' : null; + case 2: + if (_age == null) return 'Please enter your age'; + if (!_isValidAge(_age!)) return 'Age must be between 13 and 120'; + return null; + case 3: + return _height == null ? 'Please enter your height' : null; + case 4: + return _weight == null ? 'Please enter your weight' : null; + case 5: + return _goals.isEmpty ? 'Select at least one goal' : null; + default: + return null; + } + } + + Future save(String userId) async { + if (!isComplete) return false; + + _isSaving = true; + _clearError(); + notifyListeners(); + + try { + final data = OnboardingDataModel( + gender: _gender, + age: _age, + height: _height, + heightUnit: _heightUnit, + weight: _weight, + weightUnit: _weightUnit, + goals: _goals, + ); + + await _db.saveOnboardingData(userId, data); + _isSaving = false; + notifyListeners(); + return true; + } catch (e) { + _errorType = OnboardingError.saveFailed; + _isSaving = false; + notifyListeners(); + return false; + } + } + + Future load(String userId) async { + _isSaving = true; + notifyListeners(); + + try { + final data = await _db.getOnboardingData(userId); + if (data != null) { + _gender = data.gender; + _age = data.age; + _height = data.height; + _heightUnit = data.heightUnit ?? 'cm'; + _weight = data.weight; + _weightUnit = data.weightUnit ?? 'kg'; + _goals = data.goals ?? []; + } + _isSaving = false; + notifyListeners(); + return data != null; + } catch (e) { + _errorType = OnboardingError.loadFailed; + _isSaving = false; + notifyListeners(); + return false; + } + } + + void clearError() => _clearError(); + + void reset() { + _gender = null; + _age = null; + _height = null; + _heightUnit = 'cm'; + _weight = null; + _weightUnit = 'kg'; + _goals = []; + _clearError(); + notifyListeners(); + } + + void _clearError() { + if (_errorType != null) { + _errorType = null; + notifyListeners(); + } + } +} diff --git a/lib/views/age.dart b/lib/views/age.dart new file mode 100644 index 0000000..0f8ae3b --- /dev/null +++ b/lib/views/age.dart @@ -0,0 +1,196 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import '../core/constants.dart'; +import '../view_models/onboarding_view_model.dart'; +import 'height.dart'; + +class AgePage extends StatefulWidget { + const AgePage({super.key}); + + @override + State createState() => _AgePageState(); +} + +class _AgePageState extends State { + late int selectedAge; + + @override + void initState() { + super.initState(); + final vm = context.read(); + selectedAge = vm.age ?? minAge; + } + + @override + Widget build(BuildContext context) { + final vm = context.watch(); + + return Scaffold( + backgroundColor: const Color(colorBackground), + appBar: AppBar( + title: const Text( + 'What is your age?', + style: TextStyle(fontSize: textSizeLarge), + ), + centerTitle: true, + foregroundColor: const Color(colorTextSecondary), + backgroundColor: const Color(colorBackground), + ), + body: Column( + children: [ + const Spacer(), + if (vm.errorMessage != null) + Padding( + padding: const EdgeInsets.symmetric(horizontal: paddingLarge), + child: Text( + vm.errorMessage!, + style: const TextStyle( + color: Colors.redAccent, + fontSize: textSizeSmall, + ), + textAlign: TextAlign.center, + ), + ), + const SizedBox(height: paddingMedium), + Container( + height: 70, + width: 170, + padding: const EdgeInsets.symmetric(horizontal: 50, vertical: 16), + decoration: BoxDecoration( + color: const Color(colorPrimaryDark), + borderRadius: BorderRadius.circular(borderRadius), + ), + child: Text( + '$selectedAge', + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 28, + color: Colors.white, + fontWeight: FontWeight.w600, + ), + ), + ), + const SizedBox(height: 20), + Container( + width: 220, + height: 66, + padding: const EdgeInsets.symmetric(vertical: 12), + decoration: BoxDecoration( + color: const Color(colorPrimary), + borderRadius: BorderRadius.circular(borderRadius), + ), + child: const Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Age', + style: TextStyle( + fontFamily: 'Poppins', + fontSize: 19.48, + color: Colors.white, + fontWeight: FontWeight.w500, + ), + ), + SizedBox(width: 8), + Icon(Icons.keyboard_arrow_up, color: Colors.white, size: 24), + ], + ), + ), + const SizedBox(height: 20), + Container( + height: agePickerHeight, + width: 220, + color: const Color(colorSecondary), + child: ListWheelScrollView.useDelegate( + itemExtent: agePickerItemExtent, + diameterRatio: 2.0, + useMagnifier: true, + magnification: 1.2, + overAndUnderCenterOpacity: 0.4, + physics: const FixedExtentScrollPhysics(), + perspective: 0.003, + childDelegate: ListWheelChildBuilderDelegate( + builder: (context, index) { + final age = minAge + index; + return Center( + child: Text( + '$age', + style: TextStyle( + fontFamily: 'Poppins', + fontSize: 19.48, + color: (selectedAge == age) + // ignore: deprecated_member_use + ? Color(0xFFFFFFFF).withOpacity( + 0.6, + ) // 60% opacity for selected + // ignore: deprecated_member_use + : Color(colorTextSecondary).withOpacity(1.0), + ), + ), + ); + }, + childCount: maxAge - minAge + 1, + ), + onSelectedItemChanged: (index) { + setState(() { + selectedAge = minAge + index; + vm.setAge(selectedAge); + }); + }, + ), + ), + const Spacer(), + Padding( + padding: const EdgeInsets.all(24.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + TextButton.icon( + onPressed: () { + Navigator.pop(context); + }, + icon: const Icon( + Icons.arrow_back_ios, + color: Colors.white, + size: 16, + ), + label: const Text( + 'Back', + style: TextStyle(color: Colors.white, fontSize: 16), + ), + ), + TextButton( + onPressed: () { + final error = vm.validateStep(2); + if (error == null) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const HeightPage(), + ), + ); + } + }, + child: const Row( + children: [ + Text( + 'Next', + style: TextStyle(color: Colors.white, fontSize: 16), + ), + SizedBox(width: 4), + Icon( + Icons.arrow_forward_ios, + color: Colors.white, + size: 16, + ), + ], + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/views/gender.dart b/lib/views/gender.dart new file mode 100644 index 0000000..bed4ebb --- /dev/null +++ b/lib/views/gender.dart @@ -0,0 +1,168 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import '../core/constants.dart'; +import '../view_models/onboarding_view_model.dart'; +import '../models/onboarding_data_model.dart'; +import 'age.dart'; + +class GenderPage extends StatefulWidget { + const GenderPage({super.key}); + + @override + State createState() => _GenderPageState(); +} + +class _GenderPageState extends State { + Gender? selectedGender; + + @override + void initState() { + super.initState(); + final vm = context.read(); + selectedGender = vm.gender; + } + + @override + Widget build(BuildContext context) { + final vm = context.watch(); + + return Scaffold( + backgroundColor: const Color(colorBackground), + body: SafeArea( + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 50), + child: Column( + children: [ + if (vm.errorMessage != null) + Padding( + padding: const EdgeInsets.only(bottom: paddingMedium), + child: Text( + vm.errorMessage!, + style: const TextStyle( + color: Colors.redAccent, + fontSize: textSizeSmall, + ), + textAlign: TextAlign.center, + ), + ), + const Text( + 'How do you Identify?', + style: TextStyle( + color: Colors.white, + fontSize: textSizeLarge, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 40), + Container( + padding: const EdgeInsets.symmetric( + vertical: 30, + horizontal: 20, + ), + decoration: BoxDecoration( + color: const Color(colorSecondary), + borderRadius: BorderRadius.circular(24), + ), + child: Column( + children: [ + _buildOption(Gender.male, Icons.male, "Male"), + const SizedBox(height: 20), + _buildOption(Gender.female, Icons.female, "Female"), + const SizedBox(height: 20), + _buildOption(Gender.others, Icons.transgender, "Others"), + ], + ), + ), + const SizedBox(height: 40), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + TextButton.icon( + onPressed: () { + Navigator.pop(context); + }, + icon: const Icon( + Icons.arrow_back_ios, + color: Colors.white, + size: iconSizeSmall, + ), + label: const Text( + 'Back', + style: TextStyle( + color: Colors.white, + fontSize: textSizeMedium, + ), + ), + ), + TextButton( + onPressed: () { + final error = vm.validateStep(1); + if (error == null) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const AgePage(), + ), + ); + } + }, + child: const Row( + children: [ + Text( + 'Next', + style: TextStyle( + color: Colors.white, + fontSize: textSizeMedium, + ), + ), + SizedBox(width: 4), + Icon( + Icons.arrow_forward_ios, + color: Colors.white, + size: iconSizeSmall, + ), + ], + ), + ), + ], + ), + ], + ), + ), + ), + ), + ); + } + + Widget _buildOption(Gender gender, IconData icon, String label) { + bool isSelected = selectedGender == gender; + return GestureDetector( + onTap: () { + setState(() => selectedGender = gender); + final vm = context.read(); + vm.setGender(gender); + }, + child: Container( + width: 140, + height: 140, + decoration: BoxDecoration( + color: isSelected + ? const Color(colorPrimary) + : const Color(colorPrimaryDark), + borderRadius: BorderRadius.circular(borderRadius), + ), + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(icon, color: Colors.white, size: 50), + const SizedBox(height: 10), + Text(label, style: const TextStyle(color: Colors.white)), + ], + ), + ), + ), + ); + } +} diff --git a/lib/views/goals.dart b/lib/views/goals.dart new file mode 100644 index 0000000..097afa7 --- /dev/null +++ b/lib/views/goals.dart @@ -0,0 +1,236 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import '../core/constants.dart'; +import '../view_models/onboarding_view_model.dart'; +import '../models/onboarding_data_model.dart'; + +class GoalsPage extends StatefulWidget { + const GoalsPage({super.key}); + + @override + State createState() => _GoalsPageState(); +} + +class _GoalsPageState extends State { + Set selectedGoals = {}; + + @override + void initState() { + super.initState(); + final vm = context.read(); + selectedGoals = Set.from(vm.goals); + } + + @override + Widget build(BuildContext context) { + final vm = context.watch(); + + return Scaffold( + backgroundColor: const Color(colorBackground), + body: SafeArea( + child: Column( + children: [ + const SizedBox(height: 64), + if (vm.errorMessage != null) + Padding( + padding: const EdgeInsets.symmetric(horizontal: paddingLarge), + child: Text( + vm.errorMessage!, + style: const TextStyle( + color: Colors.redAccent, + fontSize: textSizeSmall, + ), + textAlign: TextAlign.center, + ), + ), + const SizedBox(height: paddingMedium), + const Text( + "What are your Goals?", + style: TextStyle( + color: Colors.white, + fontSize: textSizeLarge, + fontWeight: FontWeight.w600, + ), + ), + + const SizedBox(height: 56), + Container( + height: 504, + width: 323, + margin: const EdgeInsets.symmetric(horizontal: 20), + padding: const EdgeInsets.symmetric(vertical: 22), + decoration: BoxDecoration( + color: const Color(colorSecondary).withOpacity(0.5), + borderRadius: BorderRadius.circular(24), + ), + child: Column( + children: [ + Expanded( + child: _buildGoalTile( + vm, + FitnessGoal.gainWeight, + "Gain Weight", + ), + ), + Expanded( + child: _buildGoalTile( + vm, + FitnessGoal.loseWeight, + "Lose Weight", + ), + ), + Expanded( + child: _buildGoalTile( + vm, + FitnessGoal.muscleGain, + "Muscle Gain", + ), + ), + Expanded( + child: _buildGoalTile( + vm, + FitnessGoal.betterEndurance, + "Better Endurance", + ), + ), + Expanded( + child: _buildGoalTile(vm, FitnessGoal.others, "Others"), + ), + ], + ), + ), + + const Spacer(), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + TextButton.icon( + onPressed: () { + Navigator.pop(context); + }, + icon: const Icon( + Icons.arrow_back_ios, + color: Colors.white, + size: iconSizeSmall, + ), + label: const Text( + "Back", + style: TextStyle( + color: Colors.white, + fontSize: textSizeMedium, + ), + ), + ), + TextButton( + onPressed: () async { + final error = vm.validateStep(5); + if (error == null && vm.isComplete) { + // Save all onboarding data + final success = await vm.save( + 'user_temp_id', + ); // TODO: Use real user ID + if (success) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text( + 'Onboarding completed successfully!', + ), + ), + ); + // TODO: Navigate to home screen + } + } + } + }, + child: Row( + children: [ + Text( + vm.isSaving ? "Saving..." : "Complete", + style: const TextStyle( + color: Colors.white, + fontSize: textSizeMedium, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(width: 4), + const Icon( + Icons.arrow_forward_ios, + color: Colors.white, + size: iconSizeSmall, + ), + ], + ), + ), + ], + ), + ), + ], + ), + ), + ); + } + + Widget _buildGoalTile(OnboardingViewModel vm, FitnessGoal goal, String text) { + final isSelected = selectedGoals.contains(goal); + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + child: GestureDetector( + onTap: () { + setState(() { + if (isSelected) { + selectedGoals.remove(goal); + } else { + selectedGoals.add(goal); + } + }); + vm.toggleGoal(goal); + }, + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + width: double.infinity, + padding: const EdgeInsets.symmetric(horizontal: 16), + decoration: BoxDecoration( + color: isSelected + ? const Color(colorPrimary) + : const Color(colorPrimaryDark), + borderRadius: BorderRadius.circular(borderRadius), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + text, + style: TextStyle( + color: isSelected ? Colors.white : Colors.white70, + fontSize: 17.14, + fontWeight: FontWeight.w500, + ), + ), + Container( + width: 22, + height: 22, + decoration: BoxDecoration( + color: const Color( + 0xFF261140, + ).withOpacity(isSelected ? 1.0 : 0.68), + borderRadius: BorderRadius.circular(6), + border: Border.all( + color: const Color( + 0xFF261140, + ).withOpacity(isSelected ? 1.0 : 0.68), + ), + ), + child: isSelected + ? const Icon(Icons.check, size: 16, color: Colors.white) + : null, + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/views/height.dart b/lib/views/height.dart new file mode 100644 index 0000000..a432890 --- /dev/null +++ b/lib/views/height.dart @@ -0,0 +1,198 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import '../core/constants.dart'; +import '../view_models/onboarding_view_model.dart'; +import 'weight.dart'; + +class HeightPage extends StatefulWidget { + const HeightPage({super.key}); + + @override + State createState() => _HeightPageState(); +} + +class _HeightPageState extends State { + late int selectedHeight; + String selectedUnit = 'cm'; + + @override + void initState() { + super.initState(); + final vm = context.read(); + selectedHeight = vm.height ?? minHeight; + selectedUnit = vm.heightUnit; + } + + @override + Widget build(BuildContext context) { + final vm = context.watch(); + + return Scaffold( + backgroundColor: const Color(colorBackground), + appBar: AppBar( + title: const Text( + 'What is your Height?', + style: TextStyle(fontSize: textSizeLarge), + ), + centerTitle: true, + foregroundColor: const Color(colorTextSecondary), + backgroundColor: const Color(colorBackground), + ), + body: Column( + children: [ + const Spacer(), + if (vm.errorMessage != null) + Padding( + padding: const EdgeInsets.symmetric(horizontal: paddingLarge), + child: Text( + vm.errorMessage!, + style: const TextStyle( + color: Colors.redAccent, + fontSize: textSizeSmall, + ), + textAlign: TextAlign.center, + ), + ), + const SizedBox(height: paddingMedium), + Container( + height: 70, + width: 170, + padding: const EdgeInsets.symmetric(horizontal: 50, vertical: 16), + decoration: BoxDecoration( + color: const Color(colorPrimaryDark), + borderRadius: BorderRadius.circular(borderRadius), + ), + child: Text( + '$selectedHeight', + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 28, + color: Colors.white, + fontWeight: FontWeight.w600, + ), + ), + ), + const SizedBox(height: 20), + Container( + width: 220, + height: 66, + padding: const EdgeInsets.symmetric(vertical: 12), + decoration: BoxDecoration( + color: const Color(colorPrimary), + borderRadius: BorderRadius.circular(borderRadius), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + selectedUnit, + style: const TextStyle( + fontFamily: 'Poppins', + fontSize: 19.48, + color: Colors.white, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(width: 8), + const Icon( + Icons.keyboard_arrow_up, + color: Colors.white, + size: 24, + ), + ], + ), + ), + const SizedBox(height: 20), + Container( + height: agePickerHeight, + width: 220, + color: const Color(colorSecondary), + child: ListWheelScrollView.useDelegate( + itemExtent: agePickerItemExtent, + diameterRatio: 2.0, + useMagnifier: true, + magnification: 1.2, + overAndUnderCenterOpacity: 0.4, + physics: const FixedExtentScrollPhysics(), + perspective: 0.003, + childDelegate: ListWheelChildBuilderDelegate( + builder: (context, index) { + final height = minHeight + index; + return Center( + child: Text( + '$height', + style: TextStyle( + fontFamily: 'Poppins', + fontSize: 19.48, + color: (selectedHeight == height) + ? const Color(0xFFFFFFFF).withOpacity(0.6) + : const Color(colorTextSecondary).withOpacity(1.0), + ), + ), + ); + }, + childCount: maxHeight - minHeight + 1, + ), + onSelectedItemChanged: (index) { + setState(() { + selectedHeight = minHeight + index; + vm.setHeight(selectedHeight, unit: selectedUnit); + }); + }, + ), + ), + const Spacer(), + Padding( + padding: const EdgeInsets.all(24.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + TextButton.icon( + onPressed: () { + Navigator.pop(context); + }, + icon: const Icon( + Icons.arrow_back_ios, + color: Colors.white, + size: 16, + ), + label: const Text( + 'Back', + style: TextStyle(color: Colors.white, fontSize: 16), + ), + ), + TextButton( + onPressed: () { + final error = vm.validateStep(3); + if (error == null) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const WeightPage(), + ), + ); + } + }, + child: const Row( + children: [ + Text( + 'Next', + style: TextStyle(color: Colors.white, fontSize: 16), + ), + SizedBox(width: 4), + Icon( + Icons.arrow_forward_ios, + color: Colors.white, + size: 16, + ), + ], + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/views/register_view.dart b/lib/views/register_view.dart index 954ba0f..d1895bb 100644 --- a/lib/views/register_view.dart +++ b/lib/views/register_view.dart @@ -1,10 +1,19 @@ import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import '../view_models/auth_view_model.dart'; class RegisterView extends StatelessWidget { - const RegisterView({super.key}); + RegisterView({super.key}); + + // Controllers + final TextEditingController _emailController = TextEditingController(); + final TextEditingController _usernameController = TextEditingController(); + final TextEditingController _passwordController = TextEditingController(); @override Widget build(BuildContext context) { + final authVM = context.watch(); + return Scaffold( resizeToAvoidBottomInset: true, body: Container( @@ -26,9 +35,7 @@ class RegisterView extends StatelessWidget { return SingleChildScrollView( physics: const BouncingScrollPhysics(), child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight, - ), + constraints: BoxConstraints(minHeight: constraints.maxHeight), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 28), child: Column( @@ -62,6 +69,7 @@ class RegisterView extends StatelessWidget { _buildInput( label: 'Email', hint: 'user@mail.com', + controller: _emailController, ), const SizedBox(height: 20), @@ -69,6 +77,7 @@ class RegisterView extends StatelessWidget { _buildInput( label: 'Username', hint: 'user@name10', + controller: _usernameController, ), const SizedBox(height: 20), @@ -76,6 +85,7 @@ class RegisterView extends StatelessWidget { _buildInput( label: 'Password', hint: 'Password', + controller: _passwordController, isPassword: true, ), @@ -91,15 +101,26 @@ class RegisterView extends StatelessWidget { borderRadius: BorderRadius.circular(26), ), ), - onPressed: () {}, - child: const Text( - 'Create account', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - color: Colors.white, - ), - ), + onPressed: authVM.isLoading + ? null + : () { + authVM.register( + email: _emailController.text.trim(), + password: _passwordController.text.trim(), + ); + }, + child: authVM.isLoading + ? const CircularProgressIndicator( + color: Colors.white, + ) + : const Text( + 'Create account', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Colors.white, + ), + ), ), ), @@ -129,10 +150,11 @@ class RegisterView extends StatelessWidget { ); } - // 👇 THIS METHOD FIXES YOUR ERROR + // Reusable input field Widget _buildInput({ required String label, required String hint, + required TextEditingController controller, bool isPassword = false, }) { return Column( @@ -147,6 +169,7 @@ class RegisterView extends StatelessWidget { ), const SizedBox(height: 8), TextField( + controller: controller, obscureText: isPassword, style: const TextStyle(color: Colors.white), decoration: InputDecoration( diff --git a/lib/views/weight.dart b/lib/views/weight.dart new file mode 100644 index 0000000..d82ee96 --- /dev/null +++ b/lib/views/weight.dart @@ -0,0 +1,198 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import '../core/constants.dart'; +import '../view_models/onboarding_view_model.dart'; +import 'goals.dart'; + +class WeightPage extends StatefulWidget { + const WeightPage({super.key}); + + @override + State createState() => _WeightPageState(); +} + +class _WeightPageState extends State { + late int selectedWeight; + String selectedUnit = 'kg'; + + @override + void initState() { + super.initState(); + final vm = context.read(); + selectedWeight = vm.weight ?? minWeight; + selectedUnit = vm.weightUnit; + } + + @override + Widget build(BuildContext context) { + final vm = context.watch(); + + return Scaffold( + backgroundColor: const Color(colorBackground), + appBar: AppBar( + title: const Text( + 'What is your Weight?', + style: TextStyle(fontSize: textSizeLarge), + ), + centerTitle: true, + foregroundColor: const Color(colorTextSecondary), + backgroundColor: const Color(colorBackground), + ), + body: Column( + children: [ + const Spacer(), + if (vm.errorMessage != null) + Padding( + padding: const EdgeInsets.symmetric(horizontal: paddingLarge), + child: Text( + vm.errorMessage!, + style: const TextStyle( + color: Colors.redAccent, + fontSize: textSizeSmall, + ), + textAlign: TextAlign.center, + ), + ), + const SizedBox(height: paddingMedium), + Container( + height: 70, + width: 170, + padding: const EdgeInsets.symmetric(horizontal: 50, vertical: 16), + decoration: BoxDecoration( + color: const Color(colorPrimaryDark), + borderRadius: BorderRadius.circular(borderRadius), + ), + child: Text( + '$selectedWeight', + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 28, + color: Colors.white, + fontWeight: FontWeight.w600, + ), + ), + ), + const SizedBox(height: 20), + Container( + width: 220, + height: 66, + padding: const EdgeInsets.symmetric(vertical: 12), + decoration: BoxDecoration( + color: const Color(colorPrimary), + borderRadius: BorderRadius.circular(borderRadius), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + selectedUnit, + style: const TextStyle( + fontFamily: 'Poppins', + fontSize: 19.48, + color: Colors.white, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(width: 8), + const Icon( + Icons.keyboard_arrow_up, + color: Colors.white, + size: 24, + ), + ], + ), + ), + const SizedBox(height: 20), + Container( + height: agePickerHeight, + width: 220, + color: const Color(colorSecondary), + child: ListWheelScrollView.useDelegate( + itemExtent: agePickerItemExtent, + diameterRatio: 2.0, + useMagnifier: true, + magnification: 1.2, + overAndUnderCenterOpacity: 0.4, + physics: const FixedExtentScrollPhysics(), + perspective: 0.003, + childDelegate: ListWheelChildBuilderDelegate( + builder: (context, index) { + final weight = minWeight + index; + return Center( + child: Text( + '$weight', + style: TextStyle( + fontFamily: 'Poppins', + fontSize: 19.48, + color: (selectedWeight == weight) + ? const Color(0xFFFFFFFF).withOpacity(0.6) + : const Color(colorTextSecondary).withOpacity(1.0), + ), + ), + ); + }, + childCount: maxWeight - minWeight + 1, + ), + onSelectedItemChanged: (index) { + setState(() { + selectedWeight = minWeight + index; + vm.setWeight(selectedWeight, unit: selectedUnit); + }); + }, + ), + ), + const Spacer(), + Padding( + padding: const EdgeInsets.all(24.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + TextButton.icon( + onPressed: () { + Navigator.pop(context); + }, + icon: const Icon( + Icons.arrow_back_ios, + color: Colors.white, + size: 16, + ), + label: const Text( + 'Back', + style: TextStyle(color: Colors.white, fontSize: 16), + ), + ), + TextButton( + onPressed: () { + final error = vm.validateStep(4); + if (error == null) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const GoalsPage(), + ), + ); + } + }, + child: const Row( + children: [ + Text( + 'Next', + style: TextStyle(color: Colors.white, fontSize: 16), + ), + SizedBox(width: 4), + Icon( + Icons.arrow_forward_ios, + color: Colors.white, + size: 16, + ), + ], + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/views/welcome.dart b/lib/views/welcome.dart new file mode 100644 index 0000000..d0408be --- /dev/null +++ b/lib/views/welcome.dart @@ -0,0 +1,93 @@ +import 'package:flutter/material.dart'; +import '../core/constants.dart'; +import 'gender.dart'; + +class WelcomePage extends StatelessWidget { + const WelcomePage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: const Color(colorBackground), + body: SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: paddingLarge), + child: Column( + children: [ + const Spacer(), + + Image.asset( + 'assets/Group_111.png', + height: MediaQuery.of(context).size.height * 0.35, + ), + + const SizedBox(height: 32), + + const Text( + 'Energize your lives with', + style: TextStyle( + color: Color(colorTextPrimary), + fontSize: textSizeMedium, + ), + ), + + const SizedBox(height: 8), + + const Text( + 'Vector!', + style: TextStyle( + color: Color(colorAccent), + fontSize: textSizeXLarge, + fontWeight: FontWeight.bold, + ), + ), + + const Spacer(), + + SizedBox( + width: double.infinity, + height: buttonHeight, + child: ElevatedButton( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const GenderPage(), + ), + ); + }, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(colorPrimary), + elevation: 6, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(borderRadiusButton), + ), + ), + child: const Text( + 'Get Started', + style: TextStyle( + fontSize: textSizeMedium, + color: Color(colorTextSecondary), + ), + ), + ), + ), + + const SizedBox(height: 16), + + const Text( + 'Join us for a better lifestyle!', + style: TextStyle( + color: Colors.white70, + fontSize: textSizeSmall, + ), + ), + + const SizedBox(height: 24), + ], + ), + ), + ), + ); + } +} diff --git a/lib/views/welcome_view.dart b/lib/views/welcome_view.dart index 4a46a57..5624886 100644 --- a/lib/views/welcome_view.dart +++ b/lib/views/welcome_view.dart @@ -70,7 +70,7 @@ class WelcomeView extends StatelessWidget { Navigator.push( context, MaterialPageRoute( - builder: (_) => const RegisterView(), + builder: (_) => RegisterView(), ), ); }, diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index cccf817..67059e4 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,6 +5,12 @@ import FlutterMacOS import Foundation +import firebase_auth +import firebase_core +import firebase_database func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin")) + FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) + FLTFirebaseDatabasePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseDatabasePlugin")) } diff --git a/pubspec.lock b/pubspec.lock index 09321f1..6ba95ee 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,14 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + _flutterfire_internals: + dependency: transitive + description: + name: _flutterfire_internals + sha256: ff0a84a2734d9e1089f8aedd5c0af0061b82fb94e95260d943404e0ef2134b11 + url: "https://pub.dev" + source: hosted + version: "1.3.59" async: dependency: transitive description: @@ -57,6 +65,78 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.3" + firebase_auth: + dependency: "direct main" + description: + name: firebase_auth + sha256: "0fed2133bee1369ee1118c1fef27b2ce0d84c54b7819a2b17dada5cfec3b03ff" + url: "https://pub.dev" + source: hosted + version: "5.7.0" + firebase_auth_platform_interface: + dependency: transitive + description: + name: firebase_auth_platform_interface + sha256: "871c9df4ec9a754d1a793f7eb42fa3b94249d464cfb19152ba93e14a5966b386" + url: "https://pub.dev" + source: hosted + version: "7.7.3" + firebase_auth_web: + dependency: transitive + description: + name: firebase_auth_web + sha256: d9ada769c43261fd1b18decf113186e915c921a811bd5014f5ea08f4cf4bc57e + url: "https://pub.dev" + source: hosted + version: "5.15.3" + firebase_core: + dependency: "direct main" + description: + name: firebase_core + sha256: "7be63a3f841fc9663342f7f3a011a42aef6a61066943c90b1c434d79d5c995c5" + url: "https://pub.dev" + source: hosted + version: "3.15.2" + firebase_core_platform_interface: + dependency: transitive + description: + name: firebase_core_platform_interface + sha256: cccb4f572325dc14904c02fcc7db6323ad62ba02536833dddb5c02cac7341c64 + url: "https://pub.dev" + source: hosted + version: "6.0.2" + firebase_core_web: + dependency: transitive + description: + name: firebase_core_web + sha256: "0ed0dc292e8f9ac50992e2394e9d336a0275b6ae400d64163fdf0a8a8b556c37" + url: "https://pub.dev" + source: hosted + version: "2.24.1" + firebase_database: + dependency: "direct main" + description: + name: firebase_database + sha256: "35b37c04307b99c5f746387ce03292531c3aa1de91facffbd9cff5e069a8b5fd" + url: "https://pub.dev" + source: hosted + version: "11.3.10" + firebase_database_platform_interface: + dependency: transitive + description: + name: firebase_database_platform_interface + sha256: "095342e96d94b486b8273afc6327f777d53b63a169bd4201e5153ee3b8210c11" + url: "https://pub.dev" + source: hosted + version: "0.2.6+10" + firebase_database_web: + dependency: transitive + description: + name: firebase_database_web + sha256: "05f9b871d97b3ca879937947d0728ea95294395e7ddd5685583e8662be99eb16" + url: "https://pub.dev" + source: hosted + version: "0.2.6+16" flutter: dependency: "direct main" description: flutter @@ -75,6 +155,19 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" leak_tracker: dependency: transitive description: @@ -147,6 +240,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" provider: dependency: "direct main" description: @@ -208,6 +309,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.7" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" vector_math: dependency: transitive description: @@ -224,6 +333,14 @@ packages: url: "https://pub.dev" source: hosted version: "15.0.2" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" sdks: dart: ">=3.10.3 <4.0.0" - flutter: ">=3.18.0-18.0.pre.54" + flutter: ">=3.22.0" diff --git a/pubspec.yaml b/pubspec.yaml index 4c661f9..f9bd9f8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -30,7 +30,9 @@ environment: dependencies: flutter: sdk: flutter - + firebase_auth: ^5.3.0 + firebase_database: ^11.3.5 + firebase_core: ^3.13.0 # State management provider: ^6.1.5+1 diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 8b6d468..d141b74 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,6 +6,12 @@ #include "generated_plugin_registrant.h" +#include +#include void RegisterPlugins(flutter::PluginRegistry* registry) { + FirebaseAuthPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FirebaseAuthPluginCApi")); + FirebaseCorePluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FirebaseCorePluginCApi")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index b93c4c3..29944d5 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,6 +3,8 @@ # list(APPEND FLUTTER_PLUGIN_LIST + firebase_auth + firebase_core ) list(APPEND FLUTTER_FFI_PLUGIN_LIST