firebase esta feita acabar o register
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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
3
devtools_options.yaml
Normal 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:
|
||||
@@ -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"}}}}}}
|
||||
32
ios/Runner/GoogleService-Info.plist
Normal file
32
ios/Runner/GoogleService-Info.plist
Normal 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>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
97
lib/controllers/register_controller.dart
Normal file
97
lib/controllers/register_controller.dart
Normal 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
79
lib/firebase_options.dart
Normal 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',
|
||||
);
|
||||
}
|
||||
@@ -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,6 +18,7 @@ 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(
|
||||
@@ -18,7 +26,7 @@ class MyApp extends StatelessWidget {
|
||||
),
|
||||
useMaterial3: true,
|
||||
),
|
||||
home: LoginPage(),
|
||||
home: const LoginPage(),
|
||||
);
|
||||
}
|
||||
}
|
||||
76
lib/pages/RegisterPage.dart
Normal file
76
lib/pages/RegisterPage.dart
Normal 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)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
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: () {
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const HomeScreen(),
|
||||
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
const CreateAccountButton(),
|
||||
|
||||
const Expanded( // ← EXPANDE PARA USAR ESPAÇO
|
||||
flex: 3,
|
||||
child: SizedBox(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@override
|
||||
void dispose() {
|
||||
controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@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()),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
const CreateAccountButton(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final screenWidth = MediaQuery.of(context).size.width;
|
||||
final verticalPadding = screenWidth > 600 ? 22.0 : 16.0;
|
||||
|
||||
// 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
|
||||
|
||||
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;
|
||||
@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;
|
||||
|
||||
// BOTÃO MAIOR
|
||||
final buttonHeight = screenWidth > 600 ? 70.0 : 58.0; // ↑ Aumentado
|
||||
final fontSize = screenWidth > 600 ? 22.0 : 18.0; // ↑ Aumentado
|
||||
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
53
lib/widgets/register_widgets.dart
Normal file
53
lib/widgets/register_widgets.dart
Normal 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),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user