Files
CheckTheethKids/lib/screens/settings_screen.dart
Carlos Correia a8af2c6845 CKT String|Colors
2026-07-15 19:13:15 +01:00

264 lines
7.8 KiB
Dart

import 'package:flutter/material.dart';
import '../colors/app_colors.dart';
import '../main.dart' show supabase;
import '../strings/settings_strings.dart';
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 = AppColors.teal;
const Color _accentPink = AppColors.pink;
/// 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: SettingsStrings.deleteAccountData,
message: SettingsStrings.deleteAccountMessage,
confirmLabel: SettingsStrings.delete,
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 ??
SettingsStrings.errorDeletingAccount(response.status),
);
}
await supabase.auth.signOut();
if (!mounted) return;
Navigator.of(context).popUntil((route) => route.isFirst);
} catch (e) {
if (mounted) showPillSnackBar(context, SettingsStrings.errorDeleting(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(SettingsStrings.account),
_SettingsCard(
children: [
_InfoTile(
icon: Icons.person_outline_rounded,
title: name.isEmpty ? SettingsStrings.noName : name,
subtitle: email,
),
const Divider(height: 1),
_ActionTile(
icon: Icons.logout_rounded,
title: SettingsStrings.signOut,
onTap: _signOut,
),
],
),
],
),
),
const SizedBox(height: 20),
FadeSlideIn(
delay: const Duration(milliseconds: 80),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_SectionLabel(SettingsStrings.about),
_SettingsCard(
children: [
_ActionTile(
icon: Icons.description_outlined,
title: SettingsStrings.termsOfService,
onTap: () => Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => const TermsScreen(),
),
),
),
const Divider(height: 1),
_ActionTile(
icon: Icons.diversity_3_outlined,
title: SettingsStrings.creatorsAndContributors,
onTap: () => Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => const CreditsScreen(),
),
),
),
const Divider(height: 1),
const _InfoTile(
icon: Icons.info_outline_rounded,
title: SettingsStrings.appVersion,
subtitle: '1.0.0',
),
],
),
],
),
),
const SizedBox(height: 20),
FadeSlideIn(
delay: const Duration(milliseconds: 160),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_SectionLabel(SettingsStrings.dangerZone),
_SettingsCard(
children: [
_ActionTile(
icon: Icons.delete_forever_rounded,
title: SettingsStrings.deleteAccountData,
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,
),
);
}
}