import 'dart:async'; import 'dart:math' as math; import 'package:flutter/material.dart'; import 'package:lottie/lottie.dart'; import '../main.dart' show supabase; import '../terms_gate_prefs.dart'; import '../widgets/entrance.dart'; import '../widgets/tap_bounce.dart'; import '../widgets/terms_content.dart'; /// Ecrã de bloqueio mostrado logo após o cadastro (primeira vez), antes de /// entrar na app. Só avança para a Home se o utilizador aceitar os Termos; /// 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 TermsGateScreen extends StatefulWidget { const TermsGateScreen({ super.key, required this.userId, required this.onAccepted, }); final String userId; final VoidCallback onAccepted; @override State createState() => _TermsGateScreenState(); } class _TermsGateScreenState extends State { bool _accepted = false; bool _declining = false; 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 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, ), ), // O checkbox e o botão ficam fixos, fora do // SingleChildScrollView — só o texto dos termos rola. Além // de ser o padrão comum em ecrãs de Termos (ação sempre // visível, sem precisar de chegar ao fundo do texto), evita // que a ação de aceitar dependa de o scroll estar // completamente parado. Expanded( child: SingleChildScrollView( padding: const EdgeInsets.fromLTRB(24, 4, 24, 4), child: Column( children: [ const FadeSlideIn(child: TermsHeader()), const SizedBox(height: 22), FadeSlideIn( delay: const Duration(milliseconds: 80), child: const TermsBody(), ), ], ), ), ), Padding( padding: const EdgeInsets.fromLTRB(24, 12, 24, 20), child: Column( children: [ _AcceptCheckboxRow( accepted: _accepted, onChanged: _declining ? null : (v) => setState(() => _accepted = v), ), const SizedBox(height: 18), _AdvanceButton( enabled: _accepted && !_declining, onPressed: widget.onAccepted, ), ], ), ), ], ), ), if (_declining) Positioned.fill( child: ColoredBox( color: Colors.black.withValues(alpha: 0.12), child: const Center( child: CircularProgressIndicator(color: kTermsPink), ), ), ), ], ), ), ); } } class _AcceptCheckboxRow extends StatelessWidget { const _AcceptCheckboxRow({required this.accepted, required this.onChanged}); final bool accepted; final ValueChanged? onChanged; @override Widget build(BuildContext context) { return TapBounce( scale: 0.98, child: InkWell( borderRadius: BorderRadius.circular(14), onTap: onChanged == null ? null : () => onChanged!(!accepted), child: Padding( padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 4), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ SizedBox( width: 24, height: 24, // IgnorePointer: o Checkbox tem um tap target mínimo de // 48x48 que, espremido neste SizedBox de 24x24 dentro do // InkWell da linha toda, competia na arena de gestos e // acabava por engolir o toque sem disparar nada. Aqui serve // só para o visual (marcado/desmarcado); quem trata o toque // é sempre o InkWell à volta de toda a linha. child: IgnorePointer( child: Checkbox( value: accepted, onChanged: onChanged == null ? null : (_) {}, activeColor: kTermsPink, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(6), ), ), ), ), const SizedBox(width: 10), const Text( 'Aceitar tudo', style: TextStyle( fontWeight: FontWeight.w800, fontSize: 15, color: kTermsPink, ), ), ], ), ), ), ); } } 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: kTermsPink, disabledBackgroundColor: kTermsPink.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'), ), ), ); } }