diff --git a/analysis_options.yaml b/analysis_options.yaml index 0d29021..bee01e3 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -7,6 +7,9 @@ # The following line activates a set of recommended lints for Flutter apps, # packages, and plugins designed to encourage good coding practices. +analyzer: + errors: + unused_element: ignore include: package:flutter_lints/flutter.yaml linter: diff --git a/android/app/google-services.json b/android/app/google-services.json index 280f900..e1adf43 100644 --- a/android/app/google-services.json +++ b/android/app/google-services.json @@ -1,9 +1,49 @@ { "project_info": { - "project_number": "123456789012", - "project_id": "playmaker-basketball", - "storage_bucket": "playmaker-basketball.appspot.com" + "project_number": "74256198630", + "firebase_url": "https://playmaker-9e0fc-default-rtdb.firebaseio.com", + "project_id": "playmaker-9e0fc", + "storage_bucket": "playmaker-9e0fc.firebasestorage.app" }, - "client": [], + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:74256198630:android:145e08f6bc85ff13d18427", + "android_client_info": { + "package_name": "com.example.playmaker" + } + }, + "oauth_client": [], + "api_key": [ + { + "current_key": "AIzaSyDm7MBJQ6vZEE_gM1Ek5LH3Mf5ui2YHc2I" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:74256198630:android:092ff62687a5e51dd18427", + "android_client_info": { + "package_name": "com.playmaker.basketball" + } + }, + "oauth_client": [], + "api_key": [ + { + "current_key": "AIzaSyDm7MBJQ6vZEE_gM1Ek5LH3Mf5ui2YHc2I" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [] + } + } + } + ], "configuration_version": "1" } \ No newline at end of file diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts index fb605bc..ff284ff 100644 --- a/android/settings.gradle.kts +++ b/android/settings.gradle.kts @@ -20,6 +20,9 @@ pluginManagement { plugins { id("dev.flutter.flutter-plugin-loader") version "1.0.0" id("com.android.application") version "8.9.1" apply false + // START: FlutterFire Configuration + id("com.google.gms.google-services") version("4.3.15") apply false + // END: FlutterFire Configuration id("org.jetbrains.kotlin.android") version "2.1.0" apply false } diff --git a/devtools_options.yaml b/devtools_options.yaml new file mode 100644 index 0000000..fa0b357 --- /dev/null +++ b/devtools_options.yaml @@ -0,0 +1,3 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: diff --git a/firebase.json b/firebase.json index 602d1eb..138d198 100644 --- a/firebase.json +++ b/firebase.json @@ -1,8 +1 @@ -{ - "storage": { - "rules": "storage.rules" - }, - "database": { - "rules": "database.rules.json" - } -} +{"storage":{"rules":"storage.rules"},"database":{"rules":"database.rules.json"},"flutter":{"platforms":{"android":{"default":{"projectId":"playmaker-9e0fc","appId":"1:74256198630:android:145e08f6bc85ff13d18427","fileOutput":"android/app/google-services.json"}},"dart":{"lib/firebase_options.dart":{"projectId":"playmaker-9e0fc","configurations":{"android":"1:74256198630:android:145e08f6bc85ff13d18427","web":"1:74256198630:web:ba3d62a31608d686d18427","windows":"1:74256198630:web:6458f24490c3dc80d18427"}}}}}} \ No newline at end of file diff --git a/ios/Runner/GoogleService-Info.plist b/ios/Runner/GoogleService-Info.plist new file mode 100644 index 0000000..8738dfd --- /dev/null +++ b/ios/Runner/GoogleService-Info.plist @@ -0,0 +1,32 @@ + + + + + API_KEY + AIzaSyDxtHmQfK9WVHVAdNEA9zomqgNkNlj5u0g + GCM_SENDER_ID + 74256198630 + PLIST_VERSION + 1 + BUNDLE_ID + com.example.playmaker + PROJECT_ID + playmaker-9e0fc + STORAGE_BUCKET + playmaker-9e0fc.firebasestorage.app + IS_ADS_ENABLED + + IS_ANALYTICS_ENABLED + + IS_APPINVITE_ENABLED + + IS_GCM_ENABLED + + IS_SIGNIN_ENABLED + + GOOGLE_APP_ID + 1:74256198630:ios:a3a6ee8f075dd9d2d18427 + DATABASE_URL + https://playmaker-9e0fc-default-rtdb.firebaseio.com + + \ No newline at end of file diff --git a/lib/controllers/login_controller.dart b/lib/controllers/login_controller.dart index ca34074..01f7f2b 100644 --- a/lib/controllers/login_controller.dart +++ b/lib/controllers/login_controller.dart @@ -1,6 +1,9 @@ +import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/material.dart'; class LoginController with ChangeNotifier { + final FirebaseAuth _auth = FirebaseAuth.instance; + final TextEditingController emailController = TextEditingController(); final TextEditingController passwordController = TextEditingController(); @@ -20,26 +23,19 @@ class LoginController with ChangeNotifier { } String? validateEmail(String? value) { - if (value == null || value.isEmpty) { - return 'Por favor, insira o seu email'; - } + if (value == null || value.isEmpty) return 'Por favor, insira o seu email'; final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$'); - if (!emailRegex.hasMatch(value)) { - return 'Por favor, insira um email válido'; - } + if (!emailRegex.hasMatch(value)) return 'Por favor, insira um email válido'; return null; } String? validatePassword(String? value) { - if (value == null || value.isEmpty) { - return 'Por favor, insira a sua password'; - } - if (value.length < 6) { - return 'A password deve ter pelo menos 6 caracteres'; - } + if (value == null || value.isEmpty) return 'Por favor, insira a sua password'; + if (value.length < 6) return 'A password deve ter pelo menos 6 caracteres'; return null; } + // --- MÉTODO PARA ENTRAR (LOGIN) --- Future login() async { _emailError = validateEmail(emailController.text); _passwordError = validatePassword(passwordController.text); @@ -53,21 +49,76 @@ class LoginController with ChangeNotifier { notifyListeners(); try { - await Future.delayed(const Duration(seconds: 2)); - + await _auth.signInWithEmailAndPassword( + email: emailController.text.trim(), + password: passwordController.text.trim(), + ); _isLoading = false; notifyListeners(); return true; - } catch (e) { + } on FirebaseAuthException catch (e) { _isLoading = false; - _emailError = 'Erro no login. Tente novamente.'; + _handleFirebaseError(e.code); notifyListeners(); return false; } } + // --- MÉTODO PARA CRIAR CONTA (SIGN UP) --- + Future signUp() async { + _emailError = validateEmail(emailController.text); + _passwordError = validatePassword(passwordController.text); + + if (_emailError != null || _passwordError != null) { + notifyListeners(); + return false; + } + + _isLoading = true; + notifyListeners(); + + try { + await _auth.createUserWithEmailAndPassword( + email: emailController.text.trim(), + password: passwordController.text.trim(), + ); + _isLoading = false; + notifyListeners(); + return true; + } on FirebaseAuthException catch (e) { + _isLoading = false; + _handleFirebaseError(e.code); + notifyListeners(); + return false; + } + } + + void _handleFirebaseError(String code) { + switch (code) { + case 'email-already-in-use': + _emailError = 'Este e-mail já está a ser utilizado.'; + break; + case 'invalid-credential': + _emailError = 'E-mail ou password incorretos.'; + break; + case 'user-not-found': + _emailError = 'Utilizador não encontrado.'; + break; + case 'wrong-password': + _passwordError = 'Palavra-passe incorreta.'; + break; + case 'weak-password': + _passwordError = 'A password é demasiado fraca.'; + break; + default: + _emailError = 'Erro: $code'; + } + } + + @override void dispose() { emailController.dispose(); passwordController.dispose(); + super.dispose(); } } \ No newline at end of file diff --git a/lib/controllers/register_controller.dart b/lib/controllers/register_controller.dart new file mode 100644 index 0000000..a94ae77 --- /dev/null +++ b/lib/controllers/register_controller.dart @@ -0,0 +1,97 @@ + +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/material.dart'; + +class RegisterController with ChangeNotifier { + final FirebaseAuth _auth = FirebaseAuth.instance; + + final TextEditingController emailController = TextEditingController(); + final TextEditingController passwordController = TextEditingController(); + final TextEditingController confirmPasswordController = TextEditingController(); + + bool _isLoading = false; + String? _emailError; + String? _passwordError; + String? _confirmPasswordError; // Novo! + String? get confirmPasswordError => _confirmPasswordError; // Novo! + + bool get isLoading => _isLoading; + String? get emailError => _emailError; + String? get passwordError => _passwordError; + + // Validações + String? validateEmail(String? value) { + if (value == null || value.isEmpty) return 'Por favor, insira o seu email'; + final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$'); + if (!emailRegex.hasMatch(value)) return 'Por favor, insira um email válido'; + return null; + } + + String? validatePassword(String? value) { + if (value == null || value.isEmpty) return 'Por favor, insira a sua password'; + if (value.length < 6) return 'A password deve ter pelo menos 6 caracteres'; + return null; + } + String? validateConfirmPassword(String? value) { + if (value == null || value.isEmpty) { + return 'Por favor, confirme a sua password'; + } + if (value != passwordController.text) { + return 'As passwords não coincidem'; + } + return null; + } + + + // MÉTODO PARA CRIAR CONTA (SIGN UP) + Future signUp() async { + _emailError = validateEmail(emailController.text); + _passwordError = validatePassword(passwordController.text); + _emailError = validateEmail(emailController.text); + _passwordError = validatePassword(passwordController.text); + _confirmPasswordError = validateConfirmPassword(confirmPasswordController.text); // Valida aqui! + + if (_emailError != null || _passwordError != null || _confirmPasswordError != null) { + notifyListeners(); + return false; + } + + _isLoading = true; + notifyListeners(); + + try { + await _auth.createUserWithEmailAndPassword( + email: emailController.text.trim(), + password: passwordController.text.trim(), + ); + _isLoading = false; + notifyListeners(); + return true; + } on FirebaseAuthException catch (e) { + _isLoading = false; + _handleFirebaseError(e.code); + notifyListeners(); + return false; + } + } + + void _handleFirebaseError(String code) { + switch (code) { + case 'email-already-in-use': + _emailError = 'Este e-mail já está a ser utilizado.'; + break; + case 'weak-password': + _passwordError = 'A password é demasiado fraca.'; + break; + default: + _emailError = 'Erro ao registar: $code'; + } + } + + @override + void dispose() { + emailController.dispose(); + passwordController.dispose(); + super.dispose(); + } +} \ No newline at end of file diff --git a/lib/firebase_options.dart b/lib/firebase_options.dart new file mode 100644 index 0000000..c0a142a --- /dev/null +++ b/lib/firebase_options.dart @@ -0,0 +1,79 @@ +// File generated by FlutterFire CLI. +// ignore_for_file: type=lint +import 'package:firebase_core/firebase_core.dart' show FirebaseOptions; +import 'package:flutter/foundation.dart' + show defaultTargetPlatform, kIsWeb, TargetPlatform; + +/// Default [FirebaseOptions] for use with your Firebase apps. +/// +/// Example: +/// ```dart +/// import 'firebase_options.dart'; +/// // ... +/// await Firebase.initializeApp( +/// options: DefaultFirebaseOptions.currentPlatform, +/// ); +/// ``` +class DefaultFirebaseOptions { + static FirebaseOptions get currentPlatform { + if (kIsWeb) { + return web; + } + switch (defaultTargetPlatform) { + case TargetPlatform.android: + return android; + case TargetPlatform.iOS: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for ios - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + case TargetPlatform.macOS: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for macos - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + case TargetPlatform.windows: + return windows; + case TargetPlatform.linux: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for linux - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + default: + throw UnsupportedError( + 'DefaultFirebaseOptions are not supported for this platform.', + ); + } + } + + static const FirebaseOptions web = FirebaseOptions( + apiKey: 'AIzaSyBkHZtox18LRzXWYHEKVEXaYkkf8jv8Enk', + appId: '1:74256198630:web:ba3d62a31608d686d18427', + messagingSenderId: '74256198630', + projectId: 'playmaker-9e0fc', + authDomain: 'playmaker-9e0fc.firebaseapp.com', + databaseURL: 'https://playmaker-9e0fc-default-rtdb.firebaseio.com', + storageBucket: 'playmaker-9e0fc.firebasestorage.app', + measurementId: 'G-QQE1EZWZ8K', + ); + + static const FirebaseOptions android = FirebaseOptions( + apiKey: 'AIzaSyDm7MBJQ6vZEE_gM1Ek5LH3Mf5ui2YHc2I', + appId: '1:74256198630:android:145e08f6bc85ff13d18427', + messagingSenderId: '74256198630', + projectId: 'playmaker-9e0fc', + databaseURL: 'https://playmaker-9e0fc-default-rtdb.firebaseio.com', + storageBucket: 'playmaker-9e0fc.firebasestorage.app', + ); + + static const FirebaseOptions windows = FirebaseOptions( + apiKey: 'AIzaSyBkHZtox18LRzXWYHEKVEXaYkkf8jv8Enk', + appId: '1:74256198630:web:6458f24490c3dc80d18427', + messagingSenderId: '74256198630', + projectId: 'playmaker-9e0fc', + authDomain: 'playmaker-9e0fc.firebaseapp.com', + databaseURL: 'https://playmaker-9e0fc-default-rtdb.firebaseio.com', + storageBucket: 'playmaker-9e0fc.firebasestorage.app', + measurementId: 'G-D56MT819B0', + ); +} diff --git a/lib/main.dart b/lib/main.dart index c0fd229..8b6b066 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,7 +1,14 @@ import 'package:flutter/material.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'firebase_options.dart'; import 'pages/login.dart'; -void main() { +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + await Firebase.initializeApp( + options: DefaultFirebaseOptions.currentPlatform, + ); + runApp(const MyApp()); } @@ -11,14 +18,15 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( + debugShowCheckedModeBanner: false, // Opcional: remove a faixa de debug title: 'BasketTrack', theme: ThemeData( colorScheme: ColorScheme.fromSeed( seedColor: const Color(0xFFE74C3C), ), useMaterial3: true, - ), - home: LoginPage(), + ), + home: const LoginPage(), ); } } \ No newline at end of file diff --git a/lib/pages/RegisterPage.dart b/lib/pages/RegisterPage.dart new file mode 100644 index 0000000..b34df99 --- /dev/null +++ b/lib/pages/RegisterPage.dart @@ -0,0 +1,76 @@ +import 'package:flutter/material.dart'; +import 'package:playmaker/controllers/register_controller.dart'; +import '../Controllers/register_controller.dart'; +import '../widgets/register_widgets.dart'; // Import dos novos widgets +import 'home.dart'; + +class RegisterPage extends StatefulWidget { + const RegisterPage({super.key}); + + @override + State createState() => _RegisterPageState(); +} + +class _RegisterPageState extends State { + final RegisterController controller = RegisterController(); + + @override + void dispose() { + controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(backgroundColor: Colors.white, elevation: 0, foregroundColor: Colors.black), + backgroundColor: Colors.white, + body: SafeArea( + child: ListenableBuilder( + listenable: controller, + builder: (context, child) { + return Center( + child: SingleChildScrollView( + padding: const EdgeInsets.all(32), + child: Column( + children: [ + const RegisterHeader(), // Widget do Header + const SizedBox(height: 40), + + RegisterFormFields(controller: controller), // Widget dos Campos + + const SizedBox(height: 32), + + // Botão de Finalizar + SizedBox( + width: double.infinity, + height: 60, + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFFE74C3C), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)), + ), + onPressed: controller.isLoading ? null : () async { + final success = await controller.signUp(); + if (success && mounted) { + Navigator.pushReplacement( + context, + MaterialPageRoute(builder: (context) => const HomeScreen()), + ); + } + }, + child: controller.isLoading + ? const CircularProgressIndicator(color: Colors.white) + : const Text("Criar Conta", style: TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold)), + ), + ), + ], + ), + ), + ); + }, + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/pages/login.dart b/lib/pages/login.dart index d3aea7c..281fc72 100644 --- a/lib/pages/login.dart +++ b/lib/pages/login.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; - import 'package:playmaker/controllers/home_controller.dart'; import 'package:playmaker/pages/home.dart'; import '../widgets/login_widgets.dart'; import '../../Controllers/login_controller.dart'; @@ -14,69 +13,61 @@ class _LoginPageState extends State { final LoginController controller = LoginController(); - @override - void dispose() { - controller.dispose(); - super.dispose(); - } +@override + void dispose() { + controller.dispose(); + super.dispose(); + } - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Colors.white, - body: SafeArea( - child: LayoutBuilder( - builder: (context, constraints) { - final screenWidth = constraints.maxWidth; - final screenHeight = constraints.maxHeight; - - return Center( - child: Container( - width: screenWidth > 800 ? 600.0 : - screenWidth > 600 ? 500.0 : 400.0, - height: screenHeight, // ← USA A ALTURA TOTAL - padding: const EdgeInsets.all(32), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, // ← CENTRALIZA VERTICALMENTE - children: [ - const Expanded( // ← EXPANDE PARA USAR ESPAÇO - flex: 2, - child: SizedBox(), - ), - - const BasketTrackHeader(), - const SizedBox(height: 40), - - LoginFormFields(controller: controller), - const SizedBox(height: 24), - - LoginButton( - controller: controller, - onLoginSuccess: () { + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + body: SafeArea( + child: ListenableBuilder( + listenable: controller, + builder: (context, child) { + return LayoutBuilder( + builder: (context, constraints) { + final screenWidth = constraints.maxWidth; + + return Center( + child: SingleChildScrollView( + child: Container( + width: screenWidth > 800 ? 600.0 : + screenWidth > 600 ? 500.0 : 400.0, + padding: const EdgeInsets.all(32), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const BasketTrackHeader(), + const SizedBox(height: 40), + + LoginFormFields(controller: controller), + const SizedBox(height: 24), + + LoginButton( + controller: controller, + onLoginSuccess: () { Navigator.pushReplacement( - context, - MaterialPageRoute( - builder: (context) => const HomeScreen(), - - ), - ); - }, + context, + MaterialPageRoute(builder: (context) => const HomeScreen()), + ); + }, + ), + const SizedBox(height: 16), + + const CreateAccountButton(), + ], ), - const SizedBox(height: 16), - - const CreateAccountButton(), - - const Expanded( // ← EXPANDE PARA USAR ESPAÇO - flex: 3, - child: SizedBox(), - ), - ], + ), ), - ), - ); - }, - ), + ); + }, + ); + }, ), - ); - } - } \ No newline at end of file + ), + ); + } +} \ No newline at end of file diff --git a/lib/widgets/login_widgets.dart b/lib/widgets/login_widgets.dart index 5b32a08..2b679ba 100644 --- a/lib/widgets/login_widgets.dart +++ b/lib/widgets/login_widgets.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:playmaker/Controllers/login_controller.dart'; +import 'package:playmaker/pages/RegisterPage.dart'; class BasketTrackHeader extends StatelessWidget { const BasketTrackHeader({super.key}); @@ -54,121 +55,47 @@ const LoginFormFields({super.key, required this.controller}); - @override - Widget build(BuildContext context) { - final screenWidth = MediaQuery.of(context).size.width; - - // TAMANHOS AUMENTADOS - final verticalPadding = screenWidth > 600 ? 26.0 : 20.0; // ↑ Aumentado - final spacing = screenWidth > 600 ? 28.0 : 20.0; // ↑ Aumentado - final labelFontSize = screenWidth > 600 ? 18.0 : 16.0; // ↑ Aumentado - final textFontSize = screenWidth > 600 ? 18.0 : 16.0; // ↑ Aumentado + @override + Widget build(BuildContext context) { + final screenWidth = MediaQuery.of(context).size.width; + final verticalPadding = screenWidth > 600 ? 22.0 : 16.0; - return Column( - children: [ - TextField( - controller: controller.emailController, - style: TextStyle(fontSize: textFontSize), // ↑ Tamanho do texto - decoration: InputDecoration( - labelText: 'E-mail', - labelStyle: TextStyle(fontSize: labelFontSize), // ↑ Tamanho do label - prefixIcon: Icon(Icons.email_outlined, size: 24), // ↑ Ícone maior - errorText: controller.emailError, - errorStyle: TextStyle(fontSize: 14), // ↑ Tamanho do erro - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: BorderSide(color: Colors.grey[400]!), - ), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: BorderSide(color: Colors.grey[400]!), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: const BorderSide(color: Color(0xFFE74C3C), width: 2), - ), - contentPadding: EdgeInsets.symmetric( - horizontal: 18, // ↑ Aumentado - vertical: verticalPadding, - ), - ), - keyboardType: TextInputType.emailAddress, - onChanged: (_) { - if (controller.emailError != null) { - controller.validateEmail(controller.emailController.text); - } - }, + return Column( + children: [ + TextField( + controller: controller.emailController, + decoration: InputDecoration( + labelText: 'E-mail', + prefixIcon: const Icon(Icons.email_outlined), + // O erro agora vem diretamente do controller + errorText: controller.emailError, + border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)), + contentPadding: EdgeInsets.symmetric(vertical: verticalPadding, horizontal: 16), ), - SizedBox(height: spacing), - - TextField( - controller: controller.passwordController, - style: TextStyle(fontSize: textFontSize), // ↑ Tamanho do texto - decoration: InputDecoration( - labelText: 'Palavra-passe', - labelStyle: TextStyle(fontSize: labelFontSize), // ↑ Tamanho do label - prefixIcon: Icon(Icons.lock_outlined, size: 24), // ↑ Ícone maior - suffixIcon: IconButton( - icon: Icon( - controller.obscurePassword - ? Icons.visibility_outlined - : Icons.visibility_off_outlined, - color: Colors.grey[600], - size: 24, // ↑ Ícone maior - ), - onPressed: controller.togglePasswordVisibility, - ), - errorText: controller.passwordError, - errorStyle: TextStyle(fontSize: 14), // ↑ Tamanho do erro - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: BorderSide(color: Colors.grey[400]!), - ), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: BorderSide(color: Colors.grey[400]!), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: const BorderSide(color: Color(0xFFE74C3C), width: 2), - ), - contentPadding: EdgeInsets.symmetric( - horizontal: 18, // ↑ Aumentado - vertical: verticalPadding, - ), + keyboardType: TextInputType.emailAddress, + ), + const SizedBox(height: 20), + TextField( + controller: controller.passwordController, + obscureText: controller.obscurePassword, + decoration: InputDecoration( + labelText: 'Palavra-passe', + prefixIcon: const Icon(Icons.lock_outlined), + errorText: controller.passwordError, + suffixIcon: IconButton( + icon: Icon(controller.obscurePassword + ? Icons.visibility_outlined + : Icons.visibility_off_outlined), + onPressed: controller.togglePasswordVisibility, ), - obscureText: controller.obscurePassword, - onChanged: (_) { - if (controller.passwordError != null) { - controller.validatePassword(controller.passwordController.text); - } - }, + border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)), + contentPadding: EdgeInsets.symmetric(vertical: verticalPadding, horizontal: 16), ), - SizedBox(height: screenWidth > 600 ? 20.0 : 14.0), // ↑ Aumentado - - Align( - alignment: Alignment.centerRight, - child: TextButton( - onPressed: () {}, - style: TextButton.styleFrom( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), // ↑ Mais espaço - tapTargetSize: MaterialTapTargetSize.shrinkWrap, - ), - child: Text( - 'Recuperar Palavra-passe', - style: TextStyle( - fontSize: screenWidth > 600 ? 18.0 : 15.0, // ↑ Aumentado - color: const Color(0xFFE74C3C), - fontWeight: FontWeight.w600, // ↑ Mais negrito - ), - ), - ), - ), - ], - ); - } + ), + ], + ); } - +} class LoginButton extends StatelessWidget { final LoginController controller; final VoidCallback onLoginSuccess; @@ -226,37 +153,42 @@ } } - class CreateAccountButton extends StatelessWidget { - const CreateAccountButton({super.key}); +class CreateAccountButton extends StatelessWidget { + const CreateAccountButton({super.key}); - @override - Widget build(BuildContext context) { - final screenWidth = MediaQuery.of(context).size.width; - - // BOTÃO MAIOR - final buttonHeight = screenWidth > 600 ? 70.0 : 58.0; // ↑ Aumentado - final fontSize = screenWidth > 600 ? 22.0 : 18.0; // ↑ Aumentado + @override + Widget build(BuildContext context) { + final screenWidth = MediaQuery.of(context).size.width; + final buttonHeight = screenWidth > 600 ? 70.0 : 58.0; + final fontSize = screenWidth > 600 ? 22.0 : 18.0; - return SizedBox( - width: double.infinity, - height: buttonHeight, - child: OutlinedButton( - onPressed: () {}, - style: OutlinedButton.styleFrom( - foregroundColor: const Color(0xFFE74C3C), - side: const BorderSide(color: Color(0xFFE74C3C), width: 2), // ↑ Borda mais grossa - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(14), // ↑ Bordas mais arredondadas - ), - ), - child: Text( - 'Criar Conta', - style: TextStyle( - fontSize: fontSize, - fontWeight: FontWeight.w700, // ↑ Mais negrito - ), + return SizedBox( + width: double.infinity, + height: buttonHeight, + child: OutlinedButton( + onPressed: () { + // Navega para a página de registo que criaste + Navigator.push( + context, + MaterialPageRoute(builder: (context) => const RegisterPage()), + ); + }, + style: OutlinedButton.styleFrom( + foregroundColor: const Color(0xFFE74C3C), + side: const BorderSide(color: Color(0xFFE74C3C), width: 2), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(14), ), ), - ); - } - } + child: Text( + 'Criar Conta', + style: TextStyle( + fontSize: fontSize, + fontWeight: FontWeight.w700, + ), + ), + ), + ); + } +} + \ No newline at end of file diff --git a/lib/widgets/register_widgets.dart b/lib/widgets/register_widgets.dart new file mode 100644 index 0000000..28f813e --- /dev/null +++ b/lib/widgets/register_widgets.dart @@ -0,0 +1,53 @@ +import 'package:flutter/material.dart'; +import 'package:playmaker/controllers/register_controller.dart'; + +class RegisterFormFields extends StatelessWidget { + final RegisterController controller; + + const RegisterFormFields({super.key, required this.controller}); + + @override + Widget build(BuildContext context) { + final verticalPadding = MediaQuery.of(context).size.width > 600 ? 22.0 : 16.0; + + return Column( + children: [ + // Campo Email + TextField( + controller: controller.emailController, + decoration: InputDecoration( + labelText: 'E-mail', + prefixIcon: const Icon(Icons.email_outlined), + errorText: controller.emailError, + border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)), + ), + ), + const SizedBox(height: 20), + // Campo Password + TextField( + controller: controller.passwordController, + obscureText: true, + decoration: InputDecoration( + labelText: 'Palavra-passe', + prefixIcon: const Icon(Icons.lock_outlined), + errorText: controller.passwordError, + border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)), + ), + ), + const SizedBox(height: 20), + // NOVO: Campo Confirmar Password + TextField( + controller: controller.confirmPasswordController, + obscureText: true, + decoration: InputDecoration( + labelText: 'Confirmar Palavra-passe', + prefixIcon: const Icon(Icons.lock_clock_outlined), + errorText: controller.confirmPasswordError, // Erro específico + border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)), + contentPadding: EdgeInsets.symmetric(vertical: verticalPadding, horizontal: 16), + ), + ), + ], + ); + } +} \ No newline at end of file