Videos na nuvem | atualização de desing (Appbar) | mudança no quiz
This commit is contained in:
@@ -6,6 +6,7 @@ import 'package:lottie/lottie.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
|
||||
import 'main.dart' show supabase;
|
||||
import 'widgets/app_gradients.dart';
|
||||
import 'widgets/entrance.dart';
|
||||
import 'widgets/tap_bounce.dart';
|
||||
|
||||
@@ -15,6 +16,12 @@ const Color _pink = Color(0xFFFF55A7);
|
||||
/// Nomes só podem ter letras (incluindo acentuadas) e espaços — sem números.
|
||||
final RegExp _namePattern = RegExp(r"^[a-zA-ZÀ-ÖØ-öø-ÿ' -]+$");
|
||||
|
||||
/// A conta existe no Supabase Auth mas os dados (perfil) já não existem na
|
||||
/// base de dados — tratada como conta inexistente para efeitos de login.
|
||||
class _AccountNotFoundException implements Exception {
|
||||
const _AccountNotFoundException();
|
||||
}
|
||||
|
||||
class HomeScreen extends StatefulWidget {
|
||||
const HomeScreen({super.key});
|
||||
|
||||
@@ -50,11 +57,10 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
required String name,
|
||||
required String email,
|
||||
}) async {
|
||||
await supabase.from('profiles').upsert({
|
||||
'id': uid,
|
||||
'name': name,
|
||||
'email': email,
|
||||
}).timeout(const Duration(seconds: 20));
|
||||
await supabase
|
||||
.from('profiles')
|
||||
.upsert({'id': uid, 'name': name, 'email': email})
|
||||
.timeout(const Duration(seconds: 20));
|
||||
}
|
||||
|
||||
Future<void> _submit() async {
|
||||
@@ -66,10 +72,34 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
final password = _passwordController.text;
|
||||
|
||||
if (_isLogin) {
|
||||
await supabase.auth.signInWithPassword(
|
||||
final result = await supabase.auth.signInWithPassword(
|
||||
email: email,
|
||||
password: password,
|
||||
);
|
||||
|
||||
final user = result.user;
|
||||
if (user != null) {
|
||||
final profile = await supabase
|
||||
.from('profiles')
|
||||
.select('id')
|
||||
.eq('id', user.id)
|
||||
.maybeSingle();
|
||||
|
||||
if (profile == null) {
|
||||
// A conta existe no Auth mas os dados foram apagados (ex.: via
|
||||
// "Apagar dados da conta" ou diretamente na base de dados).
|
||||
// Trata como inexistente: limpa qualquer resquício e bloqueia.
|
||||
unawaited(
|
||||
supabase
|
||||
.from('children')
|
||||
.delete()
|
||||
.eq('owner_id', user.id)
|
||||
.catchError((_) => <Map<String, dynamic>>[]),
|
||||
);
|
||||
await supabase.auth.signOut();
|
||||
throw const _AccountNotFoundException();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
final name = _nameController.text.trim();
|
||||
final response = await supabase.auth
|
||||
@@ -81,14 +111,19 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
throw StateError('Usuário não encontrado após criar conta.');
|
||||
}
|
||||
|
||||
unawaited(
|
||||
_persistRegistrationData(
|
||||
uid: user.id,
|
||||
name: name,
|
||||
email: email,
|
||||
).catchError((_) {}),
|
||||
);
|
||||
// Precisa de terminar antes de navegar: o AuthGate só mostra a app
|
||||
// depois de confirmar que existe um perfil na base de dados.
|
||||
await _persistRegistrationData(uid: user.id, name: name, email: email);
|
||||
}
|
||||
} on _AccountNotFoundException {
|
||||
if (!mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text(
|
||||
'Esta conta não existe mais. Verifique o email ou crie uma nova conta.',
|
||||
),
|
||||
),
|
||||
);
|
||||
} on AuthException catch (e) {
|
||||
if (!mounted) return;
|
||||
ScaffoldMessenger.of(
|
||||
@@ -412,56 +447,59 @@ class _AuthForm extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
TapBounce(
|
||||
child: SizedBox(
|
||||
height: 50,
|
||||
child: FilledButton(
|
||||
style:
|
||||
FilledButton.styleFrom(
|
||||
backgroundColor: _teal,
|
||||
foregroundColor: Colors.white,
|
||||
shape: const StadiumBorder(),
|
||||
textStyle: const TextStyle(
|
||||
fontWeight: FontWeight.w800,
|
||||
fontSize: 15,
|
||||
),
|
||||
).copyWith(
|
||||
animationDuration: const Duration(milliseconds: 180),
|
||||
splashFactory: InkSparkle.splashFactory,
|
||||
overlayColor: WidgetStateProperty.resolveWith<Color?>((
|
||||
states,
|
||||
) {
|
||||
if (states.contains(WidgetState.pressed)) {
|
||||
return Colors.white.withValues(alpha: 0.14);
|
||||
}
|
||||
if (states.contains(WidgetState.hovered) ||
|
||||
states.contains(WidgetState.focused)) {
|
||||
return Colors.white.withValues(alpha: 0.08);
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
),
|
||||
onPressed: loading ? null : onSubmit,
|
||||
child: loading
|
||||
? const SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2.2,
|
||||
color: Colors.white,
|
||||
),
|
||||
)
|
||||
: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(isLogin ? 'Entrar' : 'Criar Conta'),
|
||||
const SizedBox(width: 8),
|
||||
const Icon(
|
||||
Icons.arrow_forward_rounded,
|
||||
size: 18,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(999),
|
||||
child: DecoratedBox(
|
||||
decoration: const BoxDecoration(gradient: kGreenButtonGradient),
|
||||
child: SizedBox(
|
||||
height: 50,
|
||||
child: FilledButton(
|
||||
style:
|
||||
FilledButton.styleFrom(
|
||||
backgroundColor: Colors.transparent,
|
||||
foregroundColor: Colors.white,
|
||||
shape: const StadiumBorder(),
|
||||
textStyle: const TextStyle(
|
||||
fontWeight: FontWeight.w800,
|
||||
fontSize: 15,
|
||||
),
|
||||
],
|
||||
),
|
||||
).copyWith(
|
||||
animationDuration: const Duration(milliseconds: 180),
|
||||
splashFactory: InkSparkle.splashFactory,
|
||||
overlayColor: WidgetStateProperty.resolveWith<Color?>(
|
||||
(states) {
|
||||
if (states.contains(WidgetState.pressed)) {
|
||||
return Colors.white.withValues(alpha: 0.14);
|
||||
}
|
||||
if (states.contains(WidgetState.hovered) ||
|
||||
states.contains(WidgetState.focused)) {
|
||||
return Colors.white.withValues(alpha: 0.08);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
),
|
||||
onPressed: loading ? null : onSubmit,
|
||||
child: loading
|
||||
? const SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2.2,
|
||||
color: Colors.white,
|
||||
),
|
||||
)
|
||||
: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(isLogin ? 'Entrar' : 'Criar Conta'),
|
||||
const SizedBox(width: 8),
|
||||
const Icon(Icons.arrow_forward_rounded, size: 18),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user