import 'package:firebase_auth/firebase_auth.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:flutter/material.dart'; import 'package:lottie/lottie.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'dart:async'; import 'dart:math' as math; Future showRegisterSheet(BuildContext context) { return showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), builder: (ctx) => const _AnimatedAuthSheet(child: RegisterBottomSheet()), ); } class _AnimatedAuthSheet extends StatelessWidget { const _AnimatedAuthSheet({required this.child}); final Widget child; @override Widget build(BuildContext context) { final size = MediaQuery.sizeOf(context); const topRadius = Radius.circular(20); return TweenAnimationBuilder( tween: Tween(begin: 0.0, end: 1.0), duration: const Duration(milliseconds: 260), curve: Curves.easeOutCubic, builder: (context, t, w) { return Opacity( opacity: t, child: Transform.translate( offset: Offset(0, (1 - t) * 12), child: w, ), ); }, child: ClipRRect( borderRadius: const BorderRadius.vertical(top: topRadius), child: Material( color: Colors.transparent, child: Stack( clipBehavior: Clip.none, children: [ Positioned.fill( child: Container( decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Color(0xFFFFE6F1), Color(0xFFFFC9DF), ], ), ), ), ), Positioned( left: -size.width * 0.38, bottom: -size.width * 0.45, child: IgnorePointer( child: SizedBox( width: size.width * 1.05, height: size.width * 1.05, child: Transform.rotate( angle: 28 * math.pi / 180, child: Opacity( opacity: 0.95, child: Lottie.asset( 'lottie/Liquid waves.json', fit: BoxFit.cover, repeat: true, ), ), ), ), ), ), child, ], ), ), ), ); } } class RegisterBottomSheet extends StatefulWidget { const RegisterBottomSheet({super.key}); @override State createState() => _RegisterBottomSheetState(); } class _RegisterBottomSheetState extends State { final _formKey = GlobalKey(); final _nameController = TextEditingController(); final _emailController = TextEditingController(); final _passwordController = TextEditingController(); final _childNameController = TextEditingController(); final _childAgeController = TextEditingController(); String? _childGender; bool _loading = false; static const String _kPendingQuizScopeKey = 'pending_quiz_scope_v1'; Future _persistRegistrationData({ required String uid, required String name, required String email, required String childId, required String childName, required int childAge, required String childGender, }) async { await Future.wait([ FirebaseFirestore.instance.collection('users').doc(uid).set({ 'name': name, 'email': email, 'createdAt': FieldValue.serverTimestamp(), }, SetOptions(merge: true)).timeout(const Duration(seconds: 20)), FirebaseFirestore.instance .collection('users') .doc(uid) .collection('children') .doc(childId) .set({ 'id': childId, 'name': childName, 'age': childAge, 'gender': childGender, 'createdAt': FieldValue.serverTimestamp(), }, SetOptions(merge: true)).timeout(const Duration(seconds: 20)), ]); } @override void dispose() { _nameController.dispose(); _emailController.dispose(); _passwordController.dispose(); _childNameController.dispose(); _childAgeController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final viewInsets = MediaQuery.viewInsetsOf(context); const accentPink = Color(0xFFFF55A7); const primaryTeal = Color(0xFF2F9E94); final underlineBorder = UnderlineInputBorder( borderSide: BorderSide(color: Colors.black.withValues(alpha: 0.20)), ); return Padding( padding: EdgeInsets.only( left: 16, right: 16, top: 12, bottom: 16 + viewInsets.bottom, ), child: ConstrainedBox( constraints: const BoxConstraints(maxHeight: 560), child: SingleChildScrollView( child: Form( key: _formKey, child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Center( child: Container( width: 46, height: 5, decoration: BoxDecoration( color: Colors.black.withValues(alpha: 0.10), borderRadius: BorderRadius.circular(99), ), ), ), const SizedBox(height: 14), const Text( 'Criar conta', textAlign: TextAlign.center, style: TextStyle( fontSize: 18, fontWeight: FontWeight.w800, color: accentPink, ), ), const SizedBox(height: 16), TextFormField( controller: _nameController, textInputAction: TextInputAction.next, decoration: InputDecoration( labelText: 'Nome', border: underlineBorder, enabledBorder: underlineBorder, focusedBorder: underlineBorder.copyWith( borderSide: const BorderSide(color: primaryTeal, width: 1.6), ), floatingLabelStyle: const TextStyle(color: primaryTeal, fontWeight: FontWeight.w700), ), validator: (v) { if (v == null || v.trim().isEmpty) return 'Informe seu nome'; if (v.trim().length < 2) return 'Nome muito curto'; return null; }, ), const SizedBox(height: 12), TextFormField( controller: _emailController, keyboardType: TextInputType.emailAddress, textInputAction: TextInputAction.next, decoration: InputDecoration( labelText: 'Email', border: underlineBorder, enabledBorder: underlineBorder, focusedBorder: underlineBorder.copyWith( borderSide: const BorderSide(color: primaryTeal, width: 1.6), ), floatingLabelStyle: const TextStyle(color: primaryTeal, fontWeight: FontWeight.w700), ), validator: (v) { final value = (v ?? '').trim(); if (value.isEmpty) return 'Informe seu email'; if (!value.contains('@')) return 'Email inválido'; return null; }, ), const SizedBox(height: 12), TextFormField( controller: _passwordController, obscureText: true, textInputAction: TextInputAction.done, decoration: InputDecoration( labelText: 'Senha', border: underlineBorder, enabledBorder: underlineBorder, focusedBorder: underlineBorder.copyWith( borderSide: const BorderSide(color: primaryTeal, width: 1.6), ), floatingLabelStyle: const TextStyle(color: primaryTeal, fontWeight: FontWeight.w700), ), validator: (v) { final value = (v ?? ''); if (value.isEmpty) return 'Informe sua senha'; if (value.length < 6) return 'Mínimo de 6 caracteres'; return null; }, ), const SizedBox(height: 18), TextFormField( controller: _childNameController, textInputAction: TextInputAction.next, decoration: InputDecoration( labelText: 'Nome do filho(a)', border: underlineBorder, enabledBorder: underlineBorder, focusedBorder: underlineBorder.copyWith( borderSide: const BorderSide(color: primaryTeal, width: 1.6), ), floatingLabelStyle: const TextStyle(color: primaryTeal, fontWeight: FontWeight.w700), ), validator: (v) { final value = (v ?? '').trim(); if (value.isEmpty) return 'Informe o nome do filho(a)'; if (value.length < 2) return 'Nome muito curto'; return null; }, ), const SizedBox(height: 12), TextFormField( controller: _childAgeController, keyboardType: TextInputType.number, textInputAction: TextInputAction.next, decoration: InputDecoration( labelText: 'Idade do filho(a)', border: underlineBorder, enabledBorder: underlineBorder, focusedBorder: underlineBorder.copyWith( borderSide: const BorderSide(color: primaryTeal, width: 1.6), ), floatingLabelStyle: const TextStyle(color: primaryTeal, fontWeight: FontWeight.w700), ), validator: (v) { final raw = (v ?? '').trim(); if (raw.isEmpty) return 'Informe a idade do filho(a)'; final age = int.tryParse(raw); if (age == null) return 'Idade inválida'; if (age < 0 || age > 25) return 'Idade inválida'; return null; }, ), const SizedBox(height: 12), DropdownButtonFormField( initialValue: _childGender, items: const [ DropdownMenuItem(value: 'Masculino', child: Text('Masculino')), DropdownMenuItem(value: 'Feminino', child: Text('Feminino')), DropdownMenuItem(value: 'Outro', child: Text('Outro')), ], onChanged: (v) => setState(() => _childGender = v), decoration: InputDecoration( labelText: 'Gênero do filho(a)', border: underlineBorder, enabledBorder: underlineBorder, focusedBorder: underlineBorder.copyWith( borderSide: const BorderSide(color: primaryTeal, width: 1.6), ), floatingLabelStyle: const TextStyle(color: primaryTeal, fontWeight: FontWeight.w700), ), validator: (v) { if (v == null || v.trim().isEmpty) return 'Selecione o gênero'; return null; }, ), const SizedBox(height: 16), SizedBox( height: 46, child: FilledButton( style: FilledButton.styleFrom( backgroundColor: primaryTeal, foregroundColor: Colors.white, shape: const StadiumBorder(), textStyle: const TextStyle(fontWeight: FontWeight.w800), ), onPressed: _loading ? null : _submit, child: _loading ? const SizedBox( width: 18, height: 18, child: CircularProgressIndicator(strokeWidth: 2), ) : const Text('Registrar'), ), ), const SizedBox(height: 8), TextButton( onPressed: _loading ? null : () => Navigator.of(context).pop(), child: const Text('Fechar'), ), ], ), ), ), ), ); } Future _submit() async { if (!(_formKey.currentState?.validate() ?? false)) return; setState(() => _loading = true); try { final name = _nameController.text.trim(); final email = _emailController.text.trim(); final password = _passwordController.text; final childName = _childNameController.text.trim(); final childAge = int.parse(_childAgeController.text.trim()); final childGender = (_childGender ?? '').trim(); final credential = await FirebaseAuth.instance .createUserWithEmailAndPassword( email: email, password: password, ) .timeout(const Duration(seconds: 20)); final user = credential.user; if (user == null) { throw StateError('Usuário não encontrado após criar conta.'); } final uid = user.uid; // Gera o childId antes de fechar o sheet para termos um scopeId determinístico. final childId = FirebaseFirestore.instance .collection('users') .doc(uid) .collection('children') .doc() .id; final scopeId = '${uid}_$childId'; // Marca para o LoggedHome abrir automaticamente o quiz desta criança. final prefs = await SharedPreferences.getInstance(); await prefs.setString(_kPendingQuizScopeKey, scopeId); if (!mounted) return; // Fecha o sheet imediatamente após autenticar. // As gravações no Firestore seguem em background para não travar a UI. Navigator.of(context).pop(); unawaited( _persistRegistrationData( uid: uid, name: name, email: email, childId: childId, childName: childName, childAge: childAge, childGender: childGender, ).catchError((_) {}), ); } on FirebaseAuthException catch (e) { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(_friendlyAuthError(e))), ); } on TimeoutException { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Tempo esgotado. Verifique sua conexão e tente novamente.')), ); } catch (e) { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Erro: $e')), ); } finally { if (mounted && _loading) setState(() => _loading = false); } } String _friendlyAuthError(FirebaseAuthException e) { switch (e.code) { case 'invalid-email': return 'Email inválido.'; case 'email-already-in-use': return 'Este email já está em uso.'; case 'weak-password': return 'Senha fraca. Use pelo menos 6 caracteres.'; default: return e.message ?? 'Falha de autenticação.'; } } }