import 'dart:async'; import 'dart:math' as math; import 'package:flutter/material.dart'; import 'package:lottie/lottie.dart'; import '../main.dart' show supabase; import '../privacy_gate_prefs.dart'; import '../terms_gate_prefs.dart'; import '../widgets/entrance.dart'; import '../widgets/tap_bounce.dart'; import '../widgets/privacy_content.dart'; /// Ecrã de bloqueio mostrado logo após o cadastro (primeira vez), antes do /// [TermsGateScreen] e antes de entrar na app. Só avança para o próximo /// passo se o utilizador consentir os três itens de privacidade; se /// recusar (botão ou seta de voltar), a conta acabada de criar é removida /// (perfil apagado + sessão terminada) e volta-se ao login. class PrivacyGateScreen extends StatefulWidget { const PrivacyGateScreen({ super.key, required this.userId, required this.onAccepted, }); final String userId; final VoidCallback onAccepted; @override State createState() => _PrivacyGateScreenState(); } class _PrivacyGateScreenState extends State { final Set _accepted = {}; bool _declining = false; bool get _allAccepted => _accepted.length == kPrivacyConsentItems.length; void _toggle(String id, bool value) { setState(() { if (value) { _accepted.add(id); } else { _accepted.remove(id); } }); } void _acceptAll() { setState(() { _accepted.addAll(kPrivacyConsentItems.map((e) => e.id)); }); } Future _decline() async { if (_declining) return; setState(() => _declining = true); try { await supabase .from('profiles') .delete() .eq('id', widget.userId) .timeout(const Duration(seconds: 20)); } catch (_) { // Mesmo que a limpeza do perfil falhe, termina a sessão de qualquer // forma — o AuthGate trata contas sem perfil como inexistentes. } await PrivacyGatePrefs.clearPendingUid(); // A conta vai ser apagada — não faz sentido deixar o utilizador cair // no ecrã de Termos logo a seguir com uma sessão já sem perfil. await TermsGatePrefs.clearPendingUid(); await supabase.auth.signOut(); // Não faz setState depois disto: o widget é removido da árvore assim // que o AuthGate reage à sessão terminada. } @override Widget build(BuildContext context) { final size = MediaQuery.sizeOf(context); return PopScope( canPop: false, onPopInvokedWithResult: (didPop, _) { if (!didPop) _decline(); }, child: Scaffold( body: Stack( clipBehavior: Clip.none, children: [ Positioned.fill( child: Container(color: const Color(0xFFFAFAF7)), ), Positioned( left: -size.width * 0.38, bottom: -size.width * 0.38, child: IgnorePointer( child: SizedBox( width: size.width * 1.05, height: size.width * 1.05, child: Transform.rotate( angle: 35 * math.pi / 180, child: Opacity( opacity: 0.95, child: Lottie.asset( 'lottie/Liquid waves.json', fit: BoxFit.cover, repeat: true, ), ), ), ), ), ), SafeArea( child: Column( children: [ Align( alignment: Alignment.topLeft, child: IconButton( onPressed: _declining ? null : _decline, icon: const Icon(Icons.close_rounded), color: Colors.black54, ), ), // Só o cabeçalho e a lista de itens rolam — os botões // ficam fixos, fora do SingleChildScrollView, tal como // no TermsGateScreen. Expanded( child: SingleChildScrollView( padding: const EdgeInsets.fromLTRB(24, 4, 24, 4), child: Column( children: [ const FadeSlideIn(child: PrivacyHeader()), const SizedBox(height: 22), FadeSlideIn( delay: const Duration(milliseconds: 80), child: _PrivacyConsentList( accepted: _accepted, onChanged: _declining ? null : _toggle, ), ), ], ), ), ), Padding( padding: const EdgeInsets.fromLTRB(24, 12, 24, 20), child: Column( children: [ TapBounce( child: TextButton( onPressed: _declining ? null : _acceptAll, child: const Text( 'Aceitar tudo', style: TextStyle( fontWeight: FontWeight.w800, fontSize: 15, color: kPrivacyTeal, ), ), ), ), const SizedBox(height: 8), _AdvanceButton( enabled: _allAccepted && !_declining, onPressed: widget.onAccepted, ), ], ), ), ], ), ), if (_declining) Positioned.fill( child: ColoredBox( color: Colors.black.withValues(alpha: 0.12), child: const Center( child: CircularProgressIndicator(color: kPrivacyTeal), ), ), ), ], ), ), ); } } class _PrivacyConsentList extends StatelessWidget { const _PrivacyConsentList({required this.accepted, required this.onChanged}); final Set accepted; final void Function(String id, bool value)? onChanged; @override Widget build(BuildContext context) { return Column( children: [ for (var i = 0; i < kPrivacyConsentItems.length; i++) ...[ if (i > 0) const SizedBox(height: 14), _PrivacyConsentRow( item: kPrivacyConsentItems[i], checked: accepted.contains(kPrivacyConsentItems[i].id), onChanged: onChanged == null ? null : (v) => onChanged!(kPrivacyConsentItems[i].id, v), ), ], ], ); } } class _PrivacyConsentRow extends StatelessWidget { const _PrivacyConsentRow({ required this.item, required this.checked, required this.onChanged, }); final PrivacyConsentItem item; final bool checked; final ValueChanged? onChanged; @override Widget build(BuildContext context) { return TapBounce( scale: 0.98, child: InkWell( borderRadius: BorderRadius.circular(14), onTap: onChanged == null ? null : () => onChanged!(!checked), child: Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.9), borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.05), blurRadius: 14, offset: const Offset(0, 6), ), ], ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox( width: 24, height: 24, // IgnorePointer: o Checkbox tem um tap target mínimo de // 48x48 que competia na arena de gestos com o InkWell da // linha toda — o mesmo problema já resolvido no // TermsGateScreen. Aqui serve só para o visual; quem trata // o toque é sempre o InkWell à volta de toda a linha. child: IgnorePointer( child: Checkbox( value: checked, onChanged: onChanged == null ? null : (_) {}, activeColor: kPrivacyTeal, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(6), ), ), ), ), const SizedBox(width: 12), Expanded( child: Text( item.text, style: TextStyle( fontSize: 13, fontWeight: FontWeight.w700, height: 1.45, color: Colors.black.withValues(alpha: 0.8), ), ), ), ], ), ), ), ); } } class _AdvanceButton extends StatelessWidget { const _AdvanceButton({required this.enabled, required this.onPressed}); final bool enabled; final VoidCallback onPressed; @override Widget build(BuildContext context) { return TapBounce( child: SizedBox( width: double.infinity, height: 52, child: FilledButton( style: FilledButton.styleFrom( backgroundColor: kPrivacyTeal, disabledBackgroundColor: kPrivacyTeal.withValues(alpha: 0.35), foregroundColor: Colors.white, shape: const StadiumBorder(), textStyle: const TextStyle( fontWeight: FontWeight.w800, fontSize: 16, ), ), onPressed: enabled ? onPressed : null, child: const Text('Avançar'), ), ), ); } }