CTK 1.0.0
This commit is contained in:
@@ -113,7 +113,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
|
||||
final user = response.user;
|
||||
if (user == null) {
|
||||
throw StateError('Usuário não encontrado após criar conta.');
|
||||
throw StateError('Utilizador não encontrado após criar a conta.');
|
||||
}
|
||||
|
||||
// Antes de persistir o perfil (o que já faz o AuthGate considerar a
|
||||
@@ -137,7 +137,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
if (!mounted) return;
|
||||
showPillSnackBar(
|
||||
context,
|
||||
'Esta conta não existe mais. Verifique o email ou crie uma nova conta.',
|
||||
'Esta conta já não existe. Verifique o email ou crie uma nova conta.',
|
||||
);
|
||||
} on AuthException catch (e) {
|
||||
if (!mounted) return;
|
||||
@@ -146,7 +146,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
if (!mounted) return;
|
||||
showPillSnackBar(
|
||||
context,
|
||||
'Tempo esgotado. Verifique sua conexão e tente novamente.',
|
||||
'Tempo esgotado. Verifique a sua ligação e tente novamente.',
|
||||
);
|
||||
} catch (e) {
|
||||
if (!mounted) return;
|
||||
@@ -159,14 +159,14 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
String _friendlyAuthError(AuthException e) {
|
||||
switch (e.code) {
|
||||
case 'invalid_credentials':
|
||||
return 'Email ou senha incorretos.';
|
||||
return 'Email ou palavra-passe incorretos.';
|
||||
case 'user_not_found':
|
||||
return 'Usuário não encontrado.';
|
||||
return 'Utilizador não encontrado.';
|
||||
case 'email_exists':
|
||||
case 'user_already_exists':
|
||||
return 'Este email já está em uso.';
|
||||
case 'weak_password':
|
||||
return 'Senha fraca. Use pelo menos 6 caracteres.';
|
||||
return 'Palavra-passe fraca. Utilize pelo menos 6 caracteres.';
|
||||
default:
|
||||
return e.message;
|
||||
}
|
||||
@@ -376,14 +376,14 @@ class _AuthForm extends StatelessWidget {
|
||||
children: [
|
||||
_AuthTextField(
|
||||
controller: nameController,
|
||||
hintText: 'Digite seu nome',
|
||||
hintText: 'Introduza o seu nome',
|
||||
icon: Icons.person_outline_rounded,
|
||||
textInputAction: TextInputAction.next,
|
||||
textCapitalization: TextCapitalization.sentences,
|
||||
inputFormatters: [CapitalizeFirstLetterFormatter()],
|
||||
validator: (v) {
|
||||
final value = (v ?? '').trim();
|
||||
if (value.isEmpty) return 'Informe seu nome';
|
||||
if (value.isEmpty) return 'Indique o seu nome';
|
||||
if (value.length < 2) return 'Nome muito curto';
|
||||
if (!_namePattern.hasMatch(value)) {
|
||||
return 'O nome não pode conter números';
|
||||
@@ -398,13 +398,13 @@ class _AuthForm extends StatelessWidget {
|
||||
),
|
||||
_AuthTextField(
|
||||
controller: emailController,
|
||||
hintText: 'Digite seu email',
|
||||
hintText: 'Introduza o seu email',
|
||||
icon: Icons.mail_outline_rounded,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
textInputAction: TextInputAction.next,
|
||||
validator: (v) {
|
||||
final value = (v ?? '').trim();
|
||||
if (value.isEmpty) return 'Informe seu email';
|
||||
if (value.isEmpty) return 'Indique o seu email';
|
||||
if (!value.contains('@')) return 'Email inválido';
|
||||
return null;
|
||||
},
|
||||
@@ -412,13 +412,13 @@ class _AuthForm extends StatelessWidget {
|
||||
const SizedBox(height: 12),
|
||||
_AuthTextField(
|
||||
controller: passwordController,
|
||||
hintText: 'Digite sua senha',
|
||||
hintText: 'Introduza a sua palavra-passe',
|
||||
icon: Icons.lock_outline_rounded,
|
||||
obscureText: true,
|
||||
textInputAction: TextInputAction.done,
|
||||
validator: (v) {
|
||||
final value = v ?? '';
|
||||
if (value.isEmpty) return 'Informe sua senha';
|
||||
if (value.isEmpty) return 'Indique a sua palavra-passe';
|
||||
if (value.length < 6) return 'Mínimo de 6 caracteres';
|
||||
return null;
|
||||
},
|
||||
|
||||
@@ -813,6 +813,20 @@ class _InicioTab extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
FadeSlideIn(
|
||||
delay: const Duration(milliseconds: 150),
|
||||
child: Center(
|
||||
child: Text(
|
||||
'Mais funcionalidades em breve',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 12.5,
|
||||
color: Colors.black.withValues(alpha: 0.35),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
),
|
||||
@@ -1095,7 +1109,7 @@ Future<Map<String, dynamic>?> _requireFirstChild(
|
||||
) async {
|
||||
final proceed = await showConfirmDialog(
|
||||
context,
|
||||
title: 'Cadastre uma criança',
|
||||
title: 'Registe uma criança',
|
||||
message: 'Antes de iniciar o quiz, adicione uma criança ao seu perfil.',
|
||||
confirmLabel: 'Adicionar criança',
|
||||
);
|
||||
@@ -1636,7 +1650,7 @@ class _PerfilTabState extends State<_PerfilTab> {
|
||||
),
|
||||
onPressed: () =>
|
||||
Navigator.of(ctx).pop(ImageSource.camera),
|
||||
child: const Text('Câmera'),
|
||||
child: const Text('Câmara'),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -1729,7 +1743,7 @@ class _PerfilTabState extends State<_PerfilTab> {
|
||||
context,
|
||||
title: 'Remover criança',
|
||||
message:
|
||||
'Tem certeza que deseja remover "$childName"? Essa ação não pode ser desfeita.',
|
||||
'Tem a certeza que quer remover "$childName"? Esta ação não pode ser desfeita.',
|
||||
confirmLabel: 'Remover',
|
||||
confirmColor: const Color(0xFFFF55A7),
|
||||
);
|
||||
@@ -2160,7 +2174,7 @@ class _PerfilTabState extends State<_PerfilTab> {
|
||||
: 'Criança ${i + 1}';
|
||||
final subtitle = [
|
||||
if (childAge != null) 'Idade: $childAge',
|
||||
if (childGender.isNotEmpty) 'Gênero: $childGender',
|
||||
if (childGender.isNotEmpty) 'Género: $childGender',
|
||||
].join(' • ');
|
||||
final bool selected = i == selectedIndex;
|
||||
|
||||
@@ -2389,7 +2403,7 @@ class _AddChildSheetState extends State<_AddChildSheet> {
|
||||
final formOk = _formKey.currentState?.validate() ?? false;
|
||||
setState(() {
|
||||
_birthDateError = _birthDate == null
|
||||
? 'Informe a data de nascimento'
|
||||
? 'Indique a data de nascimento'
|
||||
: null;
|
||||
});
|
||||
if (!formOk || _birthDate == null) return;
|
||||
@@ -2442,7 +2456,7 @@ class _AddChildSheetState extends State<_AddChildSheet> {
|
||||
),
|
||||
validator: (v) {
|
||||
final value = (v ?? '').trim();
|
||||
if (value.isEmpty) return 'Informe o nome';
|
||||
if (value.isEmpty) return 'Indique o nome';
|
||||
if (value.length < 2) return 'Nome muito curto';
|
||||
if (!_namePattern.hasMatch(value)) {
|
||||
return 'O nome não pode conter números';
|
||||
@@ -2490,10 +2504,10 @@ class _AddChildSheetState extends State<_AddChildSheet> {
|
||||
DropdownMenuItem(value: 'Outro', child: Text('Outro')),
|
||||
],
|
||||
onChanged: (v) => setState(() => _gender = v),
|
||||
decoration: const InputDecoration(labelText: 'Gênero'),
|
||||
decoration: const InputDecoration(labelText: 'Género'),
|
||||
validator: (v) {
|
||||
if (v == null || v.trim().isEmpty) {
|
||||
return 'Selecione o gênero';
|
||||
return 'Selecione o género';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
@@ -920,11 +920,11 @@ class Quiz19Screen extends StatelessWidget {
|
||||
'Qual das seguintes imagens é mais parecida com o perfil do seu filho/a?',
|
||||
answers: [
|
||||
QuizAnswer(
|
||||
title: 'Perfil reto',
|
||||
description: 'Perfil facial reto',
|
||||
weight: 1,
|
||||
title: 'Perfil convexo',
|
||||
description: 'Perfil facial convexo',
|
||||
weight: 2,
|
||||
hideTitle: true,
|
||||
value: 'perfil_reto',
|
||||
value: 'perfil_convexo',
|
||||
imageBuilder: (context) => _zoomCrop(
|
||||
'assets/mockup_images/1.jpeg',
|
||||
alignment: const Alignment(0.1, 0.7),
|
||||
@@ -932,11 +932,11 @@ class Quiz19Screen extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
QuizAnswer(
|
||||
title: 'Perfil convexo',
|
||||
description: 'Perfil facial convexo',
|
||||
weight: 2,
|
||||
title: 'Perfil reto',
|
||||
description: 'Perfil facial reto',
|
||||
weight: 1,
|
||||
hideTitle: true,
|
||||
value: 'perfil_convexo',
|
||||
value: 'perfil_reto',
|
||||
imageBuilder: (context) => _zoomCrop(
|
||||
'assets/mockup_images/2.jpeg',
|
||||
alignment: const Alignment(0.1, 0.7),
|
||||
@@ -1199,20 +1199,20 @@ class Quiz24Screen extends StatelessWidget {
|
||||
question:
|
||||
'Qual das seguintes imagens se assemelha à boca do seu filho/a?',
|
||||
answers: const [
|
||||
QuizAnswer(
|
||||
title: 'Ligeiro desalinhamento',
|
||||
description: 'Pequeno desalinhamento ou espaço entre alguns dentes',
|
||||
weight: 1,
|
||||
hideTitle: true,
|
||||
value: 'apinhamento_leve',
|
||||
imagePath: 'assets/mockup_images/16.png',
|
||||
),
|
||||
QuizAnswer(
|
||||
title: 'Apinhamento moderado',
|
||||
description: 'Dentes rodados/desalinhados de forma mais visível',
|
||||
weight: 2,
|
||||
hideTitle: true,
|
||||
value: 'apinhamento_moderado',
|
||||
imagePath: 'assets/mockup_images/16.png',
|
||||
),
|
||||
QuizAnswer(
|
||||
title: 'Ligeiro desalinhamento',
|
||||
description: 'Pequeno desalinhamento ou espaço entre alguns dentes',
|
||||
weight: 1,
|
||||
hideTitle: true,
|
||||
value: 'apinhamento_leve',
|
||||
imagePath: 'assets/mockup_images/15.png',
|
||||
),
|
||||
QuizAnswer(
|
||||
@@ -1297,20 +1297,20 @@ class Quiz26Screen extends StatelessWidget {
|
||||
question:
|
||||
'Qual das seguintes imagens se assemelha ao freio lingual do seu filho/a?',
|
||||
answers: const [
|
||||
QuizAnswer(
|
||||
title: 'Freio lingual correto',
|
||||
description: 'Língua move-se livremente, sem restrição visível',
|
||||
weight: 1,
|
||||
hideTitle: true,
|
||||
value: 'freio_lingual_correto',
|
||||
imagePath: 'assets/mockup_images/17.png',
|
||||
),
|
||||
QuizAnswer(
|
||||
title: 'Freio lingual inadequado',
|
||||
description: 'Freio lingual curto/apertado (língua em coração)',
|
||||
weight: 2,
|
||||
hideTitle: true,
|
||||
value: 'freio_lingual_inadequado',
|
||||
imagePath: 'assets/mockup_images/17.png',
|
||||
),
|
||||
QuizAnswer(
|
||||
title: 'Freio lingual correto',
|
||||
description: 'Língua move-se livremente, sem restrição visível',
|
||||
weight: 1,
|
||||
hideTitle: true,
|
||||
value: 'freio_lingual_correto',
|
||||
imagePath: 'assets/mockup_images/18.jpeg',
|
||||
),
|
||||
],
|
||||
@@ -1389,20 +1389,20 @@ class Quiz28Screen extends StatelessWidget {
|
||||
question:
|
||||
'Qual das seguintes imagens se assemelha ao céu da boca do seu filho/a?',
|
||||
answers: const [
|
||||
QuizAnswer(
|
||||
title: 'Posição em U saudável',
|
||||
description: 'Palato largo, em forma de U',
|
||||
weight: 1,
|
||||
hideTitle: true,
|
||||
value: 'palato_u',
|
||||
imagePath: 'assets/mockup_images/26.png',
|
||||
),
|
||||
QuizAnswer(
|
||||
title: 'Posição profunda incorreta',
|
||||
description: 'Palato estreito/profundo, em forma de V',
|
||||
weight: 2,
|
||||
hideTitle: true,
|
||||
value: 'palato_v',
|
||||
imagePath: 'assets/mockup_images/26.png',
|
||||
),
|
||||
QuizAnswer(
|
||||
title: 'Posição em U saudável',
|
||||
description: 'Palato largo, em forma de U',
|
||||
weight: 1,
|
||||
hideTitle: true,
|
||||
value: 'palato_u',
|
||||
imagePath: 'assets/mockup_images/27.png',
|
||||
),
|
||||
],
|
||||
|
||||
@@ -79,7 +79,7 @@ class QuizVideoGuideScreen extends StatefulWidget {
|
||||
required this.youtubeId,
|
||||
required this.onAdvance,
|
||||
this.caption =
|
||||
'Assista para compreender melhor as próximas perguntas do questionário.',
|
||||
'Veja o vídeo para compreender melhor as próximas perguntas do questionário.',
|
||||
this.extraHeading,
|
||||
this.extraSubtitle,
|
||||
});
|
||||
@@ -144,7 +144,7 @@ class _QuizVideoGuideScreenState extends State<QuizVideoGuideScreen> {
|
||||
const SizedBox(height: 4),
|
||||
const FadeSlideIn(
|
||||
child: Text(
|
||||
'Antes de continuar, assista ao vídeo educativo...',
|
||||
'Antes de continuar, veja o vídeo educativo...',
|
||||
style: TextStyle(
|
||||
fontSize: 21,
|
||||
fontWeight: FontWeight.w900,
|
||||
|
||||
@@ -34,10 +34,10 @@ class _SettingsBodyState extends State<SettingsBody> {
|
||||
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?',
|
||||
'Isto remove permanentemente a sua conta, perfil, crianças '
|
||||
'registadas e fotos — incluindo o login, permitindo criar uma '
|
||||
'nova conta com o mesmo e-mail depois. Esta ação não pode ser '
|
||||
'desfeita. Pretende continuar?',
|
||||
confirmLabel: 'Apagar',
|
||||
confirmColor: _accentPink,
|
||||
);
|
||||
|
||||
@@ -253,7 +253,7 @@ class _VideoScreenState extends State<VideoScreen> {
|
||||
elevation: 0,
|
||||
scrolledUnderElevation: 0,
|
||||
title: const Text(
|
||||
'Videos Educativos',
|
||||
'Vídeos Educativos',
|
||||
style: TextStyle(fontWeight: FontWeight.w900),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -16,8 +16,8 @@ const List<PrivacyConsentItem> kPrivacyConsentItems = [
|
||||
id: 'health_data',
|
||||
text:
|
||||
'Concordo com o processamento dos meus dados pessoais de saúde para '
|
||||
'ter acesso aos recursos do aplicativo Check-Teeth Kids. Veja mais '
|
||||
'na Política de Privacidade.',
|
||||
'ter acesso às funcionalidades da aplicação Check-Teeth Kids. Veja '
|
||||
'mais na Política de Privacidade.',
|
||||
),
|
||||
PrivacyConsentItem(
|
||||
id: 'privacy_policy',
|
||||
@@ -26,11 +26,11 @@ const List<PrivacyConsentItem> kPrivacyConsentItems = [
|
||||
PrivacyConsentItem(
|
||||
id: 'tracking',
|
||||
text:
|
||||
'Autorizo o CK-T-KD a rastrear a minha atividade na app. Podem '
|
||||
'receber dados como minha faixa etária, status da assinatura, '
|
||||
'Autorizo o CK-T-KD a monitorizar a minha atividade na app. Podem '
|
||||
'receber dados como a minha faixa etária, estado da subscrição, '
|
||||
'momento de abertura da app e identificadores técnicos, conforme '
|
||||
'descrito na Política de Privacidade. Isso ajuda a CK-T-KD a '
|
||||
'aprimorar as suas campanhas publicitárias. *',
|
||||
'descrito na Política de Privacidade. Isto ajuda o CK-T-KD a '
|
||||
'melhorar as suas campanhas publicitárias. *',
|
||||
),
|
||||
];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user