265 lines
7.8 KiB
Dart
265 lines
7.8 KiB
Dart
import 'package:flutter/material.dart';
|
|
|
|
import '../main.dart' show supabase;
|
|
import '../widgets/app_dialogs.dart';
|
|
import '../widgets/entrance.dart';
|
|
import '../widgets/pill_snackbar.dart';
|
|
import '../widgets/tap_bounce.dart';
|
|
import 'credits_screen.dart';
|
|
import 'terms_screen.dart';
|
|
|
|
const Color _teal = Color(0xFF2F9E94);
|
|
const Color _accentPink = Color(0xFFFF55A7);
|
|
|
|
/// Conteúdo da aba de Configurações, para ser embutido na bottom navigation
|
|
/// do LoggedHomeScreen (sem Scaffold/AppBar próprios).
|
|
class SettingsBody extends StatefulWidget {
|
|
const SettingsBody({super.key});
|
|
|
|
@override
|
|
State<SettingsBody> createState() => _SettingsBodyState();
|
|
}
|
|
|
|
class _SettingsBodyState extends State<SettingsBody> {
|
|
bool _deletingAccount = false;
|
|
|
|
Future<void> _signOut() async {
|
|
await supabase.auth.signOut();
|
|
if (!mounted) return;
|
|
Navigator.of(context).popUntil((route) => route.isFirst);
|
|
}
|
|
|
|
Future<void> _confirmDeleteAccountData() async {
|
|
final confirmed = await showConfirmDialog(
|
|
context,
|
|
title: 'Apagar dados da conta',
|
|
message:
|
|
'Isso remove permanentemente a sua conta, perfil, crianças '
|
|
'cadastradas e fotos — incluindo o login, permitindo criar uma '
|
|
'nova conta com o mesmo e-mail depois. Essa ação não pode ser '
|
|
'desfeita. Deseja continuar?',
|
|
confirmLabel: 'Apagar',
|
|
confirmColor: _accentPink,
|
|
);
|
|
|
|
if (confirmed != true) return;
|
|
|
|
setState(() => _deletingAccount = true);
|
|
try {
|
|
// A remoção de `auth.users` exige a service role key, que o app nunca
|
|
// deve carregar — por isso corre numa Edge Function (server-side); ver
|
|
// supabase/functions/delete-account. Sem isto, apagar só as linhas de
|
|
// `profiles`/`children` deixava o e-mail "ocupado" no Supabase Auth,
|
|
// impedindo criar uma nova conta com o mesmo e-mail.
|
|
final response = await supabase.functions.invoke('delete-account');
|
|
final data = response.data;
|
|
final errorMessage = (data is Map) ? data['error']?.toString() : null;
|
|
if (response.status != 200 || errorMessage != null) {
|
|
throw StateError(
|
|
errorMessage ?? 'Erro ao apagar conta (status ${response.status})',
|
|
);
|
|
}
|
|
|
|
await supabase.auth.signOut();
|
|
if (!mounted) return;
|
|
Navigator.of(context).popUntil((route) => route.isFirst);
|
|
} catch (e) {
|
|
if (mounted) showPillSnackBar(context, 'Erro ao apagar: $e');
|
|
} finally {
|
|
if (mounted) setState(() => _deletingAccount = false);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final user = supabase.auth.currentUser;
|
|
final name = (user?.userMetadata?['name'] ?? '').toString().trim();
|
|
final email = (user?.email ?? '').trim();
|
|
|
|
return ListView(
|
|
padding: const EdgeInsets.all(16),
|
|
children: [
|
|
FadeSlideIn(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
_SectionLabel('Conta'),
|
|
_SettingsCard(
|
|
children: [
|
|
_InfoTile(
|
|
icon: Icons.person_outline_rounded,
|
|
title: name.isEmpty ? 'Sem nome' : name,
|
|
subtitle: email,
|
|
),
|
|
const Divider(height: 1),
|
|
_ActionTile(
|
|
icon: Icons.logout_rounded,
|
|
title: 'Sair',
|
|
onTap: _signOut,
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 20),
|
|
FadeSlideIn(
|
|
delay: const Duration(milliseconds: 80),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
_SectionLabel('Sobre'),
|
|
_SettingsCard(
|
|
children: [
|
|
_ActionTile(
|
|
icon: Icons.description_outlined,
|
|
title: 'Termos de Serviço',
|
|
onTap: () => Navigator.of(context).push(
|
|
MaterialPageRoute<void>(
|
|
builder: (_) => const TermsScreen(),
|
|
),
|
|
),
|
|
),
|
|
const Divider(height: 1),
|
|
_ActionTile(
|
|
icon: Icons.diversity_3_outlined,
|
|
title: 'Criadores e colaboradores',
|
|
onTap: () => Navigator.of(context).push(
|
|
MaterialPageRoute<void>(
|
|
builder: (_) => const CreditsScreen(),
|
|
),
|
|
),
|
|
),
|
|
const Divider(height: 1),
|
|
const _InfoTile(
|
|
icon: Icons.info_outline_rounded,
|
|
title: 'Versão do app',
|
|
subtitle: '1.0.0',
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 20),
|
|
FadeSlideIn(
|
|
delay: const Duration(milliseconds: 160),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
_SectionLabel('Zona de risco'),
|
|
_SettingsCard(
|
|
children: [
|
|
_ActionTile(
|
|
icon: Icons.delete_forever_rounded,
|
|
title: 'Apagar dados da conta',
|
|
titleColor: _accentPink,
|
|
loading: _deletingAccount,
|
|
onTap: _deletingAccount ? null : _confirmDeleteAccountData,
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
class _SectionLabel extends StatelessWidget {
|
|
const _SectionLabel(this.text);
|
|
|
|
final String text;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Padding(
|
|
padding: const EdgeInsets.only(left: 4, bottom: 8),
|
|
child: Text(
|
|
text,
|
|
style: const TextStyle(
|
|
color: _teal,
|
|
fontWeight: FontWeight.w900,
|
|
fontSize: 14,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _SettingsCard extends StatelessWidget {
|
|
const _SettingsCard({required this.children});
|
|
|
|
final List<Widget> children;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Material(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(18),
|
|
elevation: 6,
|
|
shadowColor: Colors.black.withValues(alpha: 0.12),
|
|
child: Column(children: children),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _InfoTile extends StatelessWidget {
|
|
const _InfoTile({required this.icon, required this.title, this.subtitle});
|
|
|
|
final IconData icon;
|
|
final String title;
|
|
final String? subtitle;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return ListTile(
|
|
leading: Icon(icon, color: _teal),
|
|
title: Text(title, style: const TextStyle(fontWeight: FontWeight.w800)),
|
|
subtitle: (subtitle == null || subtitle!.isEmpty)
|
|
? null
|
|
: Text(subtitle!),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _ActionTile extends StatelessWidget {
|
|
const _ActionTile({
|
|
required this.icon,
|
|
required this.title,
|
|
required this.onTap,
|
|
this.titleColor,
|
|
this.loading = false,
|
|
});
|
|
|
|
final IconData icon;
|
|
final String title;
|
|
final VoidCallback? onTap;
|
|
final Color? titleColor;
|
|
final bool loading;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return TapBounce(
|
|
scale: 0.98,
|
|
child: ListTile(
|
|
leading: Icon(icon, color: titleColor ?? _teal),
|
|
title: Text(
|
|
title,
|
|
style: TextStyle(fontWeight: FontWeight.w800, color: titleColor),
|
|
),
|
|
trailing: loading
|
|
? const SizedBox(
|
|
width: 18,
|
|
height: 18,
|
|
child: CircularProgressIndicator(strokeWidth: 2),
|
|
)
|
|
: const Icon(Icons.chevron_right_rounded),
|
|
onTap: onTap,
|
|
),
|
|
);
|
|
}
|
|
}
|