diff --git a/lib/controllers/login_controller.dart b/lib/controllers/login_controller.dart index 01f7f2b..7df42c8 100644 --- a/lib/controllers/login_controller.dart +++ b/lib/controllers/login_controller.dart @@ -1,8 +1,9 @@ -import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/material.dart'; +import 'package:supabase_flutter/supabase_flutter.dart'; class LoginController with ChangeNotifier { - final FirebaseAuth _auth = FirebaseAuth.instance; + // 1. Substituímos o FirebaseAuth pelo cliente do Supabase + final SupabaseClient _supabase = Supabase.instance.client; final TextEditingController emailController = TextEditingController(); final TextEditingController passwordController = TextEditingController(); @@ -22,6 +23,7 @@ class LoginController with ChangeNotifier { notifyListeners(); } + // --- VALIDAÇÕES (Mantêm-se iguais) --- 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}$'); @@ -37,10 +39,17 @@ class LoginController with ChangeNotifier { // --- MÉTODO PARA ENTRAR (LOGIN) --- Future login() async { - _emailError = validateEmail(emailController.text); - _passwordError = validatePassword(passwordController.text); + // Limpa erros anteriores + _emailError = null; + _passwordError = null; + + // Valida localmente primeiro + String? emailValidation = validateEmail(emailController.text); + String? passValidation = validatePassword(passwordController.text); - if (_emailError != null || _passwordError != null) { + if (emailValidation != null || passValidation != null) { + _emailError = emailValidation; + _passwordError = passValidation; notifyListeners(); return false; } @@ -49,16 +58,25 @@ class LoginController with ChangeNotifier { notifyListeners(); try { - await _auth.signInWithEmailAndPassword( + // 2. Chamada ao Supabase para Login + await _supabase.auth.signInWithPassword( email: emailController.text.trim(), password: passwordController.text.trim(), ); + _isLoading = false; notifyListeners(); return true; - } on FirebaseAuthException catch (e) { + + } on AuthException catch (e) { + // 3. Captura erros específicos do Supabase _isLoading = false; - _handleFirebaseError(e.code); + _handleSupabaseError(e); + notifyListeners(); + return false; + } catch (e) { + _isLoading = false; + _emailError = 'Ocorreu um erro inesperado.'; notifyListeners(); return false; } @@ -66,10 +84,15 @@ class LoginController with ChangeNotifier { // --- MÉTODO PARA CRIAR CONTA (SIGN UP) --- Future signUp() async { - _emailError = validateEmail(emailController.text); - _passwordError = validatePassword(passwordController.text); + _emailError = null; + _passwordError = null; - if (_emailError != null || _passwordError != null) { + String? emailValidation = validateEmail(emailController.text); + String? passValidation = validatePassword(passwordController.text); + + if (emailValidation != null || passValidation != null) { + _emailError = emailValidation; + _passwordError = passValidation; notifyListeners(); return false; } @@ -78,40 +101,46 @@ class LoginController with ChangeNotifier { notifyListeners(); try { - await _auth.createUserWithEmailAndPassword( + // 4. Chamada ao Supabase para Registo + await _supabase.auth.signUp( email: emailController.text.trim(), password: passwordController.text.trim(), ); + _isLoading = false; notifyListeners(); return true; - } on FirebaseAuthException catch (e) { + + } on AuthException catch (e) { _isLoading = false; - _handleFirebaseError(e.code); + _handleSupabaseError(e); + notifyListeners(); + return false; + } catch (e) { + _isLoading = false; + _emailError = 'Ocorreu um erro inesperado.'; 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'; + // --- TRATAMENTO DE ERROS SUPABASE --- + void _handleSupabaseError(AuthException error) { + // O Supabase retorna mensagens em inglês, vamos traduzir as mais comuns. + // O 'message' contém o texto do erro. + final msg = error.message.toLowerCase(); + + if (msg.contains('invalid login credentials')) { + _emailError = 'E-mail ou password incorretos.'; + } else if (msg.contains('user already registered') || msg.contains('already exists')) { + _emailError = 'Este e-mail já está registado.'; + } else if (msg.contains('password')) { + _passwordError = 'A password deve ter pelo menos 6 caracteres.'; + } else if (msg.contains('email')) { + _emailError = 'Formato de e-mail inválido.'; + } else { + // Fallback para mostrar a mensagem original se não conhecermos o erro + _emailError = error.message; } } diff --git a/lib/controllers/register_controller.dart b/lib/controllers/register_controller.dart index a94ae77..5feadea 100644 --- a/lib/controllers/register_controller.dart +++ b/lib/controllers/register_controller.dart @@ -1,25 +1,18 @@ - -import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/material.dart'; +import 'package:supabase_flutter/supabase_flutter.dart'; -class RegisterController with ChangeNotifier { - final FirebaseAuth _auth = FirebaseAuth.instance; +class RegisterController extends ChangeNotifier { + // Chave para identificar e validar o formulário + final GlobalKey formKey = GlobalKey(); - 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! + final nameController = TextEditingController(); + final emailController = TextEditingController(); + final passwordController = TextEditingController(); + final confirmPasswordController = TextEditingController(); // Novo campo - bool get isLoading => _isLoading; - String? get emailError => _emailError; - String? get passwordError => _passwordError; + bool isLoading = false; - // Validações + // --- AS TUAS 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}$'); @@ -32,6 +25,7 @@ class RegisterController with ChangeNotifier { 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'; @@ -41,57 +35,56 @@ class RegisterController with ChangeNotifier { } return null; } - + // --------------------------- - // MÉTODO PARA CRIAR CONTA (SIGN UP) - Future signUp() async { - _emailError = validateEmail(emailController.text); - _passwordError = validatePassword(passwordController.text); - _emailError = validateEmail(emailController.text); - _passwordError = validatePassword(passwordController.text); - _confirmPasswordError = validateConfirmPassword(confirmPasswordController.text); // Valida aqui! - - if (_emailError != null || _passwordError != null || _confirmPasswordError != null) { - notifyListeners(); - return false; + Future signUp(BuildContext context) async { + // 1. Verifica se o formulário é válido antes de fazer qualquer coisa + if (!formKey.currentState!.validate()) { + return; } - _isLoading = true; + isLoading = true; notifyListeners(); try { - await _auth.createUserWithEmailAndPassword( + final AuthResponse res = await Supabase.instance.client.auth.signUp( email: emailController.text.trim(), password: passwordController.text.trim(), + data: {'full_name': nameController.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'; + final user = res.user; + + if (user != null && context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Conta criada! Podes fazer login.')), + ); + Navigator.pop(context); + } + } on AuthException catch (e) { + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(e.message), backgroundColor: Colors.red), + ); + } + } catch (e) { + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Erro inesperado'), backgroundColor: Colors.red), + ); + } + } finally { + isLoading = false; + notifyListeners(); } } @override void dispose() { + nameController.dispose(); emailController.dispose(); passwordController.dispose(); + confirmPasswordController.dispose(); super.dispose(); } } \ No newline at end of file diff --git a/lib/controllers/stats_controller.dart b/lib/controllers/stats_controller.dart index cc93d51..2f77208 100644 --- a/lib/controllers/stats_controller.dart +++ b/lib/controllers/stats_controller.dart @@ -1,146 +1,114 @@ -import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:flutter/material.dart'; +import 'package:supabase_flutter/supabase_flutter.dart'; import '../models/person_model.dart'; class StatsController { - final FirebaseFirestore _db = FirebaseFirestore.instance; + final SupabaseClient _supabase = Supabase.instance.client; - // --- LÓGICA DE FIREBASE --- + // --- 1. LER DADOS (STREAM) --- + Stream> getMembers(String teamId) { + return _supabase + .from('members') + .stream(primaryKey: ['id']) + .eq('team_id', teamId) + .order('name', ascending: true) // Ordena por nome + .map((data) => data.map((json) => Person.fromMap(json)).toList()); + } - // GRAVAR: Cria o personagem numa sub-coleção dentro da equipa - Future addPerson(String teamId, String name, String type, String number) async { - await _db.collection('teams').doc(teamId).collection('members').add({ + // --- 2. AÇÕES DE BASE DE DADOS --- + + // Adicionar + Future _addPersonToSupabase(String teamId, String name, String type, String number) async { + await _supabase.from('members').insert({ + 'team_id': teamId, 'name': name, 'type': type, 'number': number, - 'createdAt': FieldValue.serverTimestamp(), }); } - // LER: Vai buscar todos os membros da equipa em tempo real - Stream> getMembers(String teamId) { - return _db - .collection('teams') - .doc(teamId) - .collection('members') - .orderBy('createdAt', descending: false) // Organiza por ordem de criação - .snapshots() - .map((snapshot) => snapshot.docs - .map((doc) => Person.fromFirestore(doc.data(), doc.id)) - .toList()); + // Editar + Future _updatePersonInSupabase(String personId, String name, String type, String number) async { + await _supabase.from('members').update({ + 'name': name, + 'type': type, + 'number': number, + }).eq('id', personId); } - // --- Adiciona estas funções dentro da classe StatsController --- -// ELIMINAR: Remove o documento da sub-coleção -Future deletePerson(String teamId, String personId) async { - await _db - .collection('teams') - .doc(teamId) - .collection('members') - .doc(personId) - .delete(); -} + // Apagar + Future deletePerson(String teamId, String personId) async { + try { + await _supabase.from('members').delete().eq('id', personId); + } catch (e) { + debugPrint("Erro ao apagar: $e"); + } + } -// EDITAR (LOGICA): Abre o popup já preenchido com os dados atuais -void showEditPersonDialog(BuildContext context, String teamId, Person person) { - final nameController = TextEditingController(text: person.name); - final numberController = TextEditingController(text: person.number); - String selectedType = person.type; - - showDialog( - context: context, - builder: (context) => StatefulBuilder( - builder: (context, setPopupState) => AlertDialog( - title: const Text("Editar Personagem"), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - DropdownButtonFormField( - value: selectedType, - items: ['Jogador', 'Treinador'].map((t) => DropdownMenuItem(value: t, child: Text(t))).toList(), - onChanged: (val) => setPopupState(() => selectedType = val!), - decoration: const InputDecoration(labelText: 'Tipo'), - ), - TextField(controller: nameController, decoration: const InputDecoration(labelText: 'Nome')), - if (selectedType == 'Jogador') - TextField(controller: numberController, decoration: const InputDecoration(labelText: 'Número')), - ], - ), - actions: [ - TextButton(onPressed: () => Navigator.pop(context), child: const Text("Cancelar")), - ElevatedButton( - onPressed: () async { - await _db.collection('teams').doc(teamId).collection('members').doc(person.id).update({ - 'name': nameController.text, - 'type': selectedType, - 'number': selectedType == 'Jogador' ? numberController.text : '', - }); - if (context.mounted) Navigator.pop(context); - }, - child: const Text("Guardar Alterações"), - ), - ], - ), - ), - ); -} - - // --- LÓGICA DE INTERFACE (POPUP) --- + // --- 3. DIÁLOGOS (UI) --- + // Mostrar Diálogo de Adicionar void showAddPersonDialog(BuildContext context, String teamId) { - String selectedType = 'Jogador'; - final TextEditingController nameController = TextEditingController(); - final TextEditingController numberController = TextEditingController(); + _showPersonDialog(context, teamId: teamId); + } + + // Mostrar Diálogo de Editar + void showEditPersonDialog(BuildContext context, String teamId, Person person) { + _showPersonDialog(context, teamId: teamId, person: person); + } + + // Função Genérica para o Diálogo (Serve para criar e editar) + void _showPersonDialog(BuildContext context, {required String teamId, Person? person}) { + final isEditing = person != null; + final nameController = TextEditingController(text: person?.name ?? ''); + final numberController = TextEditingController(text: person?.number ?? ''); + + // Valor inicial do dropdown ('Jogador' por defeito) + String selectedType = person?.type ?? 'Jogador'; showDialog( context: context, builder: (context) { + // Usamos StatefulBuilder para atualizar o Dropdown dentro do Dialog return StatefulBuilder( - builder: (context, setPopupState) { + builder: (context, setState) { return AlertDialog( - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)), - title: const Text('Novo Personagem'), - content: SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - // Seletor: Jogador ou Treinador - DropdownButtonFormField( - value: selectedType, - decoration: const InputDecoration(labelText: 'Tipo'), - items: ['Jogador', 'Treinador'] - .map((t) => DropdownMenuItem(value: t, child: Text(t))) - .toList(), - onChanged: (val) { - setPopupState(() { - selectedType = val!; - }); - }, - ), - const SizedBox(height: 15), - // Campo Nome - TextField( - controller: nameController, - textCapitalization: TextCapitalization.words, - decoration: const InputDecoration( - labelText: 'Nome Completo', - hintText: 'Ex: Stephen Curry', - ), - ), - // Campo Número (Aparece apenas se for Jogador) - if (selectedType == 'Jogador') ...[ - const SizedBox(height: 15), - TextField( - controller: numberController, - keyboardType: TextInputType.number, - decoration: const InputDecoration( - labelText: 'Número da Camisola', - hintText: 'Ex: 30', - ), - ), + title: Text(isEditing ? 'Editar Membro' : 'Novo Membro'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // Nome + TextField( + controller: nameController, + decoration: const InputDecoration(labelText: 'Nome'), + textCapitalization: TextCapitalization.sentences, + ), + const SizedBox(height: 10), + + // Tipo (Jogador/Treinador) + DropdownButtonFormField( + value: selectedType, + decoration: const InputDecoration(labelText: 'Função'), + items: const [ + DropdownMenuItem(value: 'Jogador', child: Text('Jogador')), + DropdownMenuItem(value: 'Treinador', child: Text('Treinador')), ], - ], - ), + onChanged: (value) { + if (value != null) { + setState(() => selectedType = value); + } + }, + ), + const SizedBox(height: 10), + + // Número (Só aparece se for Jogador) + if (selectedType == 'Jogador') + TextField( + controller: numberController, + decoration: const InputDecoration(labelText: 'Número da Camisola'), + keyboardType: TextInputType.number, + ), + ], ), actions: [ TextButton( @@ -148,23 +116,22 @@ void showEditPersonDialog(BuildContext context, String teamId, Person person) { child: const Text('Cancelar'), ), ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFF00C853), - ), + style: ElevatedButton.styleFrom(backgroundColor: const Color(0xFF00C853)), onPressed: () async { - if (nameController.text.isNotEmpty) { - // CHAMA A FUNÇÃO DE GRAVAR DO FIREBASE - await addPerson( - teamId, - nameController.text, - selectedType, - selectedType == 'Jogador' ? numberController.text : '', - ); - - if (context.mounted) Navigator.pop(context); + if (nameController.text.isEmpty) return; + + final name = nameController.text.trim(); + final number = numberController.text.trim(); + + if (isEditing) { + await _updatePersonInSupabase(person!.id, name, selectedType, number); + } else { + await _addPersonToSupabase(teamId, name, selectedType, number); } + + if (context.mounted) Navigator.pop(context); }, - child: const Text('Guardar', style: TextStyle(color: Colors.white)), + child: Text(isEditing ? 'Guardar' : 'Adicionar', style: const TextStyle(color: Colors.white)), ), ], ); diff --git a/lib/controllers/team_controllers.dart b/lib/controllers/team_controllers.dart index 9ad2289..63408fd 100644 --- a/lib/controllers/team_controllers.dart +++ b/lib/controllers/team_controllers.dart @@ -1,31 +1,37 @@ -import 'package:cloud_firestore/cloud_firestore.dart'; -import 'package:playmaker/service/auth_service.dart'; +import 'package:supabase_flutter/supabase_flutter.dart'; class TeamController { - final AuthService _authService = AuthService(); - final CollectionReference _teamsRef = FirebaseFirestore.instance.collection('teams'); + // Acesso ao cliente do Supabase + final SupabaseClient _supabase = Supabase.instance.client; + // --- STREAM DE EQUIPAS (LER) --- + // Retorna uma Lista de Mapas em tempo real + Stream>> get teamsStream { + final user = _supabase.auth.currentUser; + + if (user == null) { + return const Stream.empty(); + } - Stream get teamsStream { - final uid = _authService.currentUid; - return _teamsRef - .where('userId', isEqualTo: uid) - .orderBy('createdAt', descending: true) - .snapshots(); + return _supabase + .from('teams') + .stream(primaryKey: ['id']) // É obrigatório definir a Primary Key para Streams + .eq('user_id', user.id) // Filtra apenas as equipas do utilizador logado + .order('created_at', ascending: false); } // --- CRIAR EQUIPA --- Future createTeam(String name, String season, String imageUrl) async { - final uid = _authService.currentUid; + final user = _supabase.auth.currentUser; - if (uid != null) { + if (user != null) { try { - await _teamsRef.add({ + await _supabase.from('teams').insert({ 'name': name, 'season': season, - 'imageUrl': imageUrl, - 'userId': uid, - 'createdAt': FieldValue.serverTimestamp(), + 'image_url': imageUrl, // Garante que na tabela a coluna se chama 'image_url' (snake_case) + 'user_id': user.id, // Chave estrangeira para ligar ao utilizador + // 'created_at': O Supabase preenche isto sozinho se tiver default: now() }); } catch (e) { print("Erro ao criar equipa: $e"); @@ -34,15 +40,32 @@ class TeamController { print("Erro: Utilizador não autenticado."); } } + + // --- ELIMINAR EQUIPA --- Future deleteTeam(String docId) async { try { - await _teamsRef.doc(docId).delete(); + // Se configuraste "ON DELETE CASCADE" no Supabase, isto apaga também os jogadores + await _supabase.from('teams').delete().eq('id', docId); } catch (e) { print("Erro ao eliminar: $e"); } - Future getPlayerCount(String teamId) async { - var snapshot = await _teamsRef.doc(teamId).collection('players').get(); - return snapshot.docs.length; } -} + + // --- CONTAR JOGADORES --- + // No SQL não entramos dentro da equipa. Vamos à tabela 'members' e filtramos pelo team_id. + // --- CONTAR JOGADORES (CORRIGIDO) --- + Future getPlayerCount(String teamId) async { + try { + // Correção: O Supabase agora retorna o 'int' diretamente, não um objeto response + final int count = await _supabase + .from('members') + .count(CountOption.exact) // Pede o número exato + .eq('team_id', teamId); + + return count; + } catch (e) { + print("Erro ao contar jogadores: $e"); + return 0; + } + } } \ No newline at end of file diff --git a/lib/firebase_options.dart b/lib/firebase_options.dart deleted file mode 100644 index c0a142a..0000000 --- a/lib/firebase_options.dart +++ /dev/null @@ -1,79 +0,0 @@ -// File generated by FlutterFire CLI. -// ignore_for_file: type=lint -import 'package:firebase_core/firebase_core.dart' show FirebaseOptions; -import 'package:flutter/foundation.dart' - show defaultTargetPlatform, kIsWeb, TargetPlatform; - -/// Default [FirebaseOptions] for use with your Firebase apps. -/// -/// Example: -/// ```dart -/// import 'firebase_options.dart'; -/// // ... -/// await Firebase.initializeApp( -/// options: DefaultFirebaseOptions.currentPlatform, -/// ); -/// ``` -class DefaultFirebaseOptions { - static FirebaseOptions get currentPlatform { - if (kIsWeb) { - return web; - } - switch (defaultTargetPlatform) { - case TargetPlatform.android: - return android; - case TargetPlatform.iOS: - throw UnsupportedError( - 'DefaultFirebaseOptions have not been configured for ios - ' - 'you can reconfigure this by running the FlutterFire CLI again.', - ); - case TargetPlatform.macOS: - throw UnsupportedError( - 'DefaultFirebaseOptions have not been configured for macos - ' - 'you can reconfigure this by running the FlutterFire CLI again.', - ); - case TargetPlatform.windows: - return windows; - case TargetPlatform.linux: - throw UnsupportedError( - 'DefaultFirebaseOptions have not been configured for linux - ' - 'you can reconfigure this by running the FlutterFire CLI again.', - ); - default: - throw UnsupportedError( - 'DefaultFirebaseOptions are not supported for this platform.', - ); - } - } - - static const FirebaseOptions web = FirebaseOptions( - apiKey: 'AIzaSyBkHZtox18LRzXWYHEKVEXaYkkf8jv8Enk', - appId: '1:74256198630:web:ba3d62a31608d686d18427', - messagingSenderId: '74256198630', - projectId: 'playmaker-9e0fc', - authDomain: 'playmaker-9e0fc.firebaseapp.com', - databaseURL: 'https://playmaker-9e0fc-default-rtdb.firebaseio.com', - storageBucket: 'playmaker-9e0fc.firebasestorage.app', - measurementId: 'G-QQE1EZWZ8K', - ); - - static const FirebaseOptions android = FirebaseOptions( - apiKey: 'AIzaSyDm7MBJQ6vZEE_gM1Ek5LH3Mf5ui2YHc2I', - appId: '1:74256198630:android:145e08f6bc85ff13d18427', - messagingSenderId: '74256198630', - projectId: 'playmaker-9e0fc', - databaseURL: 'https://playmaker-9e0fc-default-rtdb.firebaseio.com', - storageBucket: 'playmaker-9e0fc.firebasestorage.app', - ); - - static const FirebaseOptions windows = FirebaseOptions( - apiKey: 'AIzaSyBkHZtox18LRzXWYHEKVEXaYkkf8jv8Enk', - appId: '1:74256198630:web:6458f24490c3dc80d18427', - messagingSenderId: '74256198630', - projectId: 'playmaker-9e0fc', - authDomain: 'playmaker-9e0fc.firebaseapp.com', - databaseURL: 'https://playmaker-9e0fc-default-rtdb.firebaseio.com', - storageBucket: 'playmaker-9e0fc.firebasestorage.app', - measurementId: 'G-D56MT819B0', - ); -} diff --git a/lib/main.dart b/lib/main.dart index 8b6b066..610fca9 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,31 +1,34 @@ import 'package:flutter/material.dart'; -import 'package:firebase_core/firebase_core.dart'; -import 'firebase_options.dart'; +import 'package:supabase_flutter/supabase_flutter.dart'; import 'pages/login.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); - await Firebase.initializeApp( - options: DefaultFirebaseOptions.currentPlatform, + + await Supabase.initialize( + url: 'https://sihwjdshexjyvsbettcd.supabase.co', + anonKey: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InNpaHdqZHNoZXhqeXZzYmV0dGNkIiwicm9sZSI6ImFub24iLCJpYXQiOjE3Njg5MTQxMjgsImV4cCI6MjA4NDQ5MDEyOH0.gW3AvTJVNyE1Dqa72OTnhrUIKsndexrY3pKxMIAaAy8', // Uma string longa + ); runApp(const MyApp()); } + class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( - debugShowCheckedModeBanner: false, // Opcional: remove a faixa de debug - title: 'BasketTrack', + debugShowCheckedModeBanner: false, + title: 'PlayMaker', theme: ThemeData( colorScheme: ColorScheme.fromSeed( - seedColor: const Color(0xFFE74C3C), + seedColor: const Color(0xFFE74C3C), ), useMaterial3: true, - ), + ), home: const LoginPage(), ); } diff --git a/lib/models/person_model.dart b/lib/models/person_model.dart index 0cebed2..dd6bb0b 100644 --- a/lib/models/person_model.dart +++ b/lib/models/person_model.dart @@ -1,17 +1,26 @@ class Person { final String id; + final String teamId; final String name; final String type; // 'Jogador' ou 'Treinador' - final String number; // Ex: '30' + final String number; - Person({required this.id, required this.name, required this.type, required this.number}); + Person({ + required this.id, + required this.teamId, + required this.name, + required this.type, + required this.number, + }); - factory Person.fromFirestore(Map data, String id) { + // Converter do Supabase (Map) para o Objeto + factory Person.fromMap(Map map) { return Person( - id: id, - name: data['name'] ?? '', - type: data['type'] ?? 'Jogador', - number: data['number'] ?? '', + id: map['id'] ?? '', + teamId: map['team_id'] ?? '', + name: map['name'] ?? '', + type: map['type'] ?? 'Jogador', + number: map['number']?.toString() ?? '', ); } } \ No newline at end of file diff --git a/lib/pages/RegisterPage.dart b/lib/pages/RegisterPage.dart index 05a9713..2552d95 100644 --- a/lib/pages/RegisterPage.dart +++ b/lib/pages/RegisterPage.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; -import '../Controllers/register_controller.dart'; +import '../controllers/register_controller.dart'; import '../widgets/register_widgets.dart'; -import 'home.dart'; class RegisterPage extends StatefulWidget { const RegisterPage({super.key}); @@ -11,67 +10,42 @@ class RegisterPage extends StatefulWidget { } class _RegisterPageState extends State { - final RegisterController controller = RegisterController(); + // Instancia o controller + final RegisterController _controller = RegisterController(); @override void dispose() { - controller.dispose(); + _controller.dispose(); // Limpa a memória ao sair super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( - 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 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, - ); - }, - ), - ], - ), - ), - ), - ); -}, - ); - }, + appBar: AppBar(title: const Text("Criar Conta")), + body: Center( + child: SingleChildScrollView( + padding: const EdgeInsets.all(24.0), + child: ListenableBuilder( + listenable: _controller, // Ouve as mudanças (loading) + builder: (context, child) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + "Junta-te à Equipa!", + style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 30), + + // Widgets Extraídos + RegisterFormFields(controller: _controller), + const SizedBox(height: 24), + RegisterButton(controller: _controller), + ], + ); + }, + ), ), ), ); diff --git a/lib/pages/home.dart b/lib/pages/home.dart index 8ad9d1a..b0724f1 100644 --- a/lib/pages/home.dart +++ b/lib/pages/home.dart @@ -1,7 +1,8 @@ import 'package:flutter/material.dart'; import 'package:playmaker/classe/home.config.dart'; +import 'package:playmaker/controllers/team_controllers.dart'; import 'package:playmaker/grafico%20de%20pizza/grafico.dart'; -import 'package:playmaker/pages/teams_page.dart'; +// Certifica-te que o caminho do controller está correto: class HomeScreen extends StatefulWidget { const HomeScreen({super.key}); @@ -12,19 +13,20 @@ class HomeScreen extends StatefulWidget { class _HomeScreenState extends State { int _selectedIndex = 0; + + // 1. Instanciar o Controller para aceder ao Supabase + final TeamController _teamController = TeamController(); - // 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 + _buildHomeContent(), // Index 0: Home + const Center(child: Text('Tela de Jogo')), // Index 1: Jogo + _buildTeamsContent(), // Index 2: Equipas (O teu StreamBuilder entra aqui) + const Center(child: Text('Tela de Status')), // Index 3: Status ]; } @@ -34,8 +36,6 @@ class _HomeScreenState extends State { }); } -//home - @override Widget build(BuildContext context) { return Scaffold( @@ -50,7 +50,6 @@ class _HomeScreenState extends State { ), ), - // O IndexedStack mantém todas as páginas "vivas" mas só mostra uma body: IndexedStack( index: _selectedIndex, children: _pages, @@ -91,6 +90,65 @@ class _HomeScreenState extends State { // --- WIDGETS DE CONTEÚDO --- + // 2. O teu StreamBuilder foi movido para aqui + Widget _buildTeamsContent() { + return StreamBuilder>>( + stream: _teamController.teamsStream, + builder: (context, snapshot) { + // Verificar estado de carregamento + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } + + // Verificar erros ou lista vazia + if (snapshot.hasError) { + return Center(child: Text("Erro: ${snapshot.error}")); + } + if (!snapshot.hasData || snapshot.data!.isEmpty) { + return const Center(child: Text("Ainda não tens equipas.")); + } + + // Obter dados (Lista simples do Supabase) + final teams = snapshot.data!; + + return ListView.builder( + padding: const EdgeInsets.all(16), + itemCount: teams.length, + itemBuilder: (context, index) { + final team = teams[index]; + + // Construção do Card da Equipa + return Card( + elevation: 2, + margin: const EdgeInsets.only(bottom: 12), + child: ListTile( + leading: CircleAvatar( + backgroundColor: HomeConfig.primaryColor, + child: Text( + team['name'][0].toUpperCase(), + style: const TextStyle(color: Colors.white), + ), + ), + title: Text( + team['name'], + style: const TextStyle(fontWeight: FontWeight.bold), + ), + subtitle: Text("Época: ${team['season']}"), + trailing: IconButton( + icon: const Icon(Icons.delete, color: Colors.red), + onPressed: () { + // Confirmação antes de apagar (Opcional, mas recomendado) + _teamController.deleteTeam(team['id']); + }, + ), + ), + ); + }, + ); + }, + ); + } + Widget _buildHomeContent() { return SingleChildScrollView( child: Padding( diff --git a/lib/pages/login.dart b/lib/pages/login.dart index e1b9122..93839b3 100644 --- a/lib/pages/login.dart +++ b/lib/pages/login.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:playmaker/pages/home.dart'; +import 'package:playmaker/controllers/login_controller.dart'; import '../widgets/login_widgets.dart'; -import '../../Controllers/login_controller.dart'; +import 'home.dart'; // <--- IMPORTANTE: Importa a tua HomeScreen class LoginPage extends StatefulWidget { const LoginPage({super.key}); @@ -34,9 +34,7 @@ class _LoginPageState extends State { return Center( child: SingleChildScrollView( child: Container( - // 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( @@ -48,13 +46,17 @@ class _LoginPageState extends State { LoginFormFields(controller: controller), const SizedBox(height: 24), + // AQUI ESTÁ A MUDANÇA PRINCIPAL LoginButton( controller: controller, onLoginSuccess: () { - Navigator.pushReplacement( - context, - MaterialPageRoute(builder: (context) => const HomeScreen()), - ); + // Verifica se o widget ainda está no ecrã antes de navegar + if (mounted) { + Navigator.pushReplacement( + context, + MaterialPageRoute(builder: (context) => const HomeScreen()), + ); + } }, ), const SizedBox(height: 16), diff --git a/lib/pages/teams_page.dart b/lib/pages/teams_page.dart index 9e61431..064fdb9 100644 --- a/lib/pages/teams_page.dart +++ b/lib/pages/teams_page.dart @@ -1,5 +1,4 @@ -import 'package:flutter/material.dart'; -import 'package:cloud_firestore/cloud_firestore.dart'; +/*import 'package:flutter/material.dart'; import 'package:playmaker/controllers/team_controllers.dart'; import '../models/team_model.dart'; import '../widgets/team_widgets.dart'; @@ -13,7 +12,6 @@ class TeamsPage extends StatelessWidget { final TeamController controller = TeamController(); return Scaffold( - body: StreamBuilder( stream: controller.teamsStream, builder: (context, snapshot) { if (snapshot.hasError) return const Center(child: Text('Erro ao carregar')); @@ -57,4 +55,4 @@ class TeamsPage extends StatelessWidget { ), ); } -} \ No newline at end of file +}*/ \ No newline at end of file diff --git a/lib/screens/team_stats_page.dart b/lib/screens/team_stats_page.dart index 6497ea5..50222fe 100644 --- a/lib/screens/team_stats_page.dart +++ b/lib/screens/team_stats_page.dart @@ -125,4 +125,7 @@ class TeamStatsPage extends StatelessWidget { ], ); } -} \ No newline at end of file +} + +class StatsController { +} \ No newline at end of file diff --git a/lib/service/auth_service.dart b/lib/service/auth_service.dart deleted file mode 100644 index 6002b7f..0000000 --- a/lib/service/auth_service.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'package:firebase_auth/firebase_auth.dart'; - -class AuthService { - final FirebaseAuth _auth = FirebaseAuth.instance; - - // Retorna o ID do utilizador atual - String? get currentUid => _auth.currentUser?.uid; - - // Retorna o email do utilizador (útil para mostrar no perfil) - String? get currentUserEmail => _auth.currentUser?.email; - - // Verifica se o utilizador está logado - bool get isLoggedIn => _auth.currentUser != null; - - // Função para fazer Logout (Sair) - Future signOut() async { - await _auth.signOut(); - } -} \ No newline at end of file diff --git a/lib/widgets/login_widgets.dart b/lib/widgets/login_widgets.dart index 2b679ba..7565183 100644 --- a/lib/widgets/login_widgets.dart +++ b/lib/widgets/login_widgets.dart @@ -1,5 +1,5 @@ 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 { diff --git a/lib/widgets/register_widgets.dart b/lib/widgets/register_widgets.dart index 73a05da..3982b01 100644 --- a/lib/widgets/register_widgets.dart +++ b/lib/widgets/register_widgets.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import '../Controllers/register_controller.dart'; +import '../controllers/register_controller.dart'; // Garante que o caminho está certo class RegisterHeader extends StatelessWidget { const RegisterHeader({super.key}); @@ -8,7 +8,6 @@ class RegisterHeader extends StatelessWidget { 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; @@ -46,7 +45,6 @@ class RegisterHeader extends StatelessWidget { class RegisterFormFields extends StatefulWidget { final RegisterController controller; - const RegisterFormFields({super.key, required this.controller}); @@ -60,75 +58,88 @@ class _RegisterFormFieldsState extends State { @override Widget build(BuildContext context) { final screenWidth = MediaQuery.of(context).size.width; - // Padding vertical idêntico ao login final verticalPadding = screenWidth > 600 ? 22.0 : 16.0; - return Column( - children: [ - TextField( - controller: widget.controller.emailController, - decoration: InputDecoration( - labelText: 'E-mail', - prefixIcon: const Icon(Icons.email_outlined), - // 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: widget.controller.passwordController, - obscureText: _obscurePassword, - decoration: InputDecoration( - labelText: 'Palavra-passe', - prefixIcon: const Icon(Icons.lock_outlined), - errorText: widget.controller.passwordError, - suffixIcon: IconButton( - icon: Icon(_obscurePassword ? Icons.visibility_outlined : Icons.visibility_off_outlined), - onPressed: () => setState(() => _obscurePassword = !_obscurePassword), + // IMPORTANTE: Envolvemos tudo num Form usando a chave do controller + return Form( + key: widget.controller.formKey, + child: Column( + children: [ + // Campo Nome (Opcional, mas útil) + TextFormField( + controller: widget.controller.nameController, + decoration: InputDecoration( + labelText: 'Nome Completo', + prefixIcon: const Icon(Icons.person_outline), + border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)), + contentPadding: EdgeInsets.symmetric(vertical: verticalPadding, horizontal: 16), ), - border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)), - contentPadding: EdgeInsets.symmetric(vertical: verticalPadding, horizontal: 16), ), - ), - const SizedBox(height: 20), + const SizedBox(height: 20), - // Campo Confirmar Password - TextField( - controller: widget.controller.confirmPasswordController, - obscureText: _obscurePassword, - decoration: InputDecoration( - labelText: 'Confirmar Palavra-passe', - prefixIcon: const Icon(Icons.lock_clock_outlined), - errorText: widget.controller.confirmPasswordError, - border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)), - contentPadding: EdgeInsets.symmetric(vertical: verticalPadding, horizontal: 16), + // Campo Email + TextFormField( + controller: widget.controller.emailController, + // Validação automática ligada ao controller + validator: widget.controller.validateEmail, + decoration: InputDecoration( + labelText: 'E-mail', + prefixIcon: const Icon(Icons.email_outlined), + border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)), + contentPadding: EdgeInsets.symmetric(vertical: verticalPadding, horizontal: 16), + ), + keyboardType: TextInputType.emailAddress, ), - ), - ], + const SizedBox(height: 20), + + // Campo Password + TextFormField( + controller: widget.controller.passwordController, + obscureText: _obscurePassword, + validator: widget.controller.validatePassword, + decoration: InputDecoration( + labelText: 'Palavra-passe', + prefixIcon: const Icon(Icons.lock_outlined), + 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), + + // Campo Confirmar Password + TextFormField( + controller: widget.controller.confirmPasswordController, + obscureText: _obscurePassword, + validator: widget.controller.validateConfirmPassword, + decoration: InputDecoration( + labelText: 'Confirmar Palavra-passe', + prefixIcon: const Icon(Icons.lock_clock_outlined), + border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)), + contentPadding: EdgeInsets.symmetric(vertical: verticalPadding, horizontal: 16), + ), + ), + ], + ), ); } } 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; @@ -136,12 +147,8 @@ class RegisterButton extends StatelessWidget { width: double.infinity, height: buttonHeight, child: ElevatedButton( - onPressed: controller.isLoading ? null : () async { - final success = await controller.signUp(); - if (success) { - onRegisterSuccess(); - } - }, + // Passamos o context para o controller lidar com as SnackBars e Navegação + onPressed: controller.isLoading ? null : () => controller.signUp(context), style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFFE74C3C), foregroundColor: Colors.white, diff --git a/lib/widgets/team_widgets.dart b/lib/widgets/team_widgets.dart index 3976d7c..433024e 100644 --- a/lib/widgets/team_widgets.dart +++ b/lib/widgets/team_widgets.dart @@ -14,7 +14,7 @@ class TeamCard extends StatelessWidget { }); - @override + @override Widget build(BuildContext context) { return Card( elevation: 3, diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index e71a16d..3792af4 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,6 +6,14 @@ #include "generated_plugin_registrant.h" +#include +#include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) gtk_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "GtkPlugin"); + gtk_plugin_register_with_registrar(gtk_registrar); + g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); + url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); } diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 2e1de87..5d07423 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,6 +3,8 @@ # list(APPEND FLUTTER_PLUGIN_LIST + gtk + url_launcher_linux ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 804ef2f..92b6497 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,12 +5,14 @@ import FlutterMacOS import Foundation -import cloud_firestore -import firebase_auth -import firebase_core +import app_links +import path_provider_foundation +import shared_preferences_foundation +import url_launcher_macos 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")) + AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin")) + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) + UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index 2aa7c22..720ba58 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,14 +1,46 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: - _flutterfire_internals: + adaptive_number: dependency: transitive description: - name: _flutterfire_internals - sha256: e4a1b612fd2955908e26116075b3a4baf10c353418ca645b4deae231c82bf144 + name: adaptive_number + sha256: "3a567544e9b5c9c803006f51140ad544aedc79604fd4f3f2c1380003f97c1d77" url: "https://pub.dev" source: hosted - version: "1.3.65" + version: "1.0.0" + app_links: + dependency: transitive + description: + name: app_links + sha256: "5f88447519add627fe1cbcab4fd1da3d4fed15b9baf29f28b22535c95ecee3e8" + url: "https://pub.dev" + source: hosted + version: "6.4.1" + app_links_linux: + dependency: transitive + description: + name: app_links_linux + sha256: f5f7173a78609f3dfd4c2ff2c95bd559ab43c80a87dc6a095921d96c05688c81 + url: "https://pub.dev" + source: hosted + version: "1.0.3" + app_links_platform_interface: + dependency: transitive + description: + name: app_links_platform_interface + sha256: "05f5379577c513b534a29ddea68176a4d4802c46180ee8e2e966257158772a3f" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + app_links_web: + dependency: transitive + description: + name: app_links_web + sha256: af060ed76183f9e2b87510a9480e56a5352b6c249778d07bd2c95fc35632a555 + url: "https://pub.dev" + source: hosted + version: "1.0.4" async: dependency: transitive description: @@ -41,30 +73,6 @@ 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: @@ -73,6 +81,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + crypto: + dependency: transitive + description: + name: crypto + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.dev" + source: hosted + version: "3.0.7" cupertino_icons: dependency: "direct main" description: @@ -81,6 +105,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.8" + dart_jsonwebtoken: + dependency: transitive + description: + name: dart_jsonwebtoken + sha256: "0de65691c1d736e9459f22f654ddd6fd8368a271d4e41aa07e53e6301eff5075" + url: "https://pub.dev" + source: hosted + version: "3.3.1" + ed25519_edwards: + dependency: transitive + description: + name: ed25519_edwards + sha256: "6ce0112d131327ec6d42beede1e5dfd526069b18ad45dcf654f15074ad9276cd" + url: "https://pub.dev" + source: hosted + version: "0.3.1" fake_async: dependency: transitive description: @@ -89,54 +129,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.3" - firebase_auth: - dependency: "direct main" - description: - name: firebase_auth - sha256: e54fb3ba57de041d832574126a37726eedf0f57400869f1942b0ca8ce4a6e209 - url: "https://pub.dev" - source: hosted - version: "6.1.2" - firebase_auth_platform_interface: + ffi: dependency: transitive description: - name: firebase_auth_platform_interface - sha256: "421f95dc553cb283ed9d4d140e719800c0331d49ed37b962e513c9d1d61b090b" + name: ffi + sha256: d07d37192dbf97461359c1518788f203b0c9102cfd2c35a716b823741219542c url: "https://pub.dev" source: hosted - version: "8.1.4" - firebase_auth_web: + version: "2.1.5" + file: dependency: transitive description: - name: firebase_auth_web - sha256: a064ffee202f7d42d62e2c01775899d4ffcb83c602af07632f206acd46a0964e + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 url: "https://pub.dev" source: hosted - version: "6.1.0" - firebase_core: - dependency: "direct main" - description: - name: firebase_core - sha256: "29cfa93c771d8105484acac340b5ea0835be371672c91405a300303986f4eba9" - url: "https://pub.dev" - source: hosted - version: "4.3.0" - firebase_core_platform_interface: + version: "7.0.1" + fixnum: dependency: transitive description: - name: firebase_core_platform_interface - sha256: cccb4f572325dc14904c02fcc7db6323ad62ba02536833dddb5c02cac7341c64 + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be url: "https://pub.dev" source: hosted - version: "6.0.2" - firebase_core_web: - dependency: transitive - description: - name: firebase_core_web - sha256: a631bbfbfa26963d68046aed949df80b228964020e9155b086eff94f462bbf1f - url: "https://pub.dev" - source: hosted - version: "3.3.1" + version: "1.1.1" flutter: dependency: "direct main" description: flutter @@ -160,6 +176,30 @@ packages: description: flutter source: sdk version: "0.0.0" + functions_client: + dependency: transitive + description: + name: functions_client + sha256: "94074d62167ae634127ef6095f536835063a7dc80f2b1aa306d2346ff9023996" + url: "https://pub.dev" + source: hosted + version: "2.5.0" + gotrue: + dependency: transitive + description: + name: gotrue + sha256: f7b52008311941a7c3e99f9590c4ee32dfc102a5442e43abf1b287d9f8cc39b2 + url: "https://pub.dev" + source: hosted + version: "2.18.0" + gtk: + dependency: transitive + description: + name: gtk + sha256: e8ce9ca4b1df106e4d72dad201d345ea1a036cc12c360f1a7d5a758f78ffa42c + url: "https://pub.dev" + source: hosted + version: "2.1.0" http: dependency: transitive description: @@ -176,6 +216,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.2" + jwt_decode: + dependency: transitive + description: + name: jwt_decode + sha256: d2e9f68c052b2225130977429d30f187aa1981d789c76ad104a32243cfdebfbb + url: "https://pub.dev" + source: hosted + version: "0.3.1" leak_tracker: dependency: transitive description: @@ -208,6 +256,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.1.1" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" matcher: dependency: transitive description: @@ -232,6 +288,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.17.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" nested: dependency: transitive description: @@ -248,6 +312,62 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: f2c65e21139ce2c3dad46922be8272bb5963516045659e71bb16e151c93b580e + url: "https://pub.dev" + source: hosted + version: "2.2.22" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "6d13aece7b3f5c5a9731eaf553ff9dcbc2eff41087fd2df587fd0fed9a3eb0c4" + url: "https://pub.dev" + source: hosted + version: "2.5.1" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" plugin_platform_interface: dependency: transitive description: @@ -256,6 +376,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.8" + pointycastle: + dependency: transitive + description: + name: pointycastle + sha256: "92aa3841d083cc4b0f4709b5c74fd6409a3e6ba833ffc7dc6a8fee096366acf5" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + postgrest: + dependency: transitive + description: + name: postgrest + sha256: f4b6bb24b465c47649243ef0140475de8a0ec311dc9c75ebe573b2dcabb10460 + url: "https://pub.dev" + source: hosted + version: "2.6.0" provider: dependency: "direct main" description: @@ -264,6 +400,86 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.5+1" + realtime_client: + dependency: transitive + description: + name: realtime_client + sha256: "5268afc208d02fb9109854d262c1ebf6ece224cd285199ae1d2f92d2ff49dbf1" + url: "https://pub.dev" + source: hosted + version: "2.7.0" + retry: + dependency: transitive + description: + name: retry + sha256: "822e118d5b3aafed083109c72d5f484c6dc66707885e07c0fbcb8b986bba7efc" + url: "https://pub.dev" + source: hosted + version: "3.1.2" + rxdart: + dependency: transitive + description: + name: rxdart + sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" + url: "https://pub.dev" + source: hosted + version: "0.28.0" + shared_preferences: + dependency: transitive + description: + name: shared_preferences + sha256: "2939ae520c9024cb197fc20dee269cd8cdbf564c8b5746374ec6cacdc5169e64" + url: "https://pub.dev" + source: hosted + version: "2.5.4" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "83af5c682796c0f7719c2bbf74792d113e40ae97981b8f266fa84574573556bc" + url: "https://pub.dev" + source: hosted + version: "2.4.18" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f" + url: "https://pub.dev" + source: hosted + version: "2.5.6" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 + url: "https://pub.dev" + source: hosted + version: "2.4.3" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" sky_engine: dependency: transitive description: flutter @@ -285,6 +501,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.12.1" + storage_client: + dependency: transitive + description: + name: storage_client + sha256: "1c61b19ed9e78f37fdd1ca8b729ab8484e6c8fe82e15c87e070b861951183657" + url: "https://pub.dev" + source: hosted + version: "2.4.1" stream_channel: dependency: transitive description: @@ -301,6 +525,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.1" + supabase: + dependency: transitive + description: + name: supabase + sha256: cc039f63a3168386b3a4f338f3bff342c860d415a3578f3fbe854024aee6f911 + url: "https://pub.dev" + source: hosted + version: "2.10.2" + supabase_flutter: + dependency: "direct main" + description: + name: supabase_flutter + sha256: "92b2416ecb6a5c3ed34cf6e382b35ce6cc8921b64f2a9299d5d28968d42b09bb" + url: "https://pub.dev" + source: hosted + version: "2.12.0" term_glyph: dependency: transitive description: @@ -325,6 +565,70 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + url_launcher: + dependency: transitive + description: + name: url_launcher + sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 + url: "https://pub.dev" + source: hosted + version: "6.3.2" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "767344bf3063897b5cf0db830e94f904528e6dd50a6dfaf839f0abf509009611" + url: "https://pub.dev" + source: hosted + version: "6.3.28" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: cfde38aa257dae62ffe79c87fab20165dfdf6988c1d31b58ebf59b9106062aad + url: "https://pub.dev" + source: hosted + version: "6.3.6" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a + url: "https://pub.dev" + source: hosted + version: "3.2.2" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18" + url: "https://pub.dev" + source: hosted + version: "3.2.5" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: d0412fcf4c6b31ecfdb7762359b7206ffba3bbffd396c6d9f9c4616ece476c1f + url: "https://pub.dev" + source: hosted + version: "2.4.2" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f" + url: "https://pub.dev" + source: hosted + version: "3.1.5" vector_math: dependency: transitive description: @@ -349,6 +653,38 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 + url: "https://pub.dev" + source: hosted + version: "3.0.3" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + yet_another_json_isolate: + dependency: transitive + description: + name: yet_another_json_isolate + sha256: fe45897501fa156ccefbfb9359c9462ce5dec092f05e8a56109db30be864f01e + url: "https://pub.dev" + source: hosted + version: "2.1.0" sdks: - dart: ">=3.9.2 <4.0.0" - flutter: ">=3.22.0" + dart: ">=3.10.0 <4.0.0" + flutter: ">=3.38.0" diff --git a/pubspec.yaml b/pubspec.yaml index 8cdaf4d..a7e3885 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -35,9 +35,7 @@ dependencies: # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.8 provider: ^6.1.5+1 - firebase_core: ^4.2.1 - firebase_auth: ^6.1.2 - cloud_firestore: ^6.1.1 + supabase_flutter: ^2.12.0 dev_dependencies: flutter_test: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index bf6d21a..785a046 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,15 +6,12 @@ #include "generated_plugin_registrant.h" -#include -#include -#include +#include +#include void RegisterPlugins(flutter::PluginRegistry* registry) { - CloudFirestorePluginCApiRegisterWithRegistrar( - registry->GetRegistrarForPlugin("CloudFirestorePluginCApi")); - FirebaseAuthPluginCApiRegisterWithRegistrar( - registry->GetRegistrarForPlugin("FirebaseAuthPluginCApi")); - FirebaseCorePluginCApiRegisterWithRegistrar( - registry->GetRegistrarForPlugin("FirebaseCorePluginCApi")); + AppLinksPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("AppLinksPluginCApi")); + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index b83b40a..8f8ee4f 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,9 +3,8 @@ # list(APPEND FLUTTER_PLUGIN_LIST - cloud_firestore - firebase_auth - firebase_core + app_links + url_launcher_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST