firebase esta feita acabar o register

This commit is contained in:
2026-01-06 17:17:37 +00:00
parent 24bd74aedc
commit 44937ca6d3
14 changed files with 594 additions and 233 deletions

View File

@@ -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:

View File

@@ -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"
}

View File

@@ -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
}

3
devtools_options.yaml Normal file
View File

@@ -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:

View File

@@ -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"}}}}}}

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>API_KEY</key>
<string>AIzaSyDxtHmQfK9WVHVAdNEA9zomqgNkNlj5u0g</string>
<key>GCM_SENDER_ID</key>
<string>74256198630</string>
<key>PLIST_VERSION</key>
<string>1</string>
<key>BUNDLE_ID</key>
<string>com.example.playmaker</string>
<key>PROJECT_ID</key>
<string>playmaker-9e0fc</string>
<key>STORAGE_BUCKET</key>
<string>playmaker-9e0fc.firebasestorage.app</string>
<key>IS_ADS_ENABLED</key>
<false></false>
<key>IS_ANALYTICS_ENABLED</key>
<false></false>
<key>IS_APPINVITE_ENABLED</key>
<true></true>
<key>IS_GCM_ENABLED</key>
<true></true>
<key>IS_SIGNIN_ENABLED</key>
<true></true>
<key>GOOGLE_APP_ID</key>
<string>1:74256198630:ios:a3a6ee8f075dd9d2d18427</string>
<key>DATABASE_URL</key>
<string>https://playmaker-9e0fc-default-rtdb.firebaseio.com</string>
</dict>
</plist>

View File

@@ -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<bool> 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<bool> 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();
}
}

View File

@@ -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<bool> 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();
}
}

79
lib/firebase_options.dart Normal file
View File

@@ -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',
);
}

View File

@@ -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(),
);
}
}

View File

@@ -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<RegisterPage> createState() => _RegisterPageState();
}
class _RegisterPageState extends State<RegisterPage> {
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)),
),
),
],
),
),
);
},
),
),
);
}
}

View File

@@ -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<LoginPage> {
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(),
),
],
),
),
),
);
},
),
);
},
);
},
),
);
}
}
),
);
}
}

View File

@@ -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,
),
),
),
);
}
}

View File

@@ -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),
),
),
],
);
}
}