From b7ca72ed195debe3290bbb0a1a189ec0c65cecf4 Mon Sep 17 00:00:00 2001 From: 230404 <230404@epvc.pt> Date: Wed, 7 Jan 2026 10:40:48 +0000 Subject: [PATCH] focar na tela de equipa --- lib/models/team_model.dart | 14 + lib/pages/RegisterPage.dart | 85 ++--- lib/pages/home.dart | 322 ++++++------------ lib/pages/login.dart | 30 +- lib/pages/teams_page.dart | 94 +++++ lib/widgets/custom_nav_bar.dart | 6 +- lib/widgets/register_widgets.dart | 145 +++++++- macos/Flutter/GeneratedPluginRegistrant.swift | 2 + pubspec.lock | 36 +- pubspec.yaml | 1 + .../flutter/generated_plugin_registrant.cc | 3 + windows/flutter/generated_plugins.cmake | 1 + 12 files changed, 454 insertions(+), 285 deletions(-) create mode 100644 lib/models/team_model.dart create mode 100644 lib/pages/teams_page.dart diff --git a/lib/models/team_model.dart b/lib/models/team_model.dart new file mode 100644 index 0000000..b2dcde4 --- /dev/null +++ b/lib/models/team_model.dart @@ -0,0 +1,14 @@ +class Team { + final String id; + final String name; + + Team({required this.id, required this.name}); + + // Converte de Firebase para o Flutter + factory Team.fromFirestore(Map data, String id) { + return Team( + id: id, + name: data['name'] ?? '', + ); + } +} \ No newline at end of file diff --git a/lib/pages/RegisterPage.dart b/lib/pages/RegisterPage.dart index b34df99..05a9713 100644 --- a/lib/pages/RegisterPage.dart +++ b/lib/pages/RegisterPage.dart @@ -1,7 +1,6 @@ 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 '../widgets/register_widgets.dart'; import 'home.dart'; class RegisterPage extends StatefulWidget { @@ -23,50 +22,54 @@ class _RegisterPageState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(backgroundColor: Colors.white, elevation: 0, foregroundColor: Colors.black), backgroundColor: Colors.white, + // AppBar para poder voltar atrás + appBar: AppBar( + backgroundColor: Colors.white, + elevation: 0, + foregroundColor: Colors.black, + ), 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)), - ), - ), - ], - ), - ), + return LayoutBuilder( + // ... dentro do LayoutBuilder +builder: (context, constraints) { + final screenWidth = constraints.maxWidth; + + return Center( + child: SingleChildScrollView( + child: Container( + + width: screenWidth * 0.6, + constraints: const BoxConstraints(minWidth: 320), + padding: const EdgeInsets.all(32), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const RegisterHeader(), + const SizedBox(height: 40), + + RegisterFormFields(controller: controller), + const SizedBox(height: 32), + + RegisterButton( + controller: controller, + onRegisterSuccess: () { + Navigator.pushAndRemoveUntil( + context, + MaterialPageRoute(builder: (context) => const HomeScreen()), + (route) => false, + ); + }, + ), + ], + ), + ), + ), + ); +}, ); }, ), diff --git a/lib/pages/home.dart b/lib/pages/home.dart index 60683c0..8ad9d1a 100644 --- a/lib/pages/home.dart +++ b/lib/pages/home.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:playmaker/classe/home.config.dart'; import 'package:playmaker/grafico%20de%20pizza/grafico.dart'; +import 'package:playmaker/pages/teams_page.dart'; class HomeScreen extends StatefulWidget { const HomeScreen({super.key}); @@ -11,35 +12,34 @@ class HomeScreen extends StatefulWidget { class _HomeScreenState extends State { int _selectedIndex = 0; - + + // Lista de Widgets para cada aba + // O IndexedStack vai alternar entre estes 4 widgets + late final List _pages; + + @override + void initState() { + super.initState(); + _pages = [ + _buildHomeContent(), // Index 0 + const Center(child: Text('Tela de Jogo')), // Index 1 + const TeamsPage(), // Index 2 (TUA TELA DE EQUIPAS) + const Center(child: Text('Tela de Status')), // Index 3 + ]; + } + void _onItemSelected(int index) { setState(() { _selectedIndex = index; }); - - // Navegação entre telas - switch (index) { - case 0: - // Já está na Home - break; - case 1: - print('Navegar para tela de Jogo'); - // Navigator.push(context, MaterialPageRoute(builder: (_) => GameScreen())); - break; - case 2: - print('Navegar para tela de Equipas'); - // Navigator.push(context, MaterialPageRoute(builder: (_) => TeamsScreen())); - break; - case 3: - print('Navegar para tela de Status'); - // Navigator.push(context, MaterialPageRoute(builder: (_) => StatusScreen())); - break; - } } +//home + @override Widget build(BuildContext context) { return Scaffold( + backgroundColor: Colors.white, appBar: AppBar( title: const Text('PlayMaker'), backgroundColor: HomeConfig.primaryColor, @@ -49,91 +49,13 @@ class _HomeScreenState extends State { onPressed: () {}, ), ), - body: SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(20.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Primeira linha com 2 cards - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - // Card 1 - Estatísticas de Basquete - _buildStatCard( - title: 'Mais Pontos', - playerName: 'Michael Jordan', - statValue: '34.5', - statLabel: 'PPG', - color: Colors.blue[800]!, - icon: Icons.sports_basketball, - isHighlighted: true, - ), - - SizedBox(width: 20), - - // Card 2 - Estatísticas de Basquete (corrigido) - _buildStatCard( - title: 'Mais Assistências', - playerName: 'Magic Johnson', - statValue: '12.8', - statLabel: 'APG', - color: Colors.green[800]!, - icon: Icons.sports_basketball, // Corrigido para basquete - isHighlighted: false, - ), - ], - ), - - SizedBox(height: 20), - - // Segunda linha com 2 cards - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - // Card 3 - Estatísticas de Basquete (corrigido) - _buildStatCard( - title: 'Mais Rebotes', - playerName: 'Dennis Rodman', - statValue: '15.3', - statLabel: 'RPG', - color: Colors.purple[800]!, - icon: Icons.sports_basketball, // Corrigido para basquete - isHighlighted: false, - ), - - SizedBox(width: 20), - - // Card 4 - Gráfico - PieChartCard( - title: 'DESEMPENHO', - subtitle: 'Vitórias vs Derrotas', - backgroundColor: Colors.red[800]!, - onTap: () { - print('Gráfico clicado!'); - }, - ), - ], - ), - - SizedBox(height: 40), - Align( - alignment: Alignment.centerLeft, - child: Text( - 'Histórico de Jogos', - style: TextStyle( - fontSize: 24, - fontWeight: FontWeight.bold, - color: Colors.grey[800], - ), - ), - ), - ], - ), - ), + + // O IndexedStack mantém todas as páginas "vivas" mas só mostra uma + body: IndexedStack( + index: _selectedIndex, + children: _pages, ), - // ⬇️⬇️⬇️ NAVIGATION BAR AQUI ⬇️⬇️⬇️ + bottomNavigationBar: NavigationBar( selectedIndex: _selectedIndex, onDestinationSelected: _onItemSelected, @@ -167,7 +89,76 @@ class _HomeScreenState extends State { ); } - // Método para criar cards de estatísticas + // --- WIDGETS DE CONTEÚDO --- + + Widget _buildHomeContent() { + return SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + _buildStatCard( + title: 'Mais Pontos', + playerName: 'Michael Jordan', + statValue: '34.5', + statLabel: 'PPG', + color: Colors.blue[800]!, + icon: Icons.sports_basketball, + isHighlighted: true, + ), + const SizedBox(width: 20), + _buildStatCard( + title: 'Mais Assistências', + playerName: 'Magic Johnson', + statValue: '12.8', + statLabel: 'APG', + color: Colors.green[800]!, + icon: Icons.sports_basketball, + isHighlighted: false, + ), + ], + ), + const SizedBox(height: 20), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + _buildStatCard( + title: 'Mais Rebotes', + playerName: 'Dennis Rodman', + statValue: '15.3', + statLabel: 'RPG', + color: Colors.purple[800]!, + icon: Icons.sports_basketball, + isHighlighted: false, + ), + const SizedBox(width: 20), + PieChartCard( + title: 'DESEMPENHO', + subtitle: 'Vitórias vs Derrotas', + backgroundColor: Colors.red[800]!, + onTap: () {}, + ), + ], + ), + const SizedBox(height: 40), + Text( + 'Histórico de Jogos', + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: Colors.grey[800], + ), + ), + ], + ), + ), + ); + } + Widget _buildStatCard({ required String title, required String playerName, @@ -177,7 +168,7 @@ class _HomeScreenState extends State { required IconData icon, bool isHighlighted = false, }) { - return Container( + return SizedBox( width: HomeConfig.cardwidthPadding, height: HomeConfig.cardheightPadding, child: Card( @@ -185,7 +176,7 @@ class _HomeScreenState extends State { shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), side: isHighlighted - ? BorderSide(color: Colors.amber, width: 2) + ? const BorderSide(color: Colors.amber, width: 2) : BorderSide.none, ), child: Container( @@ -194,10 +185,7 @@ class _HomeScreenState extends State { gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, - colors: [ - color.withOpacity(0.9), - color.withOpacity(0.7), - ], + colors: [color.withOpacity(0.9), color.withOpacity(0.7)], ), ), child: Padding( @@ -205,7 +193,6 @@ class _HomeScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Cabeçalho com título e ícone Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -213,111 +200,27 @@ class _HomeScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - title.toUpperCase(), - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.bold, - color: Colors.white.withOpacity(0.9), - letterSpacing: 1.5, - ), - ), - SizedBox(height: 5), - Text( - playerName, - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Colors.white, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), + Text(title.toUpperCase(), style: const TextStyle(fontSize: 12, fontWeight: FontWeight.bold, color: Colors.white70)), + Text(playerName, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.white)), ], ), ), - if (isHighlighted) - Container( - padding: EdgeInsets.all(8), - decoration: BoxDecoration( - color: Colors.amber, - shape: BoxShape.circle, - ), - child: Icon( - Icons.star, - size: 20, - color: Colors.white, - ), - ), + if (isHighlighted) const Icon(Icons.star, color: Colors.amber, size: 20), ], ), - - SizedBox(height: 10), - - // Ícone do esporte - Container( - width: 60, - height: 60, - decoration: BoxDecoration( - color: Colors.white.withOpacity(0.2), - shape: BoxShape.circle, - ), - child: Icon( - icon, - size: 30, - color: Colors.white, - ), - ), - - Spacer(), - - // Estatística central + const Spacer(), Center( - child: Column( - children: [ - Text( - statValue, - style: TextStyle( - fontSize: 42, - fontWeight: FontWeight.bold, - color: Colors.white, - height: 1, - ), - ), - SizedBox(height: 5), - Text( - statLabel.toUpperCase(), - style: TextStyle( - fontSize: 14, - color: Colors.white.withOpacity(0.8), - letterSpacing: 2, - ), - ), - ], - ), + child: Text(statValue, style: const TextStyle(fontSize: 38, fontWeight: FontWeight.bold, color: Colors.white)), ), - - Spacer(), - - // Rodapé com botão + Center( + child: Text(statLabel, style: const TextStyle(fontSize: 12, color: Colors.white70)), + ), + const Spacer(), Container( width: double.infinity, - padding: EdgeInsets.symmetric(vertical: 12), - decoration: BoxDecoration( - color: Colors.white.withOpacity(0.2), - borderRadius: BorderRadius.circular(15), - ), - child: Center( - child: Text( - 'VER DETALHES', - style: TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold, - fontSize: 14, - letterSpacing: 1, - ), - ), - ), + padding: const EdgeInsets.symmetric(vertical: 8), + decoration: BoxDecoration(color: Colors.white24, borderRadius: BorderRadius.circular(10)), + child: const Center(child: Text('VER DETALHES', style: TextStyle(color: Colors.white, fontSize: 12))), ), ], ), @@ -326,5 +229,4 @@ class _HomeScreenState extends State { ), ); } -} - +} \ No newline at end of file diff --git a/lib/pages/login.dart b/lib/pages/login.dart index 281fc72..e1b9122 100644 --- a/lib/pages/login.dart +++ b/lib/pages/login.dart @@ -1,19 +1,19 @@ - import 'package:flutter/material.dart'; - import 'package:playmaker/pages/home.dart'; - import '../widgets/login_widgets.dart'; - import '../../Controllers/login_controller.dart'; +import 'package:flutter/material.dart'; +import 'package:playmaker/pages/home.dart'; +import '../widgets/login_widgets.dart'; +import '../../Controllers/login_controller.dart'; - class LoginPage extends StatefulWidget { - const LoginPage({super.key}); +class LoginPage extends StatefulWidget { + const LoginPage({super.key}); - @override - State createState() => _LoginPageState(); - } + @override + State createState() => _LoginPageState(); +} - class _LoginPageState extends State { - final LoginController controller = LoginController(); +class _LoginPageState extends State { + final LoginController controller = LoginController(); -@override + @override void dispose() { controller.dispose(); super.dispose(); @@ -34,8 +34,10 @@ return Center( child: SingleChildScrollView( child: Container( - width: screenWidth > 800 ? 600.0 : - screenWidth > 600 ? 500.0 : 400.0, + // AGORA: Ocupa 60% da largura da tela, igual ao Register + width: screenWidth * 0.6, + // Garante que em telemóveis não fique demasiado apertado + constraints: const BoxConstraints(minWidth: 340), padding: const EdgeInsets.all(32), child: Column( mainAxisAlignment: MainAxisAlignment.center, diff --git a/lib/pages/teams_page.dart b/lib/pages/teams_page.dart new file mode 100644 index 0000000..2beef3b --- /dev/null +++ b/lib/pages/teams_page.dart @@ -0,0 +1,94 @@ +import 'package:flutter/material.dart'; +import 'package:cloud_firestore/cloud_firestore.dart'; +import '../models/team_model.dart'; // Importa o teu modelo + +class TeamsPage extends StatelessWidget { + const TeamsPage({super.key}); + + // Função para abrir o Pop-up + void _showCreateTeamDialog(BuildContext context) { + final TextEditingController _nameController = TextEditingController(); + + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Nova Equipa'), + content: TextField( + controller: _nameController, + decoration: const InputDecoration(hintText: 'Nome da Equipa'), + autofocus: true, + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('Cancelar'), + ), + ElevatedButton( + onPressed: () async { + if (_nameController.text.isNotEmpty) { + // Guarda no Firebase + await FirebaseFirestore.instance.collection('teams').add({ + 'name': _nameController.text, + 'createdAt': FieldValue.serverTimestamp(), + }); + Navigator.pop(context); + } + }, + style: ElevatedButton.styleFrom(backgroundColor: const Color(0xFFE74C3C)), + child: const Text('Criar', style: TextStyle(color: Colors.white)), + ), + ], + ), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + // Removi a AppBar porque a HomeScreen já tem uma, evita barra dupla + body: StreamBuilder( + // Escuta o Firebase em tempo real + stream: FirebaseFirestore.instance.collection('teams').orderBy('createdAt', descending: true).snapshots(), + builder: (context, snapshot) { + if (snapshot.hasError) return const Center(child: Text('Erro ao carregar')); + if (snapshot.connectionState == ConnectionState.waiting) return const Center(child: CircularProgressIndicator()); + + final docs = snapshot.data!.docs; + + if (docs.isEmpty) { + return const Center(child: Text('Nenhuma equipa criada.')); + } + + return ListView.builder( + padding: const EdgeInsets.all(16), + itemCount: docs.length, + itemBuilder: (context, index) { + final team = Team.fromFirestore(docs[index].data() as Map, docs[index].id); + return Card( + elevation: 2, + margin: const EdgeInsets.only(bottom: 12), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + child: ListTile( + leading: const CircleAvatar( + backgroundColor: Color(0xFFE74C3C), + child: Icon(Icons.groups, color: Colors.white), + ), + title: Text(team.name, style: const TextStyle(fontWeight: FontWeight.bold)), + trailing: const Icon(Icons.chevron_right), + onTap: () { + // Navegar para detalhes da equipa no futuro + }, + ), + ); + }, + ); + }, + ), + floatingActionButton: FloatingActionButton( + onPressed: () => _showCreateTeamDialog(context), + backgroundColor: const Color(0xFFE74C3C), + child: const Icon(Icons.add, color: Colors.white), + ), + ); + } +} \ No newline at end of file diff --git a/lib/widgets/custom_nav_bar.dart b/lib/widgets/custom_nav_bar.dart index 130c585..2167778 100644 --- a/lib/widgets/custom_nav_bar.dart +++ b/lib/widgets/custom_nav_bar.dart @@ -12,7 +12,6 @@ class CustomNavBar extends StatelessWidget { @override Widget build(BuildContext context) { - // Usar NavigationBar (Material 3) ao invés de BottomNavigationBar return NavigationBar( selectedIndex: selectedIndex, onDestinationSelected: onItemSelected, @@ -21,25 +20,30 @@ class CustomNavBar extends StatelessWidget { elevation: 1, height: 70, destinations: const [ + NavigationDestination( icon: Icon(Icons.home_outlined), selectedIcon: Icon(Icons.home_filled), label: 'Home', ), + NavigationDestination( icon: Icon(Icons.sports_soccer_outlined), selectedIcon: Icon(Icons.sports_soccer), label: 'Jogo', ), + NavigationDestination( icon: Icon(Icons.people_outline), selectedIcon: Icon(Icons.people), label: 'Equipas', + ), NavigationDestination( icon: Icon(Icons.insights_outlined), selectedIcon: Icon(Icons.insights), label: 'Status', + ), ], ); diff --git a/lib/widgets/register_widgets.dart b/lib/widgets/register_widgets.dart index 28f813e..73a05da 100644 --- a/lib/widgets/register_widgets.dart +++ b/lib/widgets/register_widgets.dart @@ -1,48 +1,110 @@ import 'package:flutter/material.dart'; -import 'package:playmaker/controllers/register_controller.dart'; +import '../Controllers/register_controller.dart'; -class RegisterFormFields extends StatelessWidget { +class RegisterHeader extends StatelessWidget { + const RegisterHeader({super.key}); + + @override + Widget build(BuildContext context) { + final screenWidth = MediaQuery.of(context).size.width; + + // Mesma lógica de tamanhos do Login + final logoSize = screenWidth > 600 ? 150.0 : 100.0; + final titleFontSize = screenWidth > 600 ? 48.0 : 36.0; + final subtitleFontSize = screenWidth > 600 ? 22.0 : 18.0; + + return Column( + children: [ + Icon( + Icons.person_add_outlined, + size: logoSize, + color: const Color(0xFFE74C3C) + ), + const SizedBox(height: 10), + Text( + 'Nova Conta', + style: TextStyle( + fontSize: titleFontSize, + fontWeight: FontWeight.bold, + color: Colors.grey[900], + ), + ), + const SizedBox(height: 5), + Text( + 'Cria o teu perfil no BasketTrack', + style: TextStyle( + fontSize: subtitleFontSize, + color: Colors.grey[600], + fontWeight: FontWeight.w500, + ), + textAlign: TextAlign.center, + ), + ], + ); + } +} + +class RegisterFormFields extends StatefulWidget { final RegisterController controller; + const RegisterFormFields({super.key, required this.controller}); + @override + State createState() => _RegisterFormFieldsState(); +} + +class _RegisterFormFieldsState extends State { + bool _obscurePassword = true; + @override Widget build(BuildContext context) { - final verticalPadding = MediaQuery.of(context).size.width > 600 ? 22.0 : 16.0; + final screenWidth = MediaQuery.of(context).size.width; + // Padding vertical idêntico ao login + final verticalPadding = screenWidth > 600 ? 22.0 : 16.0; return Column( children: [ - // Campo Email TextField( - controller: controller.emailController, + controller: widget.controller.emailController, decoration: InputDecoration( labelText: 'E-mail', prefixIcon: const Icon(Icons.email_outlined), - errorText: controller.emailError, + // O erro agora vem diretamente do controller + errorText: widget.controller.emailError, border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)), + contentPadding: EdgeInsets.symmetric(vertical: verticalPadding, horizontal: 16), ), + keyboardType: TextInputType.emailAddress, ), const SizedBox(height: 20), + // Campo Password TextField( - controller: controller.passwordController, - obscureText: true, + controller: widget.controller.passwordController, + obscureText: _obscurePassword, decoration: InputDecoration( labelText: 'Palavra-passe', prefixIcon: const Icon(Icons.lock_outlined), - errorText: controller.passwordError, + errorText: widget.controller.passwordError, + suffixIcon: IconButton( + icon: Icon(_obscurePassword ? Icons.visibility_outlined : Icons.visibility_off_outlined), + onPressed: () => setState(() => _obscurePassword = !_obscurePassword), + ), border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)), + contentPadding: EdgeInsets.symmetric(vertical: verticalPadding, horizontal: 16), ), ), const SizedBox(height: 20), - // NOVO: Campo Confirmar Password + + // Campo Confirmar Password TextField( - controller: controller.confirmPasswordController, - obscureText: true, + controller: widget.controller.confirmPasswordController, + obscureText: _obscurePassword, decoration: InputDecoration( labelText: 'Confirmar Palavra-passe', prefixIcon: const Icon(Icons.lock_clock_outlined), - errorText: controller.confirmPasswordError, // Erro específico + errorText: widget.controller.confirmPasswordError, border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)), contentPadding: EdgeInsets.symmetric(vertical: verticalPadding, horizontal: 16), ), @@ -50,4 +112,61 @@ class RegisterFormFields extends StatelessWidget { ], ); } +} + +class RegisterButton extends StatelessWidget { + final RegisterController controller; + final VoidCallback onRegisterSuccess; + + const RegisterButton({ + super.key, + required this.controller, + required this.onRegisterSuccess, + }); + + @override + Widget build(BuildContext context) { + final screenWidth = MediaQuery.of(context).size.width; + + // Mesmos tamanhos exatos do LoginButton + final buttonHeight = screenWidth > 600 ? 70.0 : 58.0; + final fontSize = screenWidth > 600 ? 22.0 : 18.0; + + return SizedBox( + width: double.infinity, + height: buttonHeight, + child: ElevatedButton( + onPressed: controller.isLoading ? null : () async { + final success = await controller.signUp(); + if (success) { + onRegisterSuccess(); + } + }, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFFE74C3C), + foregroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(14), + ), + elevation: 3, + ), + child: controller.isLoading + ? const SizedBox( + width: 28, + height: 28, + child: CircularProgressIndicator( + strokeWidth: 3, + valueColor: AlwaysStoppedAnimation(Colors.white), + ), + ) + : Text( + 'Criar Conta', + style: TextStyle( + fontSize: fontSize, + fontWeight: FontWeight.w700, + ), + ), + ), + ); + } } \ No newline at end of file diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 7b9be20..804ef2f 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,10 +5,12 @@ import FlutterMacOS import Foundation +import cloud_firestore import firebase_auth import firebase_core func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + FLTFirebaseFirestorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseFirestorePlugin")) FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin")) FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) } diff --git a/pubspec.lock b/pubspec.lock index 070c0d2..2aa7c22 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: _flutterfire_internals - sha256: "8a1f5f3020ef2a74fb93f7ab3ef127a8feea33a7a2276279113660784ee7516a" + sha256: e4a1b612fd2955908e26116075b3a4baf10c353418ca645b4deae231c82bf144 url: "https://pub.dev" source: hosted - version: "1.3.64" + version: "1.3.65" async: dependency: transitive description: @@ -41,6 +41,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.2" + cloud_firestore: + dependency: "direct main" + description: + name: cloud_firestore + sha256: "88abd6dc7786c23c8b5e434901bb0d79176414e52e9ab50a8e52552ff6148d7a" + url: "https://pub.dev" + source: hosted + version: "6.1.1" + cloud_firestore_platform_interface: + dependency: transitive + description: + name: cloud_firestore_platform_interface + sha256: "573d4b4ebc56ba573dc9bac88b65bdb991cc5b66a885a62c7ab8dd1e2eaa0944" + url: "https://pub.dev" + source: hosted + version: "7.0.5" + cloud_firestore_web: + dependency: transitive + description: + name: cloud_firestore_web + sha256: c1ef53308a09d475503f75b658b65fae32af24047d03bba0713098f882ed187f + url: "https://pub.dev" + source: hosted + version: "5.1.1" collection: dependency: transitive description: @@ -93,10 +117,10 @@ packages: dependency: "direct main" description: name: firebase_core - sha256: "1f2dfd9f535d81f8b06d7a50ecda6eac1e6922191ed42e09ca2c84bd2288927c" + sha256: "29cfa93c771d8105484acac340b5ea0835be371672c91405a300303986f4eba9" url: "https://pub.dev" source: hosted - version: "4.2.1" + version: "4.3.0" firebase_core_platform_interface: dependency: transitive description: @@ -109,10 +133,10 @@ packages: dependency: transitive description: name: firebase_core_web - sha256: ff18fabb0ad0ed3595d2f2c85007ecc794aadecdff5b3bb1460b7ee47cded398 + sha256: a631bbfbfa26963d68046aed949df80b228964020e9155b086eff94f462bbf1f url: "https://pub.dev" source: hosted - version: "3.3.0" + version: "3.3.1" flutter: dependency: "direct main" description: flutter diff --git a/pubspec.yaml b/pubspec.yaml index f07822b..8cdaf4d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -37,6 +37,7 @@ dependencies: provider: ^6.1.5+1 firebase_core: ^4.2.1 firebase_auth: ^6.1.2 + cloud_firestore: ^6.1.1 dev_dependencies: flutter_test: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index d141b74..bf6d21a 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,10 +6,13 @@ #include "generated_plugin_registrant.h" +#include #include #include void RegisterPlugins(flutter::PluginRegistry* registry) { + CloudFirestorePluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("CloudFirestorePluginCApi")); FirebaseAuthPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("FirebaseAuthPluginCApi")); FirebaseCorePluginCApiRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 29944d5..b83b40a 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + cloud_firestore firebase_auth firebase_core )