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