kz_educa/lib/screens/auth_screen.dart

812 lines
28 KiB
Dart
Executable File

import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:provider/provider.dart';
import 'package:animate_do/animate_do.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; // <--- 🌟 NOVO: Importação para ícones sociais
import 'dart:ui';
import 'package:kzeduca_app/services/i18n_service.dart';
import 'package:kzeduca_app/services/firebase_service.dart';
import 'package:kzeduca_app/screens/dashboard_screen.dart';
import 'package:kzeduca_app/services/user_state_service.dart';
// Constantes de Cores Inspiradas no Dashboard
const Color kAppBackground = Color(0xFF1F1237); // Roxo bem escuro, quase preto
const Color kAppCardSurface = Color(0xFF2C1E4F); // Roxo escuro para a superfície do card
const Color kAppAccentPurple = Color(0xFF6A1B9A); // Roxo vibrante para acentos
const Color kAppAccentGold = Color(0xFFFFD700); // Dourado forte (como no dashboard)
const Color kAppAccentLightPurple = Color(0xFF8E24AA); // Roxo mais claro para contraste
const Color kAppTextColor = Color(0xFFE0E0E0); // Cinza claro para texto
const Color kAppHintColor = Color(0xFFB0A8C0); // Roxo acinzentado para placeholders
class AuthScreen extends StatefulWidget {
const AuthScreen({super.key});
@override
State<AuthScreen> createState() => _AuthScreenState();
}
class _AuthScreenState extends State<AuthScreen> with SingleTickerProviderStateMixin {
final TextEditingController _emailController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
final TextEditingController _usernameController = TextEditingController();
late final AnimationController _animationController;
bool _isLogin = true;
bool _isResettingPassword = false;
String _error = '';
bool _isLoading = false;
bool _isPasswordVisible = false;
@override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
duration: const Duration(seconds: 4),
)..repeat(reverse: true);
}
@override
void dispose() {
_emailController.dispose();
_passwordController.dispose();
_usernameController.dispose();
_animationController.dispose();
super.dispose();
}
void _showErrorDialog(String message) {
final i18n = Provider.of<I18nService>(context, listen: false);
if (mounted) {
showDialog(
context: context,
builder: (ctx) => AlertDialog(
title: Text(i18n.t('error_prefix'), style: TextStyle(color: kAppTextColor)),
content: Text(message, style: TextStyle(color: kAppTextColor)),
backgroundColor: kAppCardSurface,
actions: <Widget>[
TextButton(
child: Text('Ok', style: TextStyle(color: kAppAccentPurple)),
onPressed: () {
Navigator.of(ctx).pop();
},
)
],
),
);
}
}
// NOVO MÉTODO: Autenticação com Google
Future<void> _authenticateWithGoogle() async {
final i18n = Provider.of<I18nService>(context, listen: false);
final firebaseService = Provider.of<FirebaseService>(context, listen: false);
if (!mounted) return;
setState(() {
_error = '';
_isLoading = true;
});
try {
await firebaseService.signInWithGoogle();
await Provider.of<UserStateService>(context, listen: false).loadUser();
if (mounted) {
setState(() {
_isLoading = false;
});
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(builder: (context) => const DashboardScreen()),
(Route<dynamic> route) => false,
);
}
} on FirebaseAuthException catch (e) {
String errorMessage = i18n.t('auth_error');
if (e.code == 'google-login-cancelled') {
errorMessage = i18n.t('google_login_cancelled'); // Assumindo que você tem essa chave i18n
} else {
errorMessage = i18n.t('google_auth_failed'); // Assumindo que você tem essa chave i18n
}
_showErrorDialog(errorMessage);
if(mounted) {
setState(() {
_isLoading = false;
});
}
} catch (e) {
_showErrorDialog(i18n.t('auth_error'));
if(mounted) {
setState(() {
_isLoading = false;
});
}
}
}
// ============== FUNÇÃO DE REINICIALIZAÇÃO DA PALAVRA-PASSE ==============
Future<void> _sendPasswordResetEmail() async {
final i18n = Provider.of<I18nService>(context, listen: false);
final email = _emailController.text.trim();
if (email.isEmpty) {
_showErrorDialog(i18n.t('email_required_for_reset'));
return;
}
if (!mounted) return;
setState(() {
_isLoading = true;
});
try {
await FirebaseAuth.instance.sendPasswordResetEmail(email: email);
if (mounted) {
showDialog(
context: context,
builder: (ctx) => AlertDialog(
title: Text(i18n.t('password_reset_title'), style: TextStyle(color: kAppTextColor)),
content: Text(i18n.t('password_reset_sent').replaceAll('{email}', email), style: TextStyle(color: kAppTextColor)),
backgroundColor: kAppCardSurface,
actions: <Widget>[
TextButton(
child: Text('Ok', style: TextStyle(color: kAppAccentPurple)),
onPressed: () {
Navigator.of(ctx).pop();
setState(() {
_isResettingPassword = false;
_emailController.clear();
});
},
)
],
),
);
setState(() {
_isLoading = false;
});
}
} on FirebaseAuthException catch (e) {
String errorMessage = i18n.t('reset_password_error');
if (e.code == 'user-not-found' || e.code == 'invalid-email') {
errorMessage = i18n.t('email_invalid_or_not_found');
}
_showErrorDialog(errorMessage);
if(mounted) {
setState(() {
_isLoading = false;
});
}
} catch (e) {
_showErrorDialog(i18n.t('auth_error'));
if(mounted) {
setState(() {
_isLoading = false;
});
}
}
}
// ======================================================================
Future<void> _authenticate() async {
final i18n = Provider.of<I18nService>(context, listen: false);
final firebaseService = Provider.of<FirebaseService>(context, listen: false);
// ... (Lógica inalterada) ...
if (!mounted) return;
setState(() {
_error = '';
_isLoading = true;
});
try {
if (_isLogin) {
await firebaseService.signInWithEmailAndPassword(
_emailController.text,
_passwordController.text,
);
} else {
if (_usernameController.text.trim().isEmpty) {
_showErrorDialog(i18n.t('username_required'));
if (mounted) {
setState(() { _isLoading = false; });
}
return;
}
await firebaseService.registerWithEmailAndPassword(
_emailController.text,
_passwordController.text,
_usernameController.text.trim(),
);
}
await Provider.of<UserStateService>(context, listen: false).loadUser();
if (mounted) {
setState(() {
_isLoading = false;
});
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(builder: (context) => const DashboardScreen()),
(Route<dynamic> route) => false,
);
}
} on FirebaseAuthException catch (e) {
String errorMessage = i18n.t('auth_error');
if (e.code == 'weak-password') {
errorMessage = i18n.t('password_weak');
} else if (e.code == 'email-already-in-use') {
errorMessage = i18n.t('email_already_in_use');
} else if (e.code == 'invalid-email') {
errorMessage = i18n.t('email_invalid');
} else if (e.code == 'user-not-found' || e.code == 'wrong-password') {
errorMessage = i18n.t('login_error');
} else if (e.code == 'operation-not-allowed') {
errorMessage = i18n.t('auth_operation_not_allowed_email_password');
}
_showErrorDialog(errorMessage);
if(mounted) {
setState(() {
_isLoading = false;
});
}
} catch (e) {
_showErrorDialog(i18n.t('auth_error'));
if(mounted) {
setState(() {
_isLoading = false;
});
}
}
}
// ============== WIDGET: Interface de Redefinição de Senha ==============
Widget _buildResetPasswordUI(I18nService i18n) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
FadeInDown(
duration: const Duration(milliseconds: 800),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
i18n.t('reset_password_title'),
style: const TextStyle(
fontFamily: 'Poppins',
fontSize: 24,
fontWeight: FontWeight.w700,
color: kAppTextColor,
letterSpacing: 0.8,
shadows: [
Shadow(color: Colors.black45, blurRadius: 4, offset: Offset(2, 2)),
]
),
),
const SizedBox(height: 8),
Text(
i18n.t('reset_password_instructions'),
style: TextStyle(
fontFamily: 'Poppins',
fontSize: 14,
color: kAppTextColor.withOpacity(0.8),
letterSpacing: 0.4,
),
textAlign: TextAlign.center,
),
],
),
),
const SizedBox(height: 24),
_buildTextField(
controller: _emailController,
hintText: i18n.t('email_placeholder'),
icon: Icons.email,
keyboardType: TextInputType.emailAddress,
),
const SizedBox(height: 12),
_buildSignInButton(
i18n.t('send_reset_link_button'),
onPressed: _sendPasswordResetEmail,
backgroundColor: kAppAccentPurple, // Botão Dourado
),
const SizedBox(height: 8),
TextButton(
onPressed: () {
if (!mounted) return;
setState(() {
_isResettingPassword = false;
_emailController.clear();
});
},
child: Text(
i18n.t('back_to_login'),
style: TextStyle(
color: kAppTextColor.withOpacity(0.7),
fontSize: 12,
fontWeight: FontWeight.bold,
letterSpacing: 0.3,
),
),
),
],
);
}
// ======================================================================
@override
Widget build(BuildContext context) {
final i18n = Provider.of<I18nService>(context);
// Aumentamos a altura para acomodar o botão do Google e o separador
double cardHeight = _isResettingPassword ? 430 : (_isLogin ? 540 : 600);
return Scaffold(
backgroundColor: kAppCardSurface,
body: Stack(
children: [
// 1. Fundo com gradiente sutil (roxo escuro para mais claro)
Positioned.fill(
child: AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
return Container(
decoration: BoxDecoration(
gradient: RadialGradient(
center: Alignment(_animationController.value * 2 - 1, _animationController.value * 2 - 1),
radius: 1.0,
colors: [
kAppAccentPurple.withOpacity(0.1), // Roxo sutil
kAppBackground,
kAppBackground,
],
stops: const [0.0, 0.4, 1.0],
),
),
);
},
),
),
// 2. Conteúdo Central
Center(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
FadeInUp(
duration: const Duration(milliseconds: 800),
child: Stack(
alignment: Alignment.center,
children: [
// 2a. Container Externo (Borda Dourada Metálica com gradiente roxo sutil)
AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
return Container(
width: 330,
height: cardHeight + 4,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: const [
Color(0xFF27a2ff),
Color(0xFFff2770), // Roxo no gradiente
Color(0xFF27a2ff),
],
stops: [
_animationController.value - 0.5,
_animationController.value,
_animationController.value + 0.5,
],
),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.4),
blurRadius: 10,
offset: const Offset(0, 0),
),
],
),
);
},
),
// 2b. Container Interno (Superfície Roxo Escuro)
Container(
width: 326,
height: cardHeight,
padding: const EdgeInsets.fromLTRB(24, 60, 24, 24),
decoration: BoxDecoration(
color: kAppCardSurface,
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: Color(0xFF8A2BE2).withOpacity(0.3),
width: 0.5,
),
boxShadow: const [
BoxShadow(
color: Colors.black54,
blurRadius: 8,
offset: Offset(0, 4),
),
BoxShadow(
color: Colors.white10,
blurRadius: 2,
offset: Offset(0, -1),
),
]
),
child: _isResettingPassword
? _buildResetPasswordUI(i18n)
: _buildAuthUI(i18n),
),
// 2c. Seletor de Idioma
Positioned(
top: 24,
right: 10,
child: FadeInDown(
duration: const Duration(milliseconds: 800),
child: Row(
children: [
_buildLanguageButton('PT', 'pt', i18n),
const SizedBox(width: 8),
_buildLanguageButton('EN', 'en', i18n),
],
),
),
),
],
),
),
],
),
),
),
),
],
),
);
}
// WIDGET: Botão de Idioma Refatorado (Estilo Luxuoso com Cores do App)
Widget _buildLanguageButton(String text, String localeCode, I18nService i18n) {
bool isSelected = i18n.locale.languageCode == localeCode;
return InkWell(
onTap: () => i18n.setLocale(Locale(localeCode)),
borderRadius: BorderRadius.circular(20),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(
color: isSelected ? Color(0xFF8320E0) : Colors.transparent, // Dourado quando selecionado
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: isSelected ? kAppBackground : kAppTextColor.withOpacity(0.3),
width: 1.5,
),
boxShadow: isSelected ? [
BoxShadow(
color: kAppAccentPurple.withOpacity(0.5),
blurRadius: 8,
)
] : [
BoxShadow(
color: Colors.black.withOpacity(0.3),
blurRadius: 3,
offset: const Offset(1, 1),
)
],
),
child: Text(
text,
style: TextStyle(
color: isSelected ? kAppBackground : kAppTextColor.withOpacity(0.7), // Texto escuro no dourado
fontSize: 12,
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
letterSpacing: 0.5,
),
),
),
);
}
// WIDGET: UI de Login/Registro (Adição do Botão Google)
Widget _buildAuthUI(I18nService i18n) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
FadeInDown(
duration: const Duration(milliseconds: 800),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Text(
'KzEduca',
style: TextStyle(
fontFamily: 'Poppins',
fontSize: 32,
fontWeight: FontWeight.w700,
color: Color(0xFF8A2BE2), // Título em Dourado
letterSpacing: 1.5,
shadows: [
Shadow(color: Colors.black87, blurRadius: 8, offset: Offset(2, 2)),
Shadow(color: kAppAccentPurple, blurRadius: 4, offset: Offset(0, 0)),
]
),
),
const SizedBox(height: 4),
Text(
i18n.t('slogan'),
style: TextStyle(
fontFamily: 'Poppins',
fontSize: 14,
color: kAppTextColor.withOpacity(0.8),
letterSpacing: 0.4,
),
textAlign: TextAlign.center,
),
],
),
),
const SizedBox(height: 24),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: _buildTabButton(i18n.t('login'), true, _isLogin, Color(0xFF27a2ff), Color(0xFF8320E0)), // Botão de Login Dourado/Roxo
),
const SizedBox(width: 16),
Expanded(
child: _buildTabButton(i18n.t('register'), false, !_isLogin, Color(0xFF8320E0), Color(0xFF27a2ff)), // Botão de Registro Roxo/Dourado
),
],
),
const SizedBox(height: 24),
if (!_isLogin)
_buildTextField(
controller: _usernameController,
hintText: i18n.t('username_placeholder'),
icon: Icons.person,
),
_buildTextField(
controller: _emailController,
hintText: i18n.t('email_placeholder'),
icon: Icons.email,
keyboardType: TextInputType.emailAddress,
),
_buildTextField(
controller: _passwordController,
hintText: i18n.t('password_placeholder'),
icon: Icons.lock,
obscureText: !_isPasswordVisible,
isPasswordInput: true,
),
const SizedBox(height: 12),
// BOTÃO PRINCIPAL (E-MAIL/SENHA)
_buildSignInButton(
i18n.t(_isLogin ? 'enter' : 'register_button'),
onPressed: _authenticate,
backgroundColor: _isLogin ? Color(0xFF27a2ff) : Color(0xFF8320E0), // Botões de ação principais
),
const SizedBox(height: 12),
// Separador Sutil
Text(
i18n.t('or_login_with'), // Chave i18n para "ou entrar com"
style: TextStyle(color: kAppTextColor.withOpacity(0.6), fontSize: 12),
),
const SizedBox(height: 12),
// NOVO BOTÃO: GOOGLE SIGN-IN
_buildGoogleSignInButton(i18n),
if (_isLogin)
TextButton(
onPressed: _isLoading ? null : () {
setState(() {
_isResettingPassword = true;
_emailController.clear();
_passwordController.clear();
});
},
child: Text(
i18n.t('forgot_password'),
style: TextStyle(
color: kAppTextColor.withOpacity(0.7),
fontSize: 12,
fontWeight: FontWeight.bold,
letterSpacing: 0.3,
),
),
),
],
);
}
// WIDGET ATUALIZADO: Botão Google Dedicado com FaIcon (FontAwesome)
Widget _buildGoogleSignInButton(I18nService i18n) {
return SizedBox(
width: double.infinity,
child: ElevatedButton.icon(
onPressed: _isLoading ? null : _authenticateWithGoogle,
style: ElevatedButton.styleFrom(
backgroundColor: kAppCardSurface, // Fundo roxo escuro para contraste
foregroundColor: kAppTextColor,
padding: const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: BorderSide(color: Color(0xFF27a2ff).withOpacity(0.7), width: 1.5), // Borda Dourada para o toque luxuoso
),
elevation: 6,
shadowColor: Color(0xFF8A2BE2).withOpacity(0.4),
),
icon: const FaIcon( // <--- AGORA USA FaIcon
FontAwesomeIcons.google, // <--- Ícone do Google
color: Color(0xFF27a2ff),
size: 20,
),
label: Text(
i18n.t('sign_in_with_google'), // Chave i18n para "Entrar com Google"
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
letterSpacing: 0.8,
),
),
),
);
}
// WIDGETS INALTERADOS: _buildTabButton, _buildTextField, _buildSignInButton...
// WIDGET: Botão de Aba Refatorado (Estilo Luxuoso com Cores do App)
Widget _buildTabButton(String text, bool isLoginButton, bool isActive, Color activeColor, Color inactiveBorderColor) {
return ElevatedButton(
onPressed: () {
setState(() {
_isLogin = isLoginButton;
});
},
style: ElevatedButton.styleFrom(
backgroundColor: isActive ? activeColor : kAppCardSurface.withOpacity(0.8),
foregroundColor: isActive ? kAppBackground : kAppTextColor.withOpacity(0.7),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24),
side: BorderSide(color: isActive ? activeColor.withOpacity(0.6) : inactiveBorderColor.withOpacity(0.3), width: 1),
),
elevation: isActive ? 8 : 2,
shadowColor: isActive ? activeColor.withOpacity(0.5) : Colors.black.withOpacity(0.2),
padding: const EdgeInsets.symmetric(vertical: 12),
),
child: Text(
text,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
letterSpacing: 0.8,
)
),
);
}
// WIDGET: Campo de Texto Refatorado (Estilo Luxuoso com Cores do App)
Widget _buildTextField({
required TextEditingController controller,
required String hintText,
required IconData icon,
bool obscureText = false,
TextInputType keyboardType = TextInputType.text,
bool isPasswordInput = false,
}) {
return Padding(
padding: const EdgeInsets.only(bottom: 16.0),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.4),
blurRadius: 8,
offset: const Offset(0, 4),
),
BoxShadow(
color: Colors.white.withOpacity(0.05),
blurRadius: 2,
offset: const Offset(0, -1),
),
]
),
child: TextField(
controller: controller,
obscureText: obscureText,
keyboardType: keyboardType,
style: const TextStyle(color: kAppTextColor, fontWeight: FontWeight.normal),
decoration: InputDecoration(
hintText: hintText,
hintStyle: TextStyle(color: kAppHintColor),
prefixIcon: Icon(icon, color: Color(0xFF8A2BE2), size: 20),
suffixIcon: isPasswordInput
? IconButton(
icon: Icon(
_isPasswordVisible ? Icons.visibility : Icons.visibility_off,
color: kAppHintColor,
),
onPressed: () {
if (mounted) {
setState(() {
_isPasswordVisible = !_isPasswordVisible;
});
}
},
)
: null,
filled: true,
fillColor: kAppCardSurface,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: kAppAccentLightPurple.withOpacity(0.5), width: 0.5),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: Color(0xFF8A2BE2), width: 1.5),
),
contentPadding: const EdgeInsets.symmetric(vertical: 16, horizontal: 16),
),
),
),
);
}
// WIDGET: Botão de Ação Principal Refatorado (Estilo Luxuoso com Cores do App)
Widget _buildSignInButton(String buttonText, {required Color backgroundColor, required VoidCallback onPressed}) {
return SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _isLoading ? null : onPressed,
style: ElevatedButton.styleFrom(
backgroundColor: backgroundColor,
foregroundColor: kAppBackground,
padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
elevation: 10,
shadowColor: backgroundColor.withOpacity(0.6),
),
child: _isLoading
? const SizedBox(
height: 24,
width: 24,
child: CircularProgressIndicator(
color: kAppBackground,
strokeWidth: 2,
),
)
: Text(
buttonText,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w900,
letterSpacing: 1.0,
),
),
),
);
}
}