CKT String|Colors

This commit is contained in:
Carlos Correia
2026-07-15 19:13:15 +01:00
parent 3214a07779
commit a8af2c6845
35 changed files with 1390 additions and 688 deletions

View File

@@ -1,6 +1,7 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'colors/app_colors.dart';
import 'package:flutter/services.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
@@ -8,15 +9,16 @@ import 'auth_gate.dart' show pendingPrivacyUserId, pendingTermsUserId;
import 'main.dart' show supabase;
import 'privacy_gate_prefs.dart';
import 'terms_gate_prefs.dart';
import 'widgets/app_gradients.dart';
import 'colors/app_gradients.dart';
import 'strings/auth_strings.dart';
import 'widgets/entrance.dart';
import 'widgets/liquid_waves_background.dart';
import 'widgets/name_input_formatter.dart';
import 'widgets/pill_snackbar.dart';
import 'widgets/tap_bounce.dart';
const Color _teal = Color(0xFF2F9E94);
const Color _pink = Color(0xFFFF55A7);
const Color _teal = AppColors.teal;
const Color _pink = AppColors.pink;
/// Nomes só podem ter letras (incluindo acentuadas) e espaços — sem números.
final RegExp _namePattern = RegExp(r"^[a-zA-ZÀ-ÖØ-öø-ÿ' -]+$");
@@ -113,7 +115,7 @@ class _HomeScreenState extends State<HomeScreen> {
final user = response.user;
if (user == null) {
throw StateError('Utilizador não encontrado após criar a conta.');
throw StateError(AuthStrings.userNotFoundAfterSignUp);
}
// Antes de persistir o perfil (o que já faz o AuthGate considerar a
@@ -137,7 +139,7 @@ class _HomeScreenState extends State<HomeScreen> {
if (!mounted) return;
showPillSnackBar(
context,
'Esta conta já não existe. Verifique o email ou crie uma nova conta.',
AuthStrings.accountNoLongerExists,
);
} on AuthException catch (e) {
if (!mounted) return;
@@ -146,11 +148,11 @@ class _HomeScreenState extends State<HomeScreen> {
if (!mounted) return;
showPillSnackBar(
context,
'Tempo esgotado. Verifique a sua ligação e tente novamente.',
AuthStrings.timeoutError,
);
} catch (e) {
if (!mounted) return;
showPillSnackBar(context, 'Erro: $e');
showPillSnackBar(context, AuthStrings.genericError(e));
} finally {
if (mounted) setState(() => _loading = false);
}
@@ -159,14 +161,14 @@ class _HomeScreenState extends State<HomeScreen> {
String _friendlyAuthError(AuthException e) {
switch (e.code) {
case 'invalid_credentials':
return 'Email ou palavra-passe incorretos.';
return AuthStrings.invalidCredentials;
case 'user_not_found':
return 'Utilizador não encontrado.';
return AuthStrings.userNotFound;
case 'email_exists':
case 'user_already_exists':
return 'Este email já está em uso.';
return AuthStrings.emailAlreadyInUse;
case 'weak_password':
return 'Palavra-passe fraca. Utilize pelo menos 6 caracteres.';
return AuthStrings.weakPassword;
default:
return e.message;
}
@@ -178,7 +180,7 @@ class _HomeScreenState extends State<HomeScreen> {
body: Stack(
clipBehavior: Clip.none,
children: [
Positioned.fill(child: Container(color: const Color(0xFFFAFAF7))),
Positioned.fill(child: Container(color: AppColors.background)),
const LiquidWavesBackground(),
SafeArea(
child: LayoutBuilder(
@@ -195,7 +197,7 @@ class _HomeScreenState extends State<HomeScreen> {
children: [
const FadeSlideIn(
child: Text(
'Check-Teeth Kids',
AuthStrings.appTitle,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 26,
@@ -210,7 +212,7 @@ class _HomeScreenState extends State<HomeScreen> {
FadeSlideIn(
delay: const Duration(milliseconds: 80),
child: Text(
'Deteção e Prevenção da Má Oclusão Infantil',
AuthStrings.appSubtitle,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 13.5,
@@ -279,14 +281,14 @@ class _AuthTabSwitch extends StatelessWidget {
children: [
Expanded(
child: _AuthTab(
label: 'Entrar',
label: AuthStrings.login,
selected: isLogin,
onTap: () => onChanged(true),
),
),
Expanded(
child: _AuthTab(
label: 'Criar Conta',
label: AuthStrings.createAccount,
selected: !isLogin,
onTap: () => onChanged(false),
),
@@ -376,17 +378,17 @@ class _AuthForm extends StatelessWidget {
children: [
_AuthTextField(
controller: nameController,
hintText: 'Introduza o seu nome',
hintText: AuthStrings.nameHint,
icon: Icons.person_outline_rounded,
textInputAction: TextInputAction.next,
textCapitalization: TextCapitalization.sentences,
inputFormatters: [CapitalizeFirstLetterFormatter()],
validator: (v) {
final value = (v ?? '').trim();
if (value.isEmpty) return 'Indique o seu nome';
if (value.length < 2) return 'Nome muito curto';
if (value.isEmpty) return AuthStrings.nameRequired;
if (value.length < 2) return AuthStrings.nameTooShort;
if (!_namePattern.hasMatch(value)) {
return 'O nome não pode conter números';
return AuthStrings.nameNoNumbers;
}
return null;
},
@@ -398,28 +400,28 @@ class _AuthForm extends StatelessWidget {
),
_AuthTextField(
controller: emailController,
hintText: 'Introduza o seu email',
hintText: AuthStrings.emailHint,
icon: Icons.mail_outline_rounded,
keyboardType: TextInputType.emailAddress,
textInputAction: TextInputAction.next,
validator: (v) {
final value = (v ?? '').trim();
if (value.isEmpty) return 'Indique o seu email';
if (!value.contains('@')) return 'Email inválido';
if (value.isEmpty) return AuthStrings.emailRequired;
if (!value.contains('@')) return AuthStrings.emailInvalid;
return null;
},
),
const SizedBox(height: 12),
_AuthTextField(
controller: passwordController,
hintText: 'Introduza a sua palavra-passe',
hintText: AuthStrings.passwordHint,
icon: Icons.lock_outline_rounded,
obscureText: true,
textInputAction: TextInputAction.done,
validator: (v) {
final value = v ?? '';
if (value.isEmpty) return 'Indique a sua palavra-passe';
if (value.length < 6) return 'Mínimo de 6 caracteres';
if (value.isEmpty) return AuthStrings.passwordRequired;
if (value.length < 6) return AuthStrings.passwordTooShort;
return null;
},
),
@@ -471,7 +473,7 @@ class _AuthForm extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Text(isLogin ? 'Entrar' : 'Criar Conta'),
Text(isLogin ? AuthStrings.login : AuthStrings.createAccount),
const SizedBox(width: 8),
const Icon(Icons.arrow_forward_rounded, size: 18),
],