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,
|
# The following line activates a set of recommended lints for Flutter apps,
|
||||||
# packages, and plugins designed to encourage good coding practices.
|
# packages, and plugins designed to encourage good coding practices.
|
||||||
|
analyzer:
|
||||||
|
errors:
|
||||||
|
unused_element: ignore
|
||||||
include: package:flutter_lints/flutter.yaml
|
include: package:flutter_lints/flutter.yaml
|
||||||
|
|
||||||
linter:
|
linter:
|
||||||
|
|||||||
@@ -1,9 +1,49 @@
|
|||||||
{
|
{
|
||||||
"project_info": {
|
"project_info": {
|
||||||
"project_number": "123456789012",
|
"project_number": "74256198630",
|
||||||
"project_id": "playmaker-basketball",
|
"firebase_url": "https://playmaker-9e0fc-default-rtdb.firebaseio.com",
|
||||||
"storage_bucket": "playmaker-basketball.appspot.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"
|
"configuration_version": "1"
|
||||||
}
|
}
|
||||||
@@ -20,6 +20,9 @@ pluginManagement {
|
|||||||
plugins {
|
plugins {
|
||||||
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
||||||
id("com.android.application") version "8.9.1" apply false
|
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
|
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"},"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"}}}}}}
|
||||||
"storage": {
|
|
||||||
"rules": "storage.rules"
|
|
||||||
},
|
|
||||||
"database": {
|
|
||||||
"rules": "database.rules.json"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class LoginController with ChangeNotifier {
|
class LoginController with ChangeNotifier {
|
||||||
|
final FirebaseAuth _auth = FirebaseAuth.instance;
|
||||||
|
|
||||||
final TextEditingController emailController = TextEditingController();
|
final TextEditingController emailController = TextEditingController();
|
||||||
final TextEditingController passwordController = TextEditingController();
|
final TextEditingController passwordController = TextEditingController();
|
||||||
|
|
||||||
@@ -20,26 +23,19 @@ class LoginController with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String? validateEmail(String? value) {
|
String? validateEmail(String? value) {
|
||||||
if (value == null || value.isEmpty) {
|
if (value == null || value.isEmpty) return 'Por favor, insira o seu email';
|
||||||
return 'Por favor, insira o seu email';
|
|
||||||
}
|
|
||||||
final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');
|
final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');
|
||||||
if (!emailRegex.hasMatch(value)) {
|
if (!emailRegex.hasMatch(value)) return 'Por favor, insira um email válido';
|
||||||
return 'Por favor, insira um email válido';
|
|
||||||
}
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
String? validatePassword(String? value) {
|
String? validatePassword(String? value) {
|
||||||
if (value == null || value.isEmpty) {
|
if (value == null || value.isEmpty) return 'Por favor, insira a sua password';
|
||||||
return 'Por favor, insira a sua password';
|
if (value.length < 6) return 'A password deve ter pelo menos 6 caracteres';
|
||||||
}
|
|
||||||
if (value.length < 6) {
|
|
||||||
return 'A password deve ter pelo menos 6 caracteres';
|
|
||||||
}
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- MÉTODO PARA ENTRAR (LOGIN) ---
|
||||||
Future<bool> login() async {
|
Future<bool> login() async {
|
||||||
_emailError = validateEmail(emailController.text);
|
_emailError = validateEmail(emailController.text);
|
||||||
_passwordError = validatePassword(passwordController.text);
|
_passwordError = validatePassword(passwordController.text);
|
||||||
@@ -53,21 +49,76 @@ class LoginController with ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await Future.delayed(const Duration(seconds: 2));
|
await _auth.signInWithEmailAndPassword(
|
||||||
|
email: emailController.text.trim(),
|
||||||
|
password: passwordController.text.trim(),
|
||||||
|
);
|
||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} on FirebaseAuthException catch (e) {
|
||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
_emailError = 'Erro no login. Tente novamente.';
|
_handleFirebaseError(e.code);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
return false;
|
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() {
|
void dispose() {
|
||||||
emailController.dispose();
|
emailController.dispose();
|
||||||
passwordController.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:flutter/material.dart';
|
||||||
|
import 'package:firebase_core/firebase_core.dart';
|
||||||
|
import 'firebase_options.dart';
|
||||||
import 'pages/login.dart';
|
import 'pages/login.dart';
|
||||||
|
|
||||||
void main() {
|
void main() async {
|
||||||
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
await Firebase.initializeApp(
|
||||||
|
options: DefaultFirebaseOptions.currentPlatform,
|
||||||
|
);
|
||||||
|
|
||||||
runApp(const MyApp());
|
runApp(const MyApp());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -11,6 +18,7 @@ class MyApp extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
|
debugShowCheckedModeBanner: false, // Opcional: remove a faixa de debug
|
||||||
title: 'BasketTrack',
|
title: 'BasketTrack',
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
colorScheme: ColorScheme.fromSeed(
|
colorScheme: ColorScheme.fromSeed(
|
||||||
@@ -18,7 +26,7 @@ class MyApp extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
useMaterial3: true,
|
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:flutter/material.dart';
|
||||||
import 'package:playmaker/controllers/home_controller.dart';
|
|
||||||
import 'package:playmaker/pages/home.dart';
|
import 'package:playmaker/pages/home.dart';
|
||||||
import '../widgets/login_widgets.dart';
|
import '../widgets/login_widgets.dart';
|
||||||
import '../../Controllers/login_controller.dart';
|
import '../../Controllers/login_controller.dart';
|
||||||
@@ -25,25 +24,22 @@
|
|||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: Colors.white,
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: LayoutBuilder(
|
child: ListenableBuilder(
|
||||||
|
listenable: controller,
|
||||||
|
builder: (context, child) {
|
||||||
|
return LayoutBuilder(
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
final screenWidth = constraints.maxWidth;
|
final screenWidth = constraints.maxWidth;
|
||||||
final screenHeight = constraints.maxHeight;
|
|
||||||
|
|
||||||
return Center(
|
return Center(
|
||||||
|
child: SingleChildScrollView(
|
||||||
child: Container(
|
child: Container(
|
||||||
width: screenWidth > 800 ? 600.0 :
|
width: screenWidth > 800 ? 600.0 :
|
||||||
screenWidth > 600 ? 500.0 : 400.0,
|
screenWidth > 600 ? 500.0 : 400.0,
|
||||||
height: screenHeight, // ← USA A ALTURA TOTAL
|
|
||||||
padding: const EdgeInsets.all(32),
|
padding: const EdgeInsets.all(32),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center, // ← CENTRALIZA VERTICALMENTE
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
const Expanded( // ← EXPANDE PARA USAR ESPAÇO
|
|
||||||
flex: 2,
|
|
||||||
child: SizedBox(),
|
|
||||||
),
|
|
||||||
|
|
||||||
const BasketTrackHeader(),
|
const BasketTrackHeader(),
|
||||||
const SizedBox(height: 40),
|
const SizedBox(height: 40),
|
||||||
|
|
||||||
@@ -55,24 +51,19 @@
|
|||||||
onLoginSuccess: () {
|
onLoginSuccess: () {
|
||||||
Navigator.pushReplacement(
|
Navigator.pushReplacement(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(builder: (context) => const HomeScreen()),
|
||||||
builder: (context) => const HomeScreen(),
|
|
||||||
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
const CreateAccountButton(),
|
const CreateAccountButton(),
|
||||||
|
|
||||||
const Expanded( // ← EXPANDE PARA USAR ESPAÇO
|
|
||||||
flex: 3,
|
|
||||||
child: SizedBox(),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:playmaker/Controllers/login_controller.dart';
|
import 'package:playmaker/Controllers/login_controller.dart';
|
||||||
|
import 'package:playmaker/pages/RegisterPage.dart';
|
||||||
|
|
||||||
class BasketTrackHeader extends StatelessWidget {
|
class BasketTrackHeader extends StatelessWidget {
|
||||||
const BasketTrackHeader({super.key});
|
const BasketTrackHeader({super.key});
|
||||||
@@ -57,118 +58,44 @@
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final screenWidth = MediaQuery.of(context).size.width;
|
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(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
TextField(
|
TextField(
|
||||||
controller: controller.emailController,
|
controller: controller.emailController,
|
||||||
style: TextStyle(fontSize: textFontSize), // ↑ Tamanho do texto
|
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'E-mail',
|
labelText: 'E-mail',
|
||||||
labelStyle: TextStyle(fontSize: labelFontSize), // ↑ Tamanho do label
|
prefixIcon: const Icon(Icons.email_outlined),
|
||||||
prefixIcon: Icon(Icons.email_outlined, size: 24), // ↑ Ícone maior
|
// O erro agora vem diretamente do controller
|
||||||
errorText: controller.emailError,
|
errorText: controller.emailError,
|
||||||
errorStyle: TextStyle(fontSize: 14), // ↑ Tamanho do erro
|
border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)),
|
||||||
border: OutlineInputBorder(
|
contentPadding: EdgeInsets.symmetric(vertical: verticalPadding, horizontal: 16),
|
||||||
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,
|
keyboardType: TextInputType.emailAddress,
|
||||||
onChanged: (_) {
|
|
||||||
if (controller.emailError != null) {
|
|
||||||
controller.validateEmail(controller.emailController.text);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
SizedBox(height: spacing),
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
TextField(
|
TextField(
|
||||||
controller: controller.passwordController,
|
controller: controller.passwordController,
|
||||||
style: TextStyle(fontSize: textFontSize), // ↑ Tamanho do texto
|
obscureText: controller.obscurePassword,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'Palavra-passe',
|
labelText: 'Palavra-passe',
|
||||||
labelStyle: TextStyle(fontSize: labelFontSize), // ↑ Tamanho do label
|
prefixIcon: const Icon(Icons.lock_outlined),
|
||||||
prefixIcon: Icon(Icons.lock_outlined, size: 24), // ↑ Ícone maior
|
errorText: controller.passwordError,
|
||||||
suffixIcon: IconButton(
|
suffixIcon: IconButton(
|
||||||
icon: Icon(
|
icon: Icon(controller.obscurePassword
|
||||||
controller.obscurePassword
|
|
||||||
? Icons.visibility_outlined
|
? Icons.visibility_outlined
|
||||||
: Icons.visibility_off_outlined,
|
: Icons.visibility_off_outlined),
|
||||||
color: Colors.grey[600],
|
|
||||||
size: 24, // ↑ Ícone maior
|
|
||||||
),
|
|
||||||
onPressed: controller.togglePasswordVisibility,
|
onPressed: controller.togglePasswordVisibility,
|
||||||
),
|
),
|
||||||
errorText: controller.passwordError,
|
border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)),
|
||||||
errorStyle: TextStyle(fontSize: 14), // ↑ Tamanho do erro
|
contentPadding: EdgeInsets.symmetric(vertical: verticalPadding, horizontal: 16),
|
||||||
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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
obscureText: controller.obscurePassword,
|
|
||||||
onChanged: (_) {
|
|
||||||
if (controller.passwordError != null) {
|
|
||||||
controller.validatePassword(controller.passwordController.text);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
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 {
|
class LoginButton extends StatelessWidget {
|
||||||
final LoginController controller;
|
final LoginController controller;
|
||||||
final VoidCallback onLoginSuccess;
|
final VoidCallback onLoginSuccess;
|
||||||
@@ -232,31 +159,36 @@
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final screenWidth = MediaQuery.of(context).size.width;
|
final screenWidth = MediaQuery.of(context).size.width;
|
||||||
|
final buttonHeight = screenWidth > 600 ? 70.0 : 58.0;
|
||||||
// BOTÃO MAIOR
|
final fontSize = screenWidth > 600 ? 22.0 : 18.0;
|
||||||
final buttonHeight = screenWidth > 600 ? 70.0 : 58.0; // ↑ Aumentado
|
|
||||||
final fontSize = screenWidth > 600 ? 22.0 : 18.0; // ↑ Aumentado
|
|
||||||
|
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: buttonHeight,
|
height: buttonHeight,
|
||||||
child: OutlinedButton(
|
child: OutlinedButton(
|
||||||
onPressed: () {},
|
onPressed: () {
|
||||||
|
// Navega para a página de registo que criaste
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(builder: (context) => const RegisterPage()),
|
||||||
|
);
|
||||||
|
},
|
||||||
style: OutlinedButton.styleFrom(
|
style: OutlinedButton.styleFrom(
|
||||||
foregroundColor: const Color(0xFFE74C3C),
|
foregroundColor: const Color(0xFFE74C3C),
|
||||||
side: const BorderSide(color: Color(0xFFE74C3C), width: 2), // ↑ Borda mais grossa
|
side: const BorderSide(color: Color(0xFFE74C3C), width: 2),
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(14), // ↑ Bordas mais arredondadas
|
borderRadius: BorderRadius.circular(14),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
'Criar Conta',
|
'Criar Conta',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: fontSize,
|
fontSize: fontSize,
|
||||||
fontWeight: FontWeight.w700, // ↑ Mais negrito
|
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