CKT String|Colors
This commit is contained in:
36
lib/colors/app_colors.dart
Normal file
36
lib/colors/app_colors.dart
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// Paleta de cores da aplicação — todas as cores de marca usadas em mais do
|
||||||
|
/// que um sítio vivem aqui. Para mudar uma cor em toda a app, mude-a aqui
|
||||||
|
/// em vez de editar cada ecrã individualmente.
|
||||||
|
class AppColors {
|
||||||
|
const AppColors._();
|
||||||
|
|
||||||
|
/// Verde-azulado principal — botões, ícones e destaques "positivos".
|
||||||
|
static const Color teal = Color(0xFF2F9E94);
|
||||||
|
|
||||||
|
/// Rosa principal — títulos, destaques e ações "de atenção".
|
||||||
|
static const Color pink = Color(0xFFFF55A7);
|
||||||
|
|
||||||
|
/// Rosa claro — usado no título do ecrã de boas-vindas.
|
||||||
|
static const Color pinkLight = Color(0xFFFF9AD0);
|
||||||
|
|
||||||
|
/// Rosa vivo — extremo do gradiente do card de destaque do quiz.
|
||||||
|
static const Color pinkVivid = Color(0xFFE83E93);
|
||||||
|
|
||||||
|
/// Roxo — usado em pequenos destaques informativos (ex.: ícone de
|
||||||
|
/// "Episódios completos").
|
||||||
|
static const Color purple = Color(0xFF8E7CC3);
|
||||||
|
|
||||||
|
/// Verde-azulado escuro — extremo do gradiente das app bars.
|
||||||
|
static const Color tealDark = Color(0xFF1C7A6E);
|
||||||
|
|
||||||
|
/// Verde-azulado claro — extremo do gradiente das app bars.
|
||||||
|
static const Color tealLight = Color(0xFF8FD4BB);
|
||||||
|
|
||||||
|
/// Fundo bege claro usado em quase todos os ecrãs.
|
||||||
|
static const Color background = Color(0xFFFAFAF7);
|
||||||
|
|
||||||
|
/// Fundo rosa muito claro — usado em folhas modais e cartões suaves.
|
||||||
|
static const Color pinkBackground = Color(0xFFFFE6F1);
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'app_colors.dart';
|
||||||
|
|
||||||
/// Gradiente usado em todas as app bars da aplicação. Direção puramente
|
/// Gradiente usado em todas as app bars da aplicação. Direção puramente
|
||||||
/// horizontal (não diagonal) de propósito: a app bar retrátil da Home é
|
/// horizontal (não diagonal) de propósito: a app bar retrátil da Home é
|
||||||
/// alta (~190) enquanto as das outras abas são baixas (~56) — um gradiente
|
/// alta (~190) enquanto as das outras abas são baixas (~56) — um gradiente
|
||||||
@@ -10,7 +12,7 @@ import 'package:flutter/material.dart';
|
|||||||
const LinearGradient kAppBarGradient = LinearGradient(
|
const LinearGradient kAppBarGradient = LinearGradient(
|
||||||
begin: Alignment.centerLeft,
|
begin: Alignment.centerLeft,
|
||||||
end: Alignment.centerRight,
|
end: Alignment.centerRight,
|
||||||
colors: [Color(0xFF1C7A6E), Color(0xFF8FD4BB)],
|
colors: [AppColors.tealDark, AppColors.tealLight],
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Cor sólida (sem gradiente, de propósito) usada nos botões e cards verdes
|
/// Cor sólida (sem gradiente, de propósito) usada nos botões e cards verdes
|
||||||
@@ -19,12 +21,12 @@ const LinearGradient kAppBarGradient = LinearGradient(
|
|||||||
/// mantém-se como [LinearGradient] com as duas cores iguais para não obrigar
|
/// mantém-se como [LinearGradient] com as duas cores iguais para não obrigar
|
||||||
/// a mudar todos os `decoration: gradient: kGreenButtonGradient` existentes.
|
/// a mudar todos os `decoration: gradient: kGreenButtonGradient` existentes.
|
||||||
const LinearGradient kGreenButtonGradient = LinearGradient(
|
const LinearGradient kGreenButtonGradient = LinearGradient(
|
||||||
colors: [Color(0xFF2F9E94), Color(0xFF2F9E94)],
|
colors: [AppColors.teal, AppColors.teal],
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Gradiente rosa vivo do card do quiz na Home.
|
/// Gradiente rosa vivo do card do quiz na Home.
|
||||||
const LinearGradient kPinkHeroGradient = LinearGradient(
|
const LinearGradient kPinkHeroGradient = LinearGradient(
|
||||||
begin: Alignment.topLeft,
|
begin: Alignment.topLeft,
|
||||||
end: Alignment.bottomRight,
|
end: Alignment.bottomRight,
|
||||||
colors: [Color(0xFFFF55A7), Color(0xFFE83E93)],
|
colors: [AppColors.pink, AppColors.pinkVivid],
|
||||||
);
|
);
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'colors/app_colors.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:supabase_flutter/supabase_flutter.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 'main.dart' show supabase;
|
||||||
import 'privacy_gate_prefs.dart';
|
import 'privacy_gate_prefs.dart';
|
||||||
import 'terms_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/entrance.dart';
|
||||||
import 'widgets/liquid_waves_background.dart';
|
import 'widgets/liquid_waves_background.dart';
|
||||||
import 'widgets/name_input_formatter.dart';
|
import 'widgets/name_input_formatter.dart';
|
||||||
import 'widgets/pill_snackbar.dart';
|
import 'widgets/pill_snackbar.dart';
|
||||||
import 'widgets/tap_bounce.dart';
|
import 'widgets/tap_bounce.dart';
|
||||||
|
|
||||||
const Color _teal = Color(0xFF2F9E94);
|
const Color _teal = AppColors.teal;
|
||||||
const Color _pink = Color(0xFFFF55A7);
|
const Color _pink = AppColors.pink;
|
||||||
|
|
||||||
/// Nomes só podem ter letras (incluindo acentuadas) e espaços — sem números.
|
/// Nomes só podem ter letras (incluindo acentuadas) e espaços — sem números.
|
||||||
final RegExp _namePattern = RegExp(r"^[a-zA-ZÀ-ÖØ-öø-ÿ' -]+$");
|
final RegExp _namePattern = RegExp(r"^[a-zA-ZÀ-ÖØ-öø-ÿ' -]+$");
|
||||||
@@ -113,7 +115,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
|
|
||||||
final user = response.user;
|
final user = response.user;
|
||||||
if (user == null) {
|
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
|
// 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;
|
if (!mounted) return;
|
||||||
showPillSnackBar(
|
showPillSnackBar(
|
||||||
context,
|
context,
|
||||||
'Esta conta já não existe. Verifique o email ou crie uma nova conta.',
|
AuthStrings.accountNoLongerExists,
|
||||||
);
|
);
|
||||||
} on AuthException catch (e) {
|
} on AuthException catch (e) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
@@ -146,11 +148,11 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
showPillSnackBar(
|
showPillSnackBar(
|
||||||
context,
|
context,
|
||||||
'Tempo esgotado. Verifique a sua ligação e tente novamente.',
|
AuthStrings.timeoutError,
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
showPillSnackBar(context, 'Erro: $e');
|
showPillSnackBar(context, AuthStrings.genericError(e));
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) setState(() => _loading = false);
|
if (mounted) setState(() => _loading = false);
|
||||||
}
|
}
|
||||||
@@ -159,14 +161,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 palavra-passe incorretos.';
|
return AuthStrings.invalidCredentials;
|
||||||
case 'user_not_found':
|
case 'user_not_found':
|
||||||
return 'Utilizador não encontrado.';
|
return AuthStrings.userNotFound;
|
||||||
case 'email_exists':
|
case 'email_exists':
|
||||||
case 'user_already_exists':
|
case 'user_already_exists':
|
||||||
return 'Este email já está em uso.';
|
return AuthStrings.emailAlreadyInUse;
|
||||||
case 'weak_password':
|
case 'weak_password':
|
||||||
return 'Palavra-passe fraca. Utilize pelo menos 6 caracteres.';
|
return AuthStrings.weakPassword;
|
||||||
default:
|
default:
|
||||||
return e.message;
|
return e.message;
|
||||||
}
|
}
|
||||||
@@ -178,7 +180,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
body: Stack(
|
body: Stack(
|
||||||
clipBehavior: Clip.none,
|
clipBehavior: Clip.none,
|
||||||
children: [
|
children: [
|
||||||
Positioned.fill(child: Container(color: const Color(0xFFFAFAF7))),
|
Positioned.fill(child: Container(color: AppColors.background)),
|
||||||
const LiquidWavesBackground(),
|
const LiquidWavesBackground(),
|
||||||
SafeArea(
|
SafeArea(
|
||||||
child: LayoutBuilder(
|
child: LayoutBuilder(
|
||||||
@@ -195,7 +197,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
children: [
|
children: [
|
||||||
const FadeSlideIn(
|
const FadeSlideIn(
|
||||||
child: Text(
|
child: Text(
|
||||||
'Check-Teeth Kids',
|
AuthStrings.appTitle,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 26,
|
fontSize: 26,
|
||||||
@@ -210,7 +212,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
FadeSlideIn(
|
FadeSlideIn(
|
||||||
delay: const Duration(milliseconds: 80),
|
delay: const Duration(milliseconds: 80),
|
||||||
child: Text(
|
child: Text(
|
||||||
'Deteção e Prevenção da Má Oclusão Infantil',
|
AuthStrings.appSubtitle,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 13.5,
|
fontSize: 13.5,
|
||||||
@@ -279,14 +281,14 @@ class _AuthTabSwitch extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _AuthTab(
|
child: _AuthTab(
|
||||||
label: 'Entrar',
|
label: AuthStrings.login,
|
||||||
selected: isLogin,
|
selected: isLogin,
|
||||||
onTap: () => onChanged(true),
|
onTap: () => onChanged(true),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _AuthTab(
|
child: _AuthTab(
|
||||||
label: 'Criar Conta',
|
label: AuthStrings.createAccount,
|
||||||
selected: !isLogin,
|
selected: !isLogin,
|
||||||
onTap: () => onChanged(false),
|
onTap: () => onChanged(false),
|
||||||
),
|
),
|
||||||
@@ -376,17 +378,17 @@ class _AuthForm extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
_AuthTextField(
|
_AuthTextField(
|
||||||
controller: nameController,
|
controller: nameController,
|
||||||
hintText: 'Introduza o seu nome',
|
hintText: AuthStrings.nameHint,
|
||||||
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 'Indique o seu nome';
|
if (value.isEmpty) return AuthStrings.nameRequired;
|
||||||
if (value.length < 2) return 'Nome muito curto';
|
if (value.length < 2) return AuthStrings.nameTooShort;
|
||||||
if (!_namePattern.hasMatch(value)) {
|
if (!_namePattern.hasMatch(value)) {
|
||||||
return 'O nome não pode conter números';
|
return AuthStrings.nameNoNumbers;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
@@ -398,28 +400,28 @@ class _AuthForm extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
_AuthTextField(
|
_AuthTextField(
|
||||||
controller: emailController,
|
controller: emailController,
|
||||||
hintText: 'Introduza o seu email',
|
hintText: AuthStrings.emailHint,
|
||||||
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 'Indique o seu email';
|
if (value.isEmpty) return AuthStrings.emailRequired;
|
||||||
if (!value.contains('@')) return 'Email inválido';
|
if (!value.contains('@')) return AuthStrings.emailInvalid;
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
_AuthTextField(
|
_AuthTextField(
|
||||||
controller: passwordController,
|
controller: passwordController,
|
||||||
hintText: 'Introduza a sua palavra-passe',
|
hintText: AuthStrings.passwordHint,
|
||||||
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 'Indique a sua palavra-passe';
|
if (value.isEmpty) return AuthStrings.passwordRequired;
|
||||||
if (value.length < 6) return 'Mínimo de 6 caracteres';
|
if (value.length < 6) return AuthStrings.passwordTooShort;
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -471,7 +473,7 @@ class _AuthForm extends StatelessWidget {
|
|||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Text(isLogin ? 'Entrar' : 'Criar Conta'),
|
Text(isLogin ? AuthStrings.login : AuthStrings.createAccount),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
const Icon(Icons.arrow_forward_rounded, size: 18),
|
const Icon(Icons.arrow_forward_rounded, size: 18),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'colors/app_colors.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||||
@@ -17,7 +18,8 @@ import 'screens/video_screen.dart';
|
|||||||
import 'watched_videos_prefs.dart';
|
import 'watched_videos_prefs.dart';
|
||||||
import 'widgets/animated_nav_icon.dart';
|
import 'widgets/animated_nav_icon.dart';
|
||||||
import 'widgets/app_dialogs.dart';
|
import 'widgets/app_dialogs.dart';
|
||||||
import 'widgets/app_gradients.dart';
|
import 'colors/app_gradients.dart';
|
||||||
|
import 'strings/home_strings.dart';
|
||||||
import 'widgets/entrance.dart';
|
import 'widgets/entrance.dart';
|
||||||
import 'widgets/liquid_waves_background.dart';
|
import 'widgets/liquid_waves_background.dart';
|
||||||
import 'widgets/name_input_formatter.dart';
|
import 'widgets/name_input_formatter.dart';
|
||||||
@@ -58,7 +60,7 @@ class LoggedHomeScreen extends StatefulWidget {
|
|||||||
|
|
||||||
class _LoggedHomeScreenState extends State<LoggedHomeScreen>
|
class _LoggedHomeScreenState extends State<LoggedHomeScreen>
|
||||||
with SingleTickerProviderStateMixin {
|
with SingleTickerProviderStateMixin {
|
||||||
static const Color _teal = Color(0xFF2F9E94);
|
static const Color _teal = AppColors.teal;
|
||||||
static const String _kPendingQuizScopeKey = 'pending_quiz_scope_v1';
|
static const String _kPendingQuizScopeKey = 'pending_quiz_scope_v1';
|
||||||
|
|
||||||
static const double _collapsedAppBarHeight = kToolbarHeight;
|
static const double _collapsedAppBarHeight = kToolbarHeight;
|
||||||
@@ -77,7 +79,7 @@ class _LoggedHomeScreenState extends State<LoggedHomeScreen>
|
|||||||
bool _brushingDailyLimitReached = false;
|
bool _brushingDailyLimitReached = false;
|
||||||
int? _watchedVideoCount;
|
int? _watchedVideoCount;
|
||||||
|
|
||||||
String _cachedUserName = 'Sem nome';
|
String _cachedUserName = HomeStrings.noName;
|
||||||
String? _cachedPhotoUrl;
|
String? _cachedPhotoUrl;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -268,9 +270,9 @@ class _LoggedHomeScreenState extends State<LoggedHomeScreen>
|
|||||||
|
|
||||||
String _greeting() {
|
String _greeting() {
|
||||||
final hour = DateTime.now().hour;
|
final hour = DateTime.now().hour;
|
||||||
if (hour < 12) return 'Bom dia';
|
if (hour < 12) return HomeStrings.goodMorning;
|
||||||
if (hour < 18) return 'Boa tarde';
|
if (hour < 18) return HomeStrings.goodAfternoon;
|
||||||
return 'Boa noite';
|
return HomeStrings.goodEvening;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fundo comum (cor sólida + ondas decorativas) atrás de qualquer aba, com
|
/// Fundo comum (cor sólida + ondas decorativas) atrás de qualquer aba, com
|
||||||
@@ -279,7 +281,7 @@ class _LoggedHomeScreenState extends State<LoggedHomeScreen>
|
|||||||
return Stack(
|
return Stack(
|
||||||
clipBehavior: Clip.none,
|
clipBehavior: Clip.none,
|
||||||
children: [
|
children: [
|
||||||
Positioned.fill(child: Container(color: const Color(0xFFFAFAF7))),
|
Positioned.fill(child: Container(color: AppColors.background)),
|
||||||
const LiquidWavesBackground(),
|
const LiquidWavesBackground(),
|
||||||
SafeArea(
|
SafeArea(
|
||||||
top: false,
|
top: false,
|
||||||
@@ -303,7 +305,7 @@ class _LoggedHomeScreenState extends State<LoggedHomeScreen>
|
|||||||
HapticFeedback.selectionClick();
|
HapticFeedback.selectionClick();
|
||||||
setState(() => _index = i);
|
setState(() => _index = i);
|
||||||
},
|
},
|
||||||
backgroundColor: const Color(0xFFFAFAF7),
|
backgroundColor: AppColors.background,
|
||||||
selectedItemColor: _teal,
|
selectedItemColor: _teal,
|
||||||
unselectedItemColor: Colors.black54,
|
unselectedItemColor: Colors.black54,
|
||||||
type: BottomNavigationBarType.fixed,
|
type: BottomNavigationBarType.fixed,
|
||||||
@@ -313,21 +315,21 @@ class _LoggedHomeScreenState extends State<LoggedHomeScreen>
|
|||||||
icon: Icons.home_rounded,
|
icon: Icons.home_rounded,
|
||||||
selected: _index == 0,
|
selected: _index == 0,
|
||||||
),
|
),
|
||||||
label: 'Início',
|
label: HomeStrings.navHome,
|
||||||
),
|
),
|
||||||
BottomNavigationBarItem(
|
BottomNavigationBarItem(
|
||||||
icon: AnimatedNavIcon(
|
icon: AnimatedNavIcon(
|
||||||
icon: Icons.person_rounded,
|
icon: Icons.person_rounded,
|
||||||
selected: _index == 1,
|
selected: _index == 1,
|
||||||
),
|
),
|
||||||
label: 'Perfil',
|
label: HomeStrings.navProfile,
|
||||||
),
|
),
|
||||||
BottomNavigationBarItem(
|
BottomNavigationBarItem(
|
||||||
icon: AnimatedNavIcon(
|
icon: AnimatedNavIcon(
|
||||||
icon: Icons.settings_rounded,
|
icon: Icons.settings_rounded,
|
||||||
selected: _index == 2,
|
selected: _index == 2,
|
||||||
),
|
),
|
||||||
label: 'Ajustes',
|
label: HomeStrings.navSettings,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@@ -415,13 +417,13 @@ class _LoggedHomeScreenState extends State<LoggedHomeScreen>
|
|||||||
// quando o número de perguntas muda.
|
// quando o número de perguntas muda.
|
||||||
value: hasScore ? result.signs : null,
|
value: hasScore ? result.signs : null,
|
||||||
max: hasScore ? kSignsMax : null,
|
max: hasScore ? kSignsMax : null,
|
||||||
label: 'Sinais de má\noclusão',
|
label: HomeStrings.signsGaugeLabel,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 18),
|
const SizedBox(width: 18),
|
||||||
_RiskArcGauge(
|
_RiskArcGauge(
|
||||||
value: hasScore ? result.factors : null,
|
value: hasScore ? result.factors : null,
|
||||||
max: hasScore ? kFactorsMax : null,
|
max: hasScore ? kFactorsMax : null,
|
||||||
label: 'Fatores de risco',
|
label: HomeStrings.factorsGaugeLabel,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -510,7 +512,7 @@ class _LoggedHomeScreenState extends State<LoggedHomeScreen>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final String title = _index == 1 ? 'Perfil' : 'Configurações';
|
final String title = _index == 1 ? HomeStrings.navProfile : HomeStrings.settingsTitle;
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: PreferredSize(
|
appBar: PreferredSize(
|
||||||
@@ -668,7 +670,7 @@ class _RiskArcGaugePainter extends CustomPainter {
|
|||||||
..strokeCap = StrokeCap.round;
|
..strokeCap = StrokeCap.round;
|
||||||
|
|
||||||
final progressPaint = Paint()
|
final progressPaint = Paint()
|
||||||
..color = const Color(0xFFFF9AD0)
|
..color = AppColors.pinkLight
|
||||||
..style = PaintingStyle.stroke
|
..style = PaintingStyle.stroke
|
||||||
..strokeWidth = strokeWidth
|
..strokeWidth = strokeWidth
|
||||||
..strokeCap = StrokeCap.round;
|
..strokeCap = StrokeCap.round;
|
||||||
@@ -754,7 +756,7 @@ class _InicioTab extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
if (selectedChildName.isNotEmpty) ...[
|
if (selectedChildName.isNotEmpty) ...[
|
||||||
FadeSlideIn(
|
FadeSlideIn(
|
||||||
child: _HomeSectionLabel('Para $selectedChildName'),
|
child: _HomeSectionLabel(HomeStrings.forChild(selectedChildName)),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
],
|
],
|
||||||
@@ -781,7 +783,7 @@ class _InicioTab extends StatelessWidget {
|
|||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
FadeSlideIn(
|
FadeSlideIn(
|
||||||
delay: const Duration(milliseconds: 110),
|
delay: const Duration(milliseconds: 110),
|
||||||
child: const _HomeSectionLabel('Vídeos educativos'),
|
child: const _HomeSectionLabel(HomeStrings.educationalVideos),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
FadeSlideIn(
|
FadeSlideIn(
|
||||||
@@ -806,7 +808,7 @@ class _InicioTab extends StatelessWidget {
|
|||||||
delay: const Duration(milliseconds: 150),
|
delay: const Duration(milliseconds: 150),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
'Mais funcionalidades em breve',
|
HomeStrings.moreFeaturesSoon,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.w700,
|
fontWeight: FontWeight.w700,
|
||||||
fontSize: 12.5,
|
fontSize: 12.5,
|
||||||
@@ -840,7 +842,7 @@ class _InicioTab extends StatelessWidget {
|
|||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
showPillSnackBar(
|
showPillSnackBar(
|
||||||
context,
|
context,
|
||||||
'Já registou as ${BrushingPrefs.maxPerDay} escovagens de hoje!',
|
HomeStrings.brushingLimitReached(BrushingPrefs.maxPerDay),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -848,7 +850,7 @@ class _InicioTab extends StatelessWidget {
|
|||||||
await BrushingPrefs.logToday(scope);
|
await BrushingPrefs.logToday(scope);
|
||||||
await state?.refreshStats();
|
await state?.refreshStats();
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
showPillSnackBar(context, 'Escovagem registada!');
|
showPillSnackBar(context, HomeStrings.brushingLogged);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -945,11 +947,11 @@ class _StatsRow extends StatelessWidget {
|
|||||||
scale: 0.97,
|
scale: 0.97,
|
||||||
child: _StatCard(
|
child: _StatCard(
|
||||||
icon: Icons.brush_rounded,
|
icon: Icons.brush_rounded,
|
||||||
iconColor: const Color(0xFFFF55A7),
|
iconColor: AppColors.pink,
|
||||||
value: brushingCount == null
|
value: brushingCount == null
|
||||||
? '--'
|
? '--'
|
||||||
: '$brushingCount/$weeklyGoal',
|
: '$brushingCount/$weeklyGoal',
|
||||||
label: 'Escovagens esta semana',
|
label: HomeStrings.brushingThisWeek,
|
||||||
done: brushedToday,
|
done: brushedToday,
|
||||||
onTap: onTapBrushing,
|
onTap: onTapBrushing,
|
||||||
),
|
),
|
||||||
@@ -959,9 +961,9 @@ class _StatsRow extends StatelessWidget {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: _StatCard(
|
child: _StatCard(
|
||||||
icon: Icons.movie_filter_rounded,
|
icon: Icons.movie_filter_rounded,
|
||||||
iconColor: const Color(0xFF8E7CC3),
|
iconColor: AppColors.purple,
|
||||||
value: watchedCount == null ? '--' : '$watchedCount',
|
value: watchedCount == null ? '--' : '$watchedCount',
|
||||||
label: 'Episódios completos',
|
label: HomeStrings.completedEpisodes,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -1022,7 +1024,7 @@ class _StatCard extends StatelessWidget {
|
|||||||
width: 16,
|
width: 16,
|
||||||
height: 16,
|
height: 16,
|
||||||
decoration: const BoxDecoration(
|
decoration: const BoxDecoration(
|
||||||
color: Color(0xFF2F9E94),
|
color: AppColors.teal,
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
),
|
),
|
||||||
child: const Icon(
|
child: const Icon(
|
||||||
@@ -1067,7 +1069,7 @@ Future<Map<String, dynamic>?> _createChildViaSheet(
|
|||||||
context: context,
|
context: context,
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
showDragHandle: true,
|
showDragHandle: true,
|
||||||
backgroundColor: const Color(0xFFFFE6F1),
|
backgroundColor: AppColors.pinkBackground,
|
||||||
shape: const RoundedRectangleBorder(
|
shape: const RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
|
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
|
||||||
),
|
),
|
||||||
@@ -1084,9 +1086,18 @@ Future<Map<String, dynamic>?> _createChildViaSheet(
|
|||||||
.select()
|
.select()
|
||||||
.single();
|
.single();
|
||||||
return inserted;
|
return inserted;
|
||||||
|
} on PostgrestException catch (e) {
|
||||||
|
if (!context.mounted) return null;
|
||||||
|
showPillSnackBar(
|
||||||
|
context,
|
||||||
|
e.code == '23505'
|
||||||
|
? HomeStrings.childCodeAlreadyInUse
|
||||||
|
: HomeStrings.errorAddingChild(e),
|
||||||
|
);
|
||||||
|
return null;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!context.mounted) return null;
|
if (!context.mounted) return null;
|
||||||
showPillSnackBar(context, 'Erro ao adicionar criança: $e');
|
showPillSnackBar(context, HomeStrings.errorAddingChild(e));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1097,9 +1108,9 @@ Future<Map<String, dynamic>?> _requireFirstChild(
|
|||||||
) async {
|
) async {
|
||||||
final proceed = await showConfirmDialog(
|
final proceed = await showConfirmDialog(
|
||||||
context,
|
context,
|
||||||
title: 'Registe uma criança',
|
title: HomeStrings.registerAChild,
|
||||||
message: 'Antes de iniciar o quiz, adicione uma criança ao seu perfil.',
|
message: HomeStrings.registerAChildMessage,
|
||||||
confirmLabel: 'Adicionar criança',
|
confirmLabel: HomeStrings.addChild,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (proceed != true) return null;
|
if (proceed != true) return null;
|
||||||
@@ -1111,12 +1122,12 @@ Future<Map<String, dynamic>?> _pickChildSheet(
|
|||||||
BuildContext context,
|
BuildContext context,
|
||||||
List<Map<String, dynamic>> children,
|
List<Map<String, dynamic>> children,
|
||||||
) {
|
) {
|
||||||
const Color teal = Color(0xFF2F9E94);
|
const Color teal = AppColors.teal;
|
||||||
return showModalBottomSheet<Map<String, dynamic>?>(
|
return showModalBottomSheet<Map<String, dynamic>?>(
|
||||||
context: context,
|
context: context,
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
showDragHandle: true,
|
showDragHandle: true,
|
||||||
backgroundColor: const Color(0xFFFFE6F1),
|
backgroundColor: AppColors.pinkBackground,
|
||||||
shape: const RoundedRectangleBorder(
|
shape: const RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
|
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
|
||||||
),
|
),
|
||||||
@@ -1132,12 +1143,12 @@ Future<Map<String, dynamic>?> _pickChildSheet(
|
|||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
const Text(
|
const Text(
|
||||||
'Para qual criança é o quiz?',
|
HomeStrings.whichChildIsTheQuizFor,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontWeight: FontWeight.w900,
|
fontWeight: FontWeight.w900,
|
||||||
color: Color(0xFFFF55A7),
|
color: AppColors.pink,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 14),
|
const SizedBox(height: 14),
|
||||||
@@ -1149,7 +1160,7 @@ Future<Map<String, dynamic>?> _pickChildSheet(
|
|||||||
final name = (c['name'] ?? '').toString();
|
final name = (c['name'] ?? '').toString();
|
||||||
final age = _childAge(c);
|
final age = _childAge(c);
|
||||||
final label = age != null
|
final label = age != null
|
||||||
? '$name • $age anos'
|
? HomeStrings.childNameWithAge(name, age)
|
||||||
: name;
|
: name;
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 10),
|
padding: const EdgeInsets.only(bottom: 10),
|
||||||
@@ -1194,7 +1205,7 @@ Future<Map<String, dynamic>?> _pickChildSheet(
|
|||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.of(ctx).pop(null),
|
onPressed: () => Navigator.of(ctx).pop(null),
|
||||||
child: const Text('Cancelar'),
|
child: const Text(HomeStrings.cancel),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -1214,7 +1225,7 @@ class _HeroQuizCard extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Material(
|
return Material(
|
||||||
elevation: 18,
|
elevation: 18,
|
||||||
shadowColor: const Color(0xFFFF55A7).withValues(alpha: 0.45),
|
shadowColor: AppColors.pink.withValues(alpha: 0.45),
|
||||||
borderRadius: BorderRadius.circular(28),
|
borderRadius: BorderRadius.circular(28),
|
||||||
clipBehavior: Clip.antiAlias,
|
clipBehavior: Clip.antiAlias,
|
||||||
color: Colors.transparent,
|
color: Colors.transparent,
|
||||||
@@ -1300,7 +1311,7 @@ class _HeroQuizCard extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
SizedBox(width: 4),
|
SizedBox(width: 4),
|
||||||
Text(
|
Text(
|
||||||
'Avaliação gratuita',
|
HomeStrings.freeAssessmentBadge,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontWeight: FontWeight.w800,
|
fontWeight: FontWeight.w800,
|
||||||
@@ -1328,7 +1339,7 @@ class _HeroQuizCard extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 14),
|
const SizedBox(height: 14),
|
||||||
const Text(
|
const Text(
|
||||||
'Avaliação de má oclusão dentária',
|
HomeStrings.assessmentTitle,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.w900,
|
fontWeight: FontWeight.w900,
|
||||||
fontSize: 22,
|
fontSize: 22,
|
||||||
@@ -1338,7 +1349,7 @@ class _HeroQuizCard extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 5),
|
const SizedBox(height: 5),
|
||||||
Text(
|
Text(
|
||||||
'29 perguntas rápidas · menos de 3 minutos',
|
HomeStrings.assessmentSubtitle,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.white.withValues(alpha: 0.92),
|
color: Colors.white.withValues(alpha: 0.92),
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
@@ -1356,7 +1367,7 @@ class _HeroQuizCard extends StatelessWidget {
|
|||||||
child: FilledButton.icon(
|
child: FilledButton.icon(
|
||||||
style: FilledButton.styleFrom(
|
style: FilledButton.styleFrom(
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: Colors.white,
|
||||||
foregroundColor: const Color(0xFFFF55A7),
|
foregroundColor: AppColors.pink,
|
||||||
shape: const StadiumBorder(),
|
shape: const StadiumBorder(),
|
||||||
elevation: 6,
|
elevation: 6,
|
||||||
shadowColor: Colors.black.withValues(alpha: 0.25),
|
shadowColor: Colors.black.withValues(alpha: 0.25),
|
||||||
@@ -1367,7 +1378,7 @@ class _HeroQuizCard extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
onPressed: onStartQuiz,
|
onPressed: onStartQuiz,
|
||||||
icon: const Icon(Icons.play_arrow_rounded),
|
icon: const Icon(Icons.play_arrow_rounded),
|
||||||
label: const Text('Iniciar Quiz'),
|
label: const Text(HomeStrings.startQuiz),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -1396,7 +1407,7 @@ class _VideoLibraryCard extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Material(
|
return Material(
|
||||||
elevation: 14,
|
elevation: 14,
|
||||||
shadowColor: const Color(0xFF2F9E94).withValues(alpha: 0.38),
|
shadowColor: AppColors.teal.withValues(alpha: 0.38),
|
||||||
borderRadius: BorderRadius.circular(28),
|
borderRadius: BorderRadius.circular(28),
|
||||||
clipBehavior: Clip.antiAlias,
|
clipBehavior: Clip.antiAlias,
|
||||||
color: Colors.transparent,
|
color: Colors.transparent,
|
||||||
@@ -1465,7 +1476,7 @@ class _VideoLibraryCard extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
SizedBox(width: 4),
|
SizedBox(width: 4),
|
||||||
Text(
|
Text(
|
||||||
'Biblioteca de vídeos',
|
HomeStrings.videoLibraryBadge,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontWeight: FontWeight.w800,
|
fontWeight: FontWeight.w800,
|
||||||
@@ -1477,7 +1488,7 @@ class _VideoLibraryCard extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 14),
|
const SizedBox(height: 14),
|
||||||
const Text(
|
const Text(
|
||||||
'Vídeos educativos',
|
HomeStrings.educationalVideos,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.w900,
|
fontWeight: FontWeight.w900,
|
||||||
fontSize: 22,
|
fontSize: 22,
|
||||||
@@ -1488,8 +1499,11 @@ class _VideoLibraryCard extends StatelessWidget {
|
|||||||
const SizedBox(height: 5),
|
const SizedBox(height: 5),
|
||||||
Text(
|
Text(
|
||||||
watchedCount > 0
|
watchedCount > 0
|
||||||
? '$watchedCount episódio${watchedCount == 1 ? '' : 's'} completo${watchedCount == 1 ? '' : 's'} · ${videoList.length} no total'
|
? HomeStrings.watchedEpisodesSummary(
|
||||||
: '${videoList.length} episódios sobre saúde oral para toda a família',
|
watchedCount,
|
||||||
|
videoList.length,
|
||||||
|
)
|
||||||
|
: HomeStrings.allEpisodesSummary(videoList.length),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.white.withValues(alpha: 0.92),
|
color: Colors.white.withValues(alpha: 0.92),
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
@@ -1503,7 +1517,7 @@ class _VideoLibraryCard extends StatelessWidget {
|
|||||||
child: FilledButton.icon(
|
child: FilledButton.icon(
|
||||||
style: FilledButton.styleFrom(
|
style: FilledButton.styleFrom(
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: Colors.white,
|
||||||
foregroundColor: const Color(0xFF2F9E94),
|
foregroundColor: AppColors.teal,
|
||||||
shape: const StadiumBorder(),
|
shape: const StadiumBorder(),
|
||||||
textStyle: const TextStyle(
|
textStyle: const TextStyle(
|
||||||
fontWeight: FontWeight.w800,
|
fontWeight: FontWeight.w800,
|
||||||
@@ -1512,7 +1526,7 @@ class _VideoLibraryCard extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
onPressed: onTap,
|
onPressed: onTap,
|
||||||
icon: const Icon(Icons.video_library_rounded),
|
icon: const Icon(Icons.video_library_rounded),
|
||||||
label: const Text('Ver vídeos'),
|
label: const Text(HomeStrings.watchVideos),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -1597,7 +1611,7 @@ class _PerfilTabState extends State<_PerfilTab> {
|
|||||||
final source = await showModalBottomSheet<ImageSource>(
|
final source = await showModalBottomSheet<ImageSource>(
|
||||||
context: context,
|
context: context,
|
||||||
showDragHandle: true,
|
showDragHandle: true,
|
||||||
backgroundColor: const Color(0xFFFFE6F1),
|
backgroundColor: AppColors.pinkBackground,
|
||||||
shape: const RoundedRectangleBorder(
|
shape: const RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
|
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
|
||||||
),
|
),
|
||||||
@@ -1610,12 +1624,12 @@ class _PerfilTabState extends State<_PerfilTab> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
const Text(
|
const Text(
|
||||||
'Foto de perfil',
|
HomeStrings.profilePhoto,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontWeight: FontWeight.w900,
|
fontWeight: FontWeight.w900,
|
||||||
color: Color(0xFFFF55A7),
|
color: AppColors.pink,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 14),
|
const SizedBox(height: 14),
|
||||||
@@ -1638,7 +1652,7 @@ class _PerfilTabState extends State<_PerfilTab> {
|
|||||||
),
|
),
|
||||||
onPressed: () =>
|
onPressed: () =>
|
||||||
Navigator.of(ctx).pop(ImageSource.camera),
|
Navigator.of(ctx).pop(ImageSource.camera),
|
||||||
child: const Text('Câmara'),
|
child: const Text(HomeStrings.camera),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -1663,7 +1677,7 @@ class _PerfilTabState extends State<_PerfilTab> {
|
|||||||
),
|
),
|
||||||
onPressed: () =>
|
onPressed: () =>
|
||||||
Navigator.of(ctx).pop(ImageSource.gallery),
|
Navigator.of(ctx).pop(ImageSource.gallery),
|
||||||
child: const Text('Galeria'),
|
child: const Text(HomeStrings.gallery),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -1673,10 +1687,10 @@ class _PerfilTabState extends State<_PerfilTab> {
|
|||||||
height: 42,
|
height: 42,
|
||||||
child: TextButton(
|
child: TextButton(
|
||||||
style: TextButton.styleFrom(
|
style: TextButton.styleFrom(
|
||||||
foregroundColor: const Color(0xFF2F9E94),
|
foregroundColor: AppColors.teal,
|
||||||
),
|
),
|
||||||
onPressed: () => Navigator.of(ctx).pop(),
|
onPressed: () => Navigator.of(ctx).pop(),
|
||||||
child: const Text('Cancelar'),
|
child: const Text(HomeStrings.cancel),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -1716,7 +1730,7 @@ class _PerfilTabState extends State<_PerfilTab> {
|
|||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
showPillSnackBar(context, 'Erro ao enviar foto: $e');
|
showPillSnackBar(context, HomeStrings.errorUploadingPhoto(e));
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) setState(() => _updatingPhoto = false);
|
if (mounted) setState(() => _updatingPhoto = false);
|
||||||
}
|
}
|
||||||
@@ -1729,11 +1743,10 @@ class _PerfilTabState extends State<_PerfilTab> {
|
|||||||
}) async {
|
}) async {
|
||||||
final confirmed = await showConfirmDialog(
|
final confirmed = await showConfirmDialog(
|
||||||
context,
|
context,
|
||||||
title: 'Remover criança',
|
title: HomeStrings.removeChild,
|
||||||
message:
|
message: HomeStrings.removeChildConfirmMessage(childName),
|
||||||
'Tem a certeza que quer remover "$childName"? Esta ação não pode ser desfeita.',
|
confirmLabel: HomeStrings.remove,
|
||||||
confirmLabel: 'Remover',
|
confirmColor: AppColors.pink,
|
||||||
confirmColor: const Color(0xFFFF55A7),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (confirmed != true) return;
|
if (confirmed != true) return;
|
||||||
@@ -1747,15 +1760,15 @@ class _PerfilTabState extends State<_PerfilTab> {
|
|||||||
.select('id');
|
.select('id');
|
||||||
|
|
||||||
if (deleted.isEmpty) {
|
if (deleted.isEmpty) {
|
||||||
throw StateError('Sem permissão para remover esta criança.');
|
throw StateError(HomeStrings.noPermissionToRemoveChild);
|
||||||
}
|
}
|
||||||
|
|
||||||
widget.onChildSelected(0, null, null);
|
widget.onChildSelected(0, null, null);
|
||||||
await _loadPerfilData();
|
await _loadPerfilData();
|
||||||
if (context.mounted) showPillSnackBar(context, 'Criança removida');
|
if (context.mounted) showPillSnackBar(context, HomeStrings.childRemoved);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
showPillSnackBar(context, 'Erro ao remover: $e');
|
showPillSnackBar(context, HomeStrings.errorRemovingChild(e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1773,15 +1786,15 @@ class _PerfilTabState extends State<_PerfilTab> {
|
|||||||
context: context,
|
context: context,
|
||||||
builder: (ctx) {
|
builder: (ctx) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
backgroundColor: const Color(0xFFFFE6F1),
|
backgroundColor: AppColors.pinkBackground,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
),
|
),
|
||||||
title: Text(
|
title: Text(
|
||||||
'Meta semanal de $childName',
|
HomeStrings.weeklyGoalOf(childName),
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontWeight: FontWeight.w900,
|
fontWeight: FontWeight.w900,
|
||||||
color: Color(0xFFFF55A7),
|
color: AppColors.pink,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
content: TextField(
|
content: TextField(
|
||||||
@@ -1789,14 +1802,14 @@ class _PerfilTabState extends State<_PerfilTab> {
|
|||||||
keyboardType: TextInputType.number,
|
keyboardType: TextInputType.number,
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
labelText: 'Escovagens por semana',
|
labelText: HomeStrings.brushingsPerWeek,
|
||||||
helperText: 'Entre 1 e 21 (até 3 por dia)',
|
helperText: HomeStrings.brushingGoalRange,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.of(ctx).pop(),
|
onPressed: () => Navigator.of(ctx).pop(),
|
||||||
child: const Text('Cancelar'),
|
child: const Text(HomeStrings.cancel),
|
||||||
),
|
),
|
||||||
ClipRRect(
|
ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(999),
|
borderRadius: BorderRadius.circular(999),
|
||||||
@@ -1813,7 +1826,7 @@ class _PerfilTabState extends State<_PerfilTab> {
|
|||||||
if (value == null || value < 1 || value > 21) return;
|
if (value == null || value < 1 || value > 21) return;
|
||||||
Navigator.of(ctx).pop(value);
|
Navigator.of(ctx).pop(value);
|
||||||
},
|
},
|
||||||
child: const Text('Guardar'),
|
child: const Text(HomeStrings.save),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -1835,7 +1848,7 @@ class _PerfilTabState extends State<_PerfilTab> {
|
|||||||
context: context,
|
context: context,
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
showDragHandle: true,
|
showDragHandle: true,
|
||||||
backgroundColor: const Color(0xFFFFE6F1),
|
backgroundColor: AppColors.pinkBackground,
|
||||||
shape: const RoundedRectangleBorder(
|
shape: const RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
|
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
|
||||||
),
|
),
|
||||||
@@ -1857,7 +1870,9 @@ class _PerfilTabState extends State<_PerfilTab> {
|
|||||||
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
await _loadPerfilData();
|
await _loadPerfilData();
|
||||||
if (context.mounted) showPillSnackBar(context, 'Criança adicionada');
|
if (context.mounted) {
|
||||||
|
showPillSnackBar(context, HomeStrings.childAdded);
|
||||||
|
}
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() => _addingChild = false);
|
setState(() => _addingChild = false);
|
||||||
@@ -1868,9 +1883,9 @@ class _PerfilTabState extends State<_PerfilTab> {
|
|||||||
final addMore = await showConfirmDialog(
|
final addMore = await showConfirmDialog(
|
||||||
// ignore: use_build_context_synchronously
|
// ignore: use_build_context_synchronously
|
||||||
context,
|
context,
|
||||||
title: 'Adicionar outra criança?',
|
title: HomeStrings.addAnotherChildQuestion,
|
||||||
cancelLabel: 'Agora não',
|
cancelLabel: HomeStrings.notNow,
|
||||||
confirmLabel: 'Adicionar outra',
|
confirmLabel: HomeStrings.addAnother,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
@@ -1886,11 +1901,11 @@ class _PerfilTabState extends State<_PerfilTab> {
|
|||||||
if (!mounted || !context.mounted) return;
|
if (!mounted || !context.mounted) return;
|
||||||
showPillSnackBar(
|
showPillSnackBar(
|
||||||
context,
|
context,
|
||||||
'Tempo esgotado ao adicionar. Tente novamente.',
|
HomeStrings.timeoutAdding,
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!mounted || !context.mounted) return;
|
if (!mounted || !context.mounted) return;
|
||||||
showPillSnackBar(context, 'Erro ao adicionar: $e');
|
showPillSnackBar(context, HomeStrings.errorAdding(e));
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) setState(() => _addingChild = false);
|
if (mounted) setState(() => _addingChild = false);
|
||||||
}
|
}
|
||||||
@@ -1902,7 +1917,7 @@ class _PerfilTabState extends State<_PerfilTab> {
|
|||||||
final uid = (user?.id ?? '').trim();
|
final uid = (user?.id ?? '').trim();
|
||||||
final name = (user?.userMetadata?['name'] ?? '').toString().trim();
|
final name = (user?.userMetadata?['name'] ?? '').toString().trim();
|
||||||
final email = (user?.email ?? '').trim();
|
final email = (user?.email ?? '').trim();
|
||||||
final shownName = name.isNotEmpty ? name : 'Sem nome';
|
final shownName = name.isNotEmpty ? name : HomeStrings.noName;
|
||||||
|
|
||||||
if (uid.isEmpty) {
|
if (uid.isEmpty) {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
@@ -1912,7 +1927,7 @@ class _PerfilTabState extends State<_PerfilTab> {
|
|||||||
return const Center(
|
return const Center(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.only(top: 60),
|
padding: EdgeInsets.only(top: 60),
|
||||||
child: CircularProgressIndicator(color: Color(0xFF2F9E94)),
|
child: CircularProgressIndicator(color: AppColors.teal),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1970,12 +1985,12 @@ class _PerfilTabState extends State<_PerfilTab> {
|
|||||||
width: 76,
|
width: 76,
|
||||||
height: 76,
|
height: 76,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: const Color(0xFFFFE6F1),
|
color: AppColors.pinkBackground,
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: const Color(
|
color: AppColors.teal.withValues(
|
||||||
0xFF2F9E94,
|
alpha: 0.35,
|
||||||
).withValues(alpha: 0.35),
|
),
|
||||||
width: 2,
|
width: 2,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -1992,7 +2007,7 @@ class _PerfilTabState extends State<_PerfilTab> {
|
|||||||
const Icon(
|
const Icon(
|
||||||
Icons.person_rounded,
|
Icons.person_rounded,
|
||||||
size: 42,
|
size: 42,
|
||||||
color: Color(0xFF2F9E94),
|
color: AppColors.teal,
|
||||||
),
|
),
|
||||||
if (_updatingPhoto)
|
if (_updatingPhoto)
|
||||||
Container(
|
Container(
|
||||||
@@ -2020,7 +2035,7 @@ class _PerfilTabState extends State<_PerfilTab> {
|
|||||||
width: 26,
|
width: 26,
|
||||||
height: 26,
|
height: 26,
|
||||||
decoration: const BoxDecoration(
|
decoration: const BoxDecoration(
|
||||||
color: Color(0xFFFF55A7),
|
color: AppColors.pink,
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
),
|
),
|
||||||
child: const Icon(
|
child: const Icon(
|
||||||
@@ -2046,7 +2061,7 @@ class _PerfilTabState extends State<_PerfilTab> {
|
|||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
fontWeight: FontWeight.w900,
|
fontWeight: FontWeight.w900,
|
||||||
color: Color(0xFFFF55A7),
|
color: AppColors.pink,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (profileEmail.isNotEmpty) ...[
|
if (profileEmail.isNotEmpty) ...[
|
||||||
@@ -2078,9 +2093,9 @@ class _PerfilTabState extends State<_PerfilTab> {
|
|||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
const Text(
|
const Text(
|
||||||
'Meus filhos',
|
HomeStrings.myChildren,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Color(0xFF2F9E94),
|
color: AppColors.teal,
|
||||||
fontWeight: FontWeight.w900,
|
fontWeight: FontWeight.w900,
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
),
|
),
|
||||||
@@ -2092,15 +2107,13 @@ class _PerfilTabState extends State<_PerfilTab> {
|
|||||||
vertical: 2,
|
vertical: 2,
|
||||||
),
|
),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: const Color(
|
color: AppColors.teal.withValues(alpha: 0.10),
|
||||||
0xFF2F9E94,
|
|
||||||
).withValues(alpha: 0.10),
|
|
||||||
borderRadius: BorderRadius.circular(999),
|
borderRadius: BorderRadius.circular(999),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
'${children.length}',
|
'${children.length}',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: Color(0xFF2F9E94),
|
color: AppColors.teal,
|
||||||
fontWeight: FontWeight.w900,
|
fontWeight: FontWeight.w900,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
),
|
),
|
||||||
@@ -2126,18 +2139,18 @@ class _PerfilTabState extends State<_PerfilTab> {
|
|||||||
width: 40,
|
width: 40,
|
||||||
height: 40,
|
height: 40,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: const Color(0xFFFFE6F1),
|
color: AppColors.pinkBackground,
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
),
|
),
|
||||||
child: const Icon(
|
child: const Icon(
|
||||||
Icons.child_care_rounded,
|
Icons.child_care_rounded,
|
||||||
color: Color(0xFFFF55A7),
|
color: AppColors.pink,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
'Nenhuma criança adicionada ainda.',
|
HomeStrings.noChildrenYet,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.black.withValues(alpha: 0.62),
|
color: Colors.black.withValues(alpha: 0.62),
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
@@ -2155,14 +2168,20 @@ class _PerfilTabState extends State<_PerfilTab> {
|
|||||||
final childName = (c['name'] ?? '').toString().trim();
|
final childName = (c['name'] ?? '').toString().trim();
|
||||||
final childAge = _childAge(c);
|
final childAge = _childAge(c);
|
||||||
final childGender = (c['gender'] ?? '').toString().trim();
|
final childGender = (c['gender'] ?? '').toString().trim();
|
||||||
|
final childCode = (c['child_code'] ?? '')
|
||||||
|
.toString()
|
||||||
|
.trim();
|
||||||
final scopeId = '${uid}_$childId';
|
final scopeId = '${uid}_$childId';
|
||||||
|
|
||||||
final title = childName.isNotEmpty
|
final title = childName.isNotEmpty
|
||||||
? childName
|
? childName
|
||||||
: 'Criança ${i + 1}';
|
: HomeStrings.childFallbackName(i);
|
||||||
final subtitle = [
|
final subtitle = [
|
||||||
if (childAge != null) 'Idade: $childAge',
|
if (childAge != null) HomeStrings.ageLabel(childAge),
|
||||||
if (childGender.isNotEmpty) 'Género: $childGender',
|
if (childGender.isNotEmpty)
|
||||||
|
HomeStrings.genderLabel(childGender),
|
||||||
|
if (childCode.isNotEmpty)
|
||||||
|
HomeStrings.codeLabel(childCode),
|
||||||
].join(' • ');
|
].join(' • ');
|
||||||
final bool selected = i == selectedIndex;
|
final bool selected = i == selectedIndex;
|
||||||
|
|
||||||
@@ -2183,14 +2202,12 @@ class _PerfilTabState extends State<_PerfilTab> {
|
|||||||
padding: const EdgeInsets.all(14),
|
padding: const EdgeInsets.all(14),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: selected
|
color: selected
|
||||||
? const Color(0xFFFFE6F1)
|
? AppColors.pinkBackground
|
||||||
: Colors.white,
|
: Colors.white,
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: selected
|
color: selected
|
||||||
? const Color(
|
? AppColors.teal.withValues(alpha: 0.45)
|
||||||
0xFF2F9E94,
|
|
||||||
).withValues(alpha: 0.45)
|
|
||||||
: Colors.black.withValues(alpha: 0.10),
|
: Colors.black.withValues(alpha: 0.10),
|
||||||
width: selected ? 1.6 : 1,
|
width: selected ? 1.6 : 1,
|
||||||
),
|
),
|
||||||
@@ -2237,9 +2254,9 @@ class _PerfilTabState extends State<_PerfilTab> {
|
|||||||
vertical: 8,
|
vertical: 8,
|
||||||
),
|
),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: const Color(
|
color: AppColors.teal.withValues(
|
||||||
0xFF2F9E94,
|
alpha: 0.10,
|
||||||
).withValues(alpha: 0.10),
|
),
|
||||||
borderRadius: BorderRadius.circular(
|
borderRadius: BorderRadius.circular(
|
||||||
999,
|
999,
|
||||||
),
|
),
|
||||||
@@ -2248,7 +2265,7 @@ class _PerfilTabState extends State<_PerfilTab> {
|
|||||||
text,
|
text,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontWeight: FontWeight.w900,
|
fontWeight: FontWeight.w900,
|
||||||
color: Color(0xFF2F9E94),
|
color: AppColors.teal,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -2262,9 +2279,9 @@ class _PerfilTabState extends State<_PerfilTab> {
|
|||||||
),
|
),
|
||||||
icon: const Icon(
|
icon: const Icon(
|
||||||
Icons.edit_outlined,
|
Icons.edit_outlined,
|
||||||
color: Color(0xFF2F9E94),
|
color: AppColors.teal,
|
||||||
),
|
),
|
||||||
tooltip: 'Meta semanal de escovagens',
|
tooltip: HomeStrings.weeklyBrushingGoalTooltip,
|
||||||
visualDensity: VisualDensity.compact,
|
visualDensity: VisualDensity.compact,
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
@@ -2275,9 +2292,9 @@ class _PerfilTabState extends State<_PerfilTab> {
|
|||||||
),
|
),
|
||||||
icon: const Icon(
|
icon: const Icon(
|
||||||
Icons.delete_outline_rounded,
|
Icons.delete_outline_rounded,
|
||||||
color: Color(0xFFFF55A7),
|
color: AppColors.pink,
|
||||||
),
|
),
|
||||||
tooltip: 'Remover',
|
tooltip: HomeStrings.remove,
|
||||||
visualDensity: VisualDensity.compact,
|
visualDensity: VisualDensity.compact,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -2310,7 +2327,7 @@ class _PerfilTabState extends State<_PerfilTab> {
|
|||||||
? null
|
? null
|
||||||
: () => _addAnotherChild(context, uid),
|
: () => _addAnotherChild(context, uid),
|
||||||
icon: const Icon(Icons.add_rounded),
|
icon: const Icon(Icons.add_rounded),
|
||||||
label: const Text('Adicionar criança'),
|
label: const Text(HomeStrings.addChild),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -2322,9 +2339,9 @@ class _PerfilTabState extends State<_PerfilTab> {
|
|||||||
height: 46,
|
height: 46,
|
||||||
child: OutlinedButton.icon(
|
child: OutlinedButton.icon(
|
||||||
style: OutlinedButton.styleFrom(
|
style: OutlinedButton.styleFrom(
|
||||||
foregroundColor: const Color(0xFFFF55A7),
|
foregroundColor: AppColors.pink,
|
||||||
side: const BorderSide(
|
side: const BorderSide(
|
||||||
color: Color(0xFFFF55A7),
|
color: AppColors.pink,
|
||||||
width: 1.4,
|
width: 1.4,
|
||||||
),
|
),
|
||||||
shape: const StadiumBorder(),
|
shape: const StadiumBorder(),
|
||||||
@@ -2334,7 +2351,7 @@ class _PerfilTabState extends State<_PerfilTab> {
|
|||||||
await supabase.auth.signOut();
|
await supabase.auth.signOut();
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.logout_rounded),
|
icon: const Icon(Icons.logout_rounded),
|
||||||
label: const Text('Sair'),
|
label: const Text(HomeStrings.signOut),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -2358,6 +2375,7 @@ class _AddChildSheet extends StatefulWidget {
|
|||||||
class _AddChildSheetState extends State<_AddChildSheet> {
|
class _AddChildSheetState extends State<_AddChildSheet> {
|
||||||
final _formKey = GlobalKey<FormState>();
|
final _formKey = GlobalKey<FormState>();
|
||||||
final _nameController = TextEditingController();
|
final _nameController = TextEditingController();
|
||||||
|
final _codeController = TextEditingController();
|
||||||
DateTime? _birthDate;
|
DateTime? _birthDate;
|
||||||
String? _gender;
|
String? _gender;
|
||||||
String? _birthDateError;
|
String? _birthDateError;
|
||||||
@@ -2365,6 +2383,7 @@ class _AddChildSheetState extends State<_AddChildSheet> {
|
|||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_nameController.dispose();
|
_nameController.dispose();
|
||||||
|
_codeController.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2375,9 +2394,9 @@ class _AddChildSheetState extends State<_AddChildSheet> {
|
|||||||
initialDate: _birthDate ?? DateTime(now.year - 5, now.month, now.day),
|
initialDate: _birthDate ?? DateTime(now.year - 5, now.month, now.day),
|
||||||
firstDate: DateTime(now.year - 17, now.month, now.day),
|
firstDate: DateTime(now.year - 17, now.month, now.day),
|
||||||
lastDate: DateTime(now.year - 1, now.month, now.day),
|
lastDate: DateTime(now.year - 1, now.month, now.day),
|
||||||
helpText: 'Data de nascimento',
|
helpText: HomeStrings.birthDate,
|
||||||
cancelText: 'Cancelar',
|
cancelText: HomeStrings.cancel,
|
||||||
confirmText: 'Confirmar',
|
confirmText: HomeStrings.confirm,
|
||||||
locale: const Locale('pt', 'PT'),
|
locale: const Locale('pt', 'PT'),
|
||||||
);
|
);
|
||||||
if (picked == null) return;
|
if (picked == null) return;
|
||||||
@@ -2391,7 +2410,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
|
||||||
? 'Indique a data de nascimento'
|
? HomeStrings.birthDateRequired
|
||||||
: null;
|
: null;
|
||||||
});
|
});
|
||||||
if (!formOk || _birthDate == null) return;
|
if (!formOk || _birthDate == null) return;
|
||||||
@@ -2399,6 +2418,7 @@ class _AddChildSheetState extends State<_AddChildSheet> {
|
|||||||
'name': _nameController.text.trim(),
|
'name': _nameController.text.trim(),
|
||||||
'birth_date': _birthDate!.toIso8601String().split('T').first,
|
'birth_date': _birthDate!.toIso8601String().split('T').first,
|
||||||
'gender': (_gender ?? '').trim(),
|
'gender': (_gender ?? '').trim(),
|
||||||
|
'child_code': _codeController.text.trim(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2413,12 +2433,12 @@ class _AddChildSheetState extends State<_AddChildSheet> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
const Text(
|
const Text(
|
||||||
'Adicionar outra criança',
|
HomeStrings.addAnotherChildTitle,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontWeight: FontWeight.w900,
|
fontWeight: FontWeight.w900,
|
||||||
color: Color(0xFFFF55A7),
|
color: AppColors.pink,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
@@ -2440,24 +2460,40 @@ class _AddChildSheetState extends State<_AddChildSheet> {
|
|||||||
textCapitalization: TextCapitalization.sentences,
|
textCapitalization: TextCapitalization.sentences,
|
||||||
inputFormatters: [CapitalizeFirstLetterFormatter()],
|
inputFormatters: [CapitalizeFirstLetterFormatter()],
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
labelText: 'Nome da criança',
|
labelText: HomeStrings.childName,
|
||||||
),
|
),
|
||||||
validator: (v) {
|
validator: (v) {
|
||||||
final value = (v ?? '').trim();
|
final value = (v ?? '').trim();
|
||||||
if (value.isEmpty) return 'Indique o nome';
|
if (value.isEmpty) return HomeStrings.nameRequired;
|
||||||
if (value.length < 2) return 'Nome muito curto';
|
if (value.length < 2) return HomeStrings.nameTooShort;
|
||||||
if (!_namePattern.hasMatch(value)) {
|
if (!_namePattern.hasMatch(value)) {
|
||||||
return 'O nome não pode conter números';
|
return HomeStrings.nameNoNumbers;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
TextFormField(
|
||||||
|
controller: _codeController,
|
||||||
|
textInputAction: TextInputAction.next,
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
inputFormatters: [
|
||||||
|
FilteringTextInputFormatter.digitsOnly,
|
||||||
|
],
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: HomeStrings.childCode,
|
||||||
|
),
|
||||||
|
validator: (v) {
|
||||||
|
final value = (v ?? '').trim();
|
||||||
|
if (value.isEmpty) return HomeStrings.childCodeRequired;
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
InkWell(
|
InkWell(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
onTap: _pickBirthDate,
|
onTap: _pickBirthDate,
|
||||||
child: InputDecorator(
|
child: InputDecorator(
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'Data de nascimento',
|
labelText: HomeStrings.birthDate,
|
||||||
errorText: _birthDateError,
|
errorText: _birthDateError,
|
||||||
suffixIcon: const Icon(
|
suffixIcon: const Icon(
|
||||||
Icons.calendar_today_rounded,
|
Icons.calendar_today_rounded,
|
||||||
@@ -2466,7 +2502,7 @@ class _AddChildSheetState extends State<_AddChildSheet> {
|
|||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
_birthDate == null
|
_birthDate == null
|
||||||
? 'Selecione a data'
|
? HomeStrings.selectDate
|
||||||
: '${_birthDate!.day.toString().padLeft(2, '0')}/'
|
: '${_birthDate!.day.toString().padLeft(2, '0')}/'
|
||||||
'${_birthDate!.month.toString().padLeft(2, '0')}/'
|
'${_birthDate!.month.toString().padLeft(2, '0')}/'
|
||||||
'${_birthDate!.year}',
|
'${_birthDate!.year}',
|
||||||
@@ -2482,20 +2518,20 @@ class _AddChildSheetState extends State<_AddChildSheet> {
|
|||||||
initialValue: _gender,
|
initialValue: _gender,
|
||||||
items: const [
|
items: const [
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
value: 'Masculino',
|
value: HomeStrings.male,
|
||||||
child: Text('Masculino'),
|
child: Text(HomeStrings.male),
|
||||||
),
|
),
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
value: 'Feminino',
|
value: HomeStrings.female,
|
||||||
child: Text('Feminino'),
|
child: Text(HomeStrings.female),
|
||||||
),
|
),
|
||||||
DropdownMenuItem(value: 'Outro', child: Text('Outro')),
|
DropdownMenuItem(value: HomeStrings.other, child: Text(HomeStrings.other)),
|
||||||
],
|
],
|
||||||
onChanged: (v) => setState(() => _gender = v),
|
onChanged: (v) => setState(() => _gender = v),
|
||||||
decoration: const InputDecoration(labelText: 'Género'),
|
decoration: const InputDecoration(labelText: HomeStrings.gender),
|
||||||
validator: (v) {
|
validator: (v) {
|
||||||
if (v == null || v.trim().isEmpty) {
|
if (v == null || v.trim().isEmpty) {
|
||||||
return 'Selecione o género';
|
return HomeStrings.genderRequired;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
@@ -2512,7 +2548,7 @@ class _AddChildSheetState extends State<_AddChildSheet> {
|
|||||||
height: 44,
|
height: 44,
|
||||||
child: TextButton(
|
child: TextButton(
|
||||||
onPressed: () => Navigator.of(context).pop(null),
|
onPressed: () => Navigator.of(context).pop(null),
|
||||||
child: const Text('Cancelar'),
|
child: const Text(HomeStrings.cancel),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -2537,7 +2573,7 @@ class _AddChildSheetState extends State<_AddChildSheet> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
onPressed: _submit,
|
onPressed: _submit,
|
||||||
child: const Text('Adicionar'),
|
child: const Text(HomeStrings.add),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'colors/app_colors.dart';
|
||||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||||
|
|
||||||
@@ -48,8 +49,8 @@ class MyApp extends StatelessWidget {
|
|||||||
title: 'Check-Teeth Kids',
|
title: 'Check-Teeth Kids',
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF2F9E94)),
|
colorScheme: ColorScheme.fromSeed(seedColor: AppColors.teal),
|
||||||
scaffoldBackgroundColor: const Color(0xFFFAFAF7),
|
scaffoldBackgroundColor: AppColors.background,
|
||||||
useMaterial3: true,
|
useMaterial3: true,
|
||||||
// Sem isto, o Material 3 aplica por padrão uma sobreposição de cor
|
// Sem isto, o Material 3 aplica por padrão uma sobreposição de cor
|
||||||
// (surfaceTintColor) e uma elevação extra ao rolar (scrolledUnderElevation)
|
// (surfaceTintColor) e uma elevação extra ao rolar (scrolledUnderElevation)
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import '../colors/app_colors.dart';
|
||||||
|
import '../strings/quiz_strings.dart';
|
||||||
|
import '../strings/quiz_ui_strings.dart';
|
||||||
|
|
||||||
import 'quiz_checklist_screen.dart';
|
import 'quiz_checklist_screen.dart';
|
||||||
import 'quiz_question_screen.dart';
|
import 'quiz_question_screen.dart';
|
||||||
@@ -56,11 +59,11 @@ Route<void> quizPageRoute({required WidgetBuilder builder}) {
|
|||||||
Route<void> quizStartRoute({String? scopeId}) {
|
Route<void> quizStartRoute({String? scopeId}) {
|
||||||
return quizPageRoute(
|
return quizPageRoute(
|
||||||
builder: (_) => QuizChecklistScreen(
|
builder: (_) => QuizChecklistScreen(
|
||||||
heading: 'Vamos ajudá-lo/a a compreender:',
|
heading: QuizStrings.checklistHeading,
|
||||||
items: const [
|
items: const [
|
||||||
'Sinais de alerta podem passar despercebidos',
|
QuizStrings.checklistItem1,
|
||||||
'Prevenir é melhor do que tratar',
|
QuizStrings.checklistItem2,
|
||||||
'Quando deve procurar um dentista (urgência vs vigilância)',
|
QuizStrings.checklistItem3,
|
||||||
],
|
],
|
||||||
onAdvance: (context) => Navigator.of(context).pushReplacement(
|
onAdvance: (context) => Navigator.of(context).pushReplacement(
|
||||||
quizPageRoute(builder: (_) => Quiz1Screen(scopeId: scopeId)),
|
quizPageRoute(builder: (_) => Quiz1Screen(scopeId: scopeId)),
|
||||||
@@ -83,25 +86,25 @@ class Quiz1Screen extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return QuizQuestionScreen(
|
return QuizQuestionScreen(
|
||||||
title: 'Quiz 1/29',
|
title: QuizUiStrings.quizProgress(1, 29),
|
||||||
fallbackColor: const Color(0xFFFF55A7),
|
fallbackColor: AppColors.pink,
|
||||||
question: 'O seu filho/a tem problemas respiratórios diagnosticados?',
|
question: QuizStrings.q1Question,
|
||||||
answers: const [
|
answers: const [
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Sim',
|
title: QuizStrings.yes,
|
||||||
description: 'Problemas respiratórios diagnosticados',
|
description: QuizStrings.q1YesDescription,
|
||||||
weight: 2,
|
weight: 2,
|
||||||
value: 'sim',
|
value: 'sim',
|
||||||
),
|
),
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Não',
|
title: QuizStrings.no,
|
||||||
description: 'Sem problemas respiratórios diagnosticados',
|
description: QuizStrings.q1NoDescription,
|
||||||
weight: 1,
|
weight: 1,
|
||||||
value: 'nao',
|
value: 'nao',
|
||||||
),
|
),
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Não sei',
|
title: QuizStrings.dontKnow,
|
||||||
description: 'Não tenho a certeza',
|
description: QuizStrings.dontKnowDescription,
|
||||||
weight: 1,
|
weight: 1,
|
||||||
value: 'nao_sei',
|
value: 'nao_sei',
|
||||||
helpVideoId: 6,
|
helpVideoId: 6,
|
||||||
@@ -127,25 +130,25 @@ class Quiz2Screen extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return QuizQuestionScreen(
|
return QuizQuestionScreen(
|
||||||
title: 'Quiz 2/29',
|
title: QuizUiStrings.quizProgress(2, 29),
|
||||||
fallbackColor: const Color(0xFF2F9E94),
|
fallbackColor: AppColors.teal,
|
||||||
question: 'O seu filho/a respira habitualmente pela boca?',
|
question: QuizStrings.q2Question,
|
||||||
answers: const [
|
answers: const [
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Sim',
|
title: QuizStrings.yes,
|
||||||
description: 'Respira habitualmente pela boca',
|
description: QuizStrings.q2YesDescription,
|
||||||
weight: 2,
|
weight: 2,
|
||||||
value: 'sim',
|
value: 'sim',
|
||||||
),
|
),
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Não',
|
title: QuizStrings.no,
|
||||||
description: 'Não respira habitualmente pela boca',
|
description: QuizStrings.q2NoDescription,
|
||||||
weight: 1,
|
weight: 1,
|
||||||
value: 'nao',
|
value: 'nao',
|
||||||
),
|
),
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Não sei',
|
title: QuizStrings.dontKnow,
|
||||||
description: 'Não tenho a certeza',
|
description: QuizStrings.dontKnowDescription,
|
||||||
weight: 1,
|
weight: 1,
|
||||||
value: 'nao_sei',
|
value: 'nao_sei',
|
||||||
helpVideoId: 10,
|
helpVideoId: 10,
|
||||||
@@ -171,25 +174,25 @@ class Quiz3Screen extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return QuizQuestionScreen(
|
return QuizQuestionScreen(
|
||||||
title: 'Quiz 3/29',
|
title: QuizUiStrings.quizProgress(3, 29),
|
||||||
fallbackColor: const Color(0xFFFF55A7),
|
fallbackColor: AppColors.pink,
|
||||||
question: 'O seu filho/a ressona habitualmente durante a noite?',
|
question: QuizStrings.q3Question,
|
||||||
answers: const [
|
answers: const [
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Sim',
|
title: QuizStrings.yes,
|
||||||
description: 'Ressonar habitualmente durante a noite',
|
description: QuizStrings.q3YesDescription,
|
||||||
weight: 2,
|
weight: 2,
|
||||||
value: 'sim',
|
value: 'sim',
|
||||||
),
|
),
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Não',
|
title: QuizStrings.no,
|
||||||
description: 'Não ressona habitualmente',
|
description: QuizStrings.q3NoDescription,
|
||||||
weight: 1,
|
weight: 1,
|
||||||
value: 'nao',
|
value: 'nao',
|
||||||
),
|
),
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Não sei',
|
title: QuizStrings.dontKnow,
|
||||||
description: 'Não tenho a certeza',
|
description: QuizStrings.dontKnowDescription,
|
||||||
weight: 1,
|
weight: 1,
|
||||||
value: 'nao_sei',
|
value: 'nao_sei',
|
||||||
helpVideoId: 8,
|
helpVideoId: 8,
|
||||||
@@ -215,25 +218,25 @@ class Quiz4Screen extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return QuizQuestionScreen(
|
return QuizQuestionScreen(
|
||||||
title: 'Quiz 4/29',
|
title: QuizUiStrings.quizProgress(4, 29),
|
||||||
fallbackColor: const Color(0xFFFF55A7),
|
fallbackColor: AppColors.pink,
|
||||||
question: 'O seu filho/a sente habitualmente o nariz "tapado"?',
|
question: QuizStrings.q4Question,
|
||||||
answers: const [
|
answers: const [
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Sim',
|
title: QuizStrings.yes,
|
||||||
description: 'Sente habitualmente o nariz tapado',
|
description: QuizStrings.q4YesDescription,
|
||||||
weight: 2,
|
weight: 2,
|
||||||
value: 'sim',
|
value: 'sim',
|
||||||
),
|
),
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Não',
|
title: QuizStrings.no,
|
||||||
description: 'Não sente habitualmente o nariz tapado',
|
description: QuizStrings.q4NoDescription,
|
||||||
weight: 1,
|
weight: 1,
|
||||||
value: 'nao',
|
value: 'nao',
|
||||||
),
|
),
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Não sei',
|
title: QuizStrings.dontKnow,
|
||||||
description: 'Não tenho a certeza',
|
description: QuizStrings.dontKnowDescription,
|
||||||
weight: 1,
|
weight: 1,
|
||||||
value: 'nao_sei',
|
value: 'nao_sei',
|
||||||
helpVideoId: 1,
|
helpVideoId: 1,
|
||||||
@@ -259,27 +262,25 @@ class Quiz5Screen extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return QuizQuestionScreen(
|
return QuizQuestionScreen(
|
||||||
title: 'Quiz 5/29',
|
title: QuizUiStrings.quizProgress(5, 29),
|
||||||
fallbackColor: const Color(0xFFFF55A7),
|
fallbackColor: AppColors.pink,
|
||||||
question:
|
question: QuizStrings.q5Question,
|
||||||
'Durante o sono, o seu filho/a tem habitualmente interrupções da respiração?',
|
|
||||||
answers: const [
|
answers: const [
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Sim',
|
title: QuizStrings.yes,
|
||||||
description:
|
description: QuizStrings.q5YesDescription,
|
||||||
'Tem habitualmente interrupções da respiração durante o sono',
|
|
||||||
weight: 2,
|
weight: 2,
|
||||||
value: 'sim',
|
value: 'sim',
|
||||||
),
|
),
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Não',
|
title: QuizStrings.no,
|
||||||
description: 'Não tem interrupções da respiração durante o sono',
|
description: QuizStrings.q5NoDescription,
|
||||||
weight: 1,
|
weight: 1,
|
||||||
value: 'nao',
|
value: 'nao',
|
||||||
),
|
),
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Não sei',
|
title: QuizStrings.dontKnow,
|
||||||
description: 'Não tenho a certeza',
|
description: QuizStrings.dontKnowDescription,
|
||||||
weight: 1,
|
weight: 1,
|
||||||
value: 'nao_sei',
|
value: 'nao_sei',
|
||||||
helpVideoId: 7,
|
helpVideoId: 7,
|
||||||
@@ -305,19 +306,19 @@ class Quiz6Screen extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return QuizQuestionScreen(
|
return QuizQuestionScreen(
|
||||||
title: 'Quiz 6/29',
|
title: QuizUiStrings.quizProgress(6, 29),
|
||||||
fallbackColor: const Color(0xFF2F9E94),
|
fallbackColor: AppColors.teal,
|
||||||
question: 'O seu filho/a range os dentes com frequência?',
|
question: QuizStrings.q6Question,
|
||||||
answers: const [
|
answers: const [
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Sim',
|
title: QuizStrings.yes,
|
||||||
description: 'Range os dentes com frequência',
|
description: QuizStrings.q6YesDescription,
|
||||||
weight: 2,
|
weight: 2,
|
||||||
value: 'sim',
|
value: 'sim',
|
||||||
),
|
),
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Não',
|
title: QuizStrings.no,
|
||||||
description: 'Não range os dentes com frequência',
|
description: QuizStrings.q6NoDescription,
|
||||||
weight: 1,
|
weight: 1,
|
||||||
value: 'nao',
|
value: 'nao',
|
||||||
),
|
),
|
||||||
@@ -342,25 +343,25 @@ class Quiz7Screen extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return QuizQuestionScreen(
|
return QuizQuestionScreen(
|
||||||
title: 'Quiz 7/29',
|
title: QuizUiStrings.quizProgress(7, 29),
|
||||||
fallbackColor: const Color(0xFFFF55A7),
|
fallbackColor: AppColors.pink,
|
||||||
question: 'O seu filho/a habitualmente tem alergias sazonais?',
|
question: QuizStrings.q7Question,
|
||||||
answers: const [
|
answers: const [
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Sim',
|
title: QuizStrings.yes,
|
||||||
description: 'Habitualmente tem alergias sazonais',
|
description: QuizStrings.q7YesDescription,
|
||||||
weight: 2,
|
weight: 2,
|
||||||
value: 'sim',
|
value: 'sim',
|
||||||
),
|
),
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Não',
|
title: QuizStrings.no,
|
||||||
description: 'Não tem alergias sazonais',
|
description: QuizStrings.q7NoDescription,
|
||||||
weight: 1,
|
weight: 1,
|
||||||
value: 'nao',
|
value: 'nao',
|
||||||
),
|
),
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Não sei',
|
title: QuizStrings.dontKnow,
|
||||||
description: 'Não tenho a certeza',
|
description: QuizStrings.dontKnowDescription,
|
||||||
weight: 1,
|
weight: 1,
|
||||||
value: 'nao_sei',
|
value: 'nao_sei',
|
||||||
helpVideoId: 2,
|
helpVideoId: 2,
|
||||||
@@ -386,25 +387,25 @@ class Quiz8Screen extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return QuizQuestionScreen(
|
return QuizQuestionScreen(
|
||||||
title: 'Quiz 8/29',
|
title: QuizUiStrings.quizProgress(8, 29),
|
||||||
fallbackColor: const Color(0xFFFF55A7),
|
fallbackColor: AppColors.pink,
|
||||||
question: 'O seu filho/a acorda com saliva seca na cara ou na almofada?',
|
question: QuizStrings.q8Question,
|
||||||
answers: const [
|
answers: const [
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Sim',
|
title: QuizStrings.yes,
|
||||||
description: 'Acorda com saliva seca na cara ou na almofada',
|
description: QuizStrings.q8YesDescription,
|
||||||
weight: 2,
|
weight: 2,
|
||||||
value: 'sim',
|
value: 'sim',
|
||||||
),
|
),
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Não',
|
title: QuizStrings.no,
|
||||||
description: 'Não acorda com saliva seca',
|
description: QuizStrings.q8NoDescription,
|
||||||
weight: 1,
|
weight: 1,
|
||||||
value: 'nao',
|
value: 'nao',
|
||||||
),
|
),
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Não sei',
|
title: QuizStrings.dontKnow,
|
||||||
description: 'Não tenho a certeza',
|
description: QuizStrings.dontKnowDescription,
|
||||||
weight: 1,
|
weight: 1,
|
||||||
value: 'nao_sei',
|
value: 'nao_sei',
|
||||||
helpVideoId: 9,
|
helpVideoId: 9,
|
||||||
@@ -430,25 +431,25 @@ class Quiz9Screen extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return QuizQuestionScreen(
|
return QuizQuestionScreen(
|
||||||
title: 'Quiz 9/29',
|
title: QuizUiStrings.quizProgress(9, 29),
|
||||||
fallbackColor: const Color(0xFF2F9E94),
|
fallbackColor: AppColors.teal,
|
||||||
question: 'O seu filho/a teve ou costuma ter com frequência otites?',
|
question: QuizStrings.q9Question,
|
||||||
answers: const [
|
answers: const [
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Sim',
|
title: QuizStrings.yes,
|
||||||
description: 'Teve ou costuma ter com frequência otites',
|
description: QuizStrings.q9YesDescription,
|
||||||
weight: 2,
|
weight: 2,
|
||||||
value: 'sim',
|
value: 'sim',
|
||||||
),
|
),
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Não',
|
title: QuizStrings.no,
|
||||||
description: 'Não teve ou não costuma ter otites',
|
description: QuizStrings.q9NoDescription,
|
||||||
weight: 1,
|
weight: 1,
|
||||||
value: 'nao',
|
value: 'nao',
|
||||||
),
|
),
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Não sei',
|
title: QuizStrings.dontKnow,
|
||||||
description: 'Não tenho a certeza',
|
description: QuizStrings.dontKnowDescription,
|
||||||
weight: 1,
|
weight: 1,
|
||||||
value: 'nao_sei',
|
value: 'nao_sei',
|
||||||
helpVideoId: 3,
|
helpVideoId: 3,
|
||||||
@@ -474,25 +475,25 @@ class Quiz10Screen extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return QuizQuestionScreen(
|
return QuizQuestionScreen(
|
||||||
title: 'Quiz 10/29',
|
title: QuizUiStrings.quizProgress(10, 29),
|
||||||
fallbackColor: const Color(0xFFFF55A7),
|
fallbackColor: AppColors.pink,
|
||||||
question: 'O seu filho/a teve ou costuma ter com frequência amigdalites?',
|
question: QuizStrings.q10Question,
|
||||||
answers: const [
|
answers: const [
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Sim',
|
title: QuizStrings.yes,
|
||||||
description: 'Teve ou costuma ter com frequência amigdalites',
|
description: QuizStrings.q10YesDescription,
|
||||||
weight: 2,
|
weight: 2,
|
||||||
value: 'sim',
|
value: 'sim',
|
||||||
),
|
),
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Não',
|
title: QuizStrings.no,
|
||||||
description: 'Não teve ou não costuma ter amigdalites',
|
description: QuizStrings.q10NoDescription,
|
||||||
weight: 1,
|
weight: 1,
|
||||||
value: 'nao',
|
value: 'nao',
|
||||||
),
|
),
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Não sei',
|
title: QuizStrings.dontKnow,
|
||||||
description: 'Não tenho a certeza',
|
description: QuizStrings.dontKnowDescription,
|
||||||
weight: 1,
|
weight: 1,
|
||||||
value: 'nao_sei',
|
value: 'nao_sei',
|
||||||
helpVideoId: 4,
|
helpVideoId: 4,
|
||||||
@@ -518,26 +519,25 @@ class Quiz11Screen extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return QuizQuestionScreen(
|
return QuizQuestionScreen(
|
||||||
title: 'Quiz 11/29',
|
title: QuizUiStrings.quizProgress(11, 29),
|
||||||
fallbackColor: const Color(0xFF2F9E94),
|
fallbackColor: AppColors.teal,
|
||||||
question:
|
question: QuizStrings.q11Question,
|
||||||
'O seu filho/a teve ou costuma ter com frequência bronquiolites?',
|
|
||||||
answers: const [
|
answers: const [
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Sim',
|
title: QuizStrings.yes,
|
||||||
description: 'Teve ou costuma ter com frequência bronquiolites',
|
description: QuizStrings.q11YesDescription,
|
||||||
weight: 2,
|
weight: 2,
|
||||||
value: 'sim',
|
value: 'sim',
|
||||||
),
|
),
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Não',
|
title: QuizStrings.no,
|
||||||
description: 'Não teve ou não costuma ter bronquiolites',
|
description: QuizStrings.q11NoDescription,
|
||||||
weight: 1,
|
weight: 1,
|
||||||
value: 'nao',
|
value: 'nao',
|
||||||
),
|
),
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Não sei',
|
title: QuizStrings.dontKnow,
|
||||||
description: 'Não tenho a certeza',
|
description: QuizStrings.dontKnowDescription,
|
||||||
weight: 1,
|
weight: 1,
|
||||||
value: 'nao_sei',
|
value: 'nao_sei',
|
||||||
helpVideoId: 5,
|
helpVideoId: 5,
|
||||||
@@ -563,19 +563,19 @@ class Quiz12Screen extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return QuizQuestionScreen(
|
return QuizQuestionScreen(
|
||||||
title: 'Quiz 12/29',
|
title: QuizUiStrings.quizProgress(12, 29),
|
||||||
fallbackColor: const Color(0xFFFF55A7),
|
fallbackColor: AppColors.pink,
|
||||||
question: 'O seu filho/a apresenta dificuldades a mastigar?',
|
question: QuizStrings.q12Question,
|
||||||
answers: const [
|
answers: const [
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Sim',
|
title: QuizStrings.yes,
|
||||||
description: 'Apresenta dificuldades a mastigar',
|
description: QuizStrings.q12YesDescription,
|
||||||
weight: 2,
|
weight: 2,
|
||||||
value: 'sim',
|
value: 'sim',
|
||||||
),
|
),
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Não',
|
title: QuizStrings.no,
|
||||||
description: 'Não apresenta dificuldades a mastigar',
|
description: QuizStrings.q12NoDescription,
|
||||||
weight: 1,
|
weight: 1,
|
||||||
value: 'nao',
|
value: 'nao',
|
||||||
),
|
),
|
||||||
@@ -600,19 +600,19 @@ class Quiz13Screen extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return QuizQuestionScreen(
|
return QuizQuestionScreen(
|
||||||
title: 'Quiz 13/29',
|
title: QuizUiStrings.quizProgress(13, 29),
|
||||||
fallbackColor: const Color(0xFF2F9E94),
|
fallbackColor: AppColors.teal,
|
||||||
question: 'O seu filho/a habitualmente é lento a comer?',
|
question: QuizStrings.q13Question,
|
||||||
answers: const [
|
answers: const [
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Sim',
|
title: QuizStrings.yes,
|
||||||
description: 'Habitualmente é lento a comer',
|
description: QuizStrings.q13YesDescription,
|
||||||
weight: 2,
|
weight: 2,
|
||||||
value: 'sim',
|
value: 'sim',
|
||||||
),
|
),
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Não',
|
title: QuizStrings.no,
|
||||||
description: 'Não é lento a comer',
|
description: QuizStrings.q13NoDescription,
|
||||||
weight: 1,
|
weight: 1,
|
||||||
value: 'nao',
|
value: 'nao',
|
||||||
),
|
),
|
||||||
@@ -637,19 +637,19 @@ class Quiz14Screen extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return QuizQuestionScreen(
|
return QuizQuestionScreen(
|
||||||
title: 'Quiz 14/29',
|
title: QuizUiStrings.quizProgress(14, 29),
|
||||||
fallbackColor: const Color(0xFFFF55A7),
|
fallbackColor: AppColors.pink,
|
||||||
question: 'O seu filho/a habitualmente prefere comer alimentos moles?',
|
question: QuizStrings.q14Question,
|
||||||
answers: const [
|
answers: const [
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Sim',
|
title: QuizStrings.yes,
|
||||||
description: 'Habitualmente prefere comer alimentos moles',
|
description: QuizStrings.q14YesDescription,
|
||||||
weight: 2,
|
weight: 2,
|
||||||
value: 'sim',
|
value: 'sim',
|
||||||
),
|
),
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Não',
|
title: QuizStrings.no,
|
||||||
description: 'Não prefere alimentos moles',
|
description: QuizStrings.q14NoDescription,
|
||||||
weight: 1,
|
weight: 1,
|
||||||
value: 'nao',
|
value: 'nao',
|
||||||
),
|
),
|
||||||
@@ -674,25 +674,25 @@ class Quiz15Screen extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return QuizQuestionScreen(
|
return QuizQuestionScreen(
|
||||||
title: 'Quiz 15/29',
|
title: QuizUiStrings.quizProgress(15, 29),
|
||||||
fallbackColor: const Color(0xFFFF55A7),
|
fallbackColor: AppColors.pink,
|
||||||
question: 'Em bebé apenas foi alimentado por biberão?',
|
question: QuizStrings.q15Question,
|
||||||
answers: const [
|
answers: const [
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Sim',
|
title: QuizStrings.yes,
|
||||||
description: 'Em bebé apenas foi alimentado por biberão',
|
description: QuizStrings.q15YesDescription,
|
||||||
weight: 2,
|
weight: 2,
|
||||||
value: 'sim',
|
value: 'sim',
|
||||||
),
|
),
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Não',
|
title: QuizStrings.no,
|
||||||
description: 'Não foi apenas alimentado por biberão',
|
description: QuizStrings.q15NoDescription,
|
||||||
weight: 1,
|
weight: 1,
|
||||||
value: 'nao',
|
value: 'nao',
|
||||||
),
|
),
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Não sei',
|
title: QuizStrings.dontKnow,
|
||||||
description: 'Não tenho a certeza',
|
description: QuizStrings.dontKnowDescription,
|
||||||
weight: 1,
|
weight: 1,
|
||||||
value: 'nao_sei',
|
value: 'nao_sei',
|
||||||
helpVideoId: 11,
|
helpVideoId: 11,
|
||||||
@@ -718,25 +718,25 @@ class Quiz16Screen extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return QuizQuestionScreen(
|
return QuizQuestionScreen(
|
||||||
title: 'Quiz 16/29',
|
title: QuizUiStrings.quizProgress(16, 29),
|
||||||
fallbackColor: const Color(0xFF2F9E94),
|
fallbackColor: AppColors.teal,
|
||||||
question: 'O seu filho/a usa ou usou chupeta com frequência?',
|
question: QuizStrings.q16Question,
|
||||||
answers: const [
|
answers: const [
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Sim',
|
title: QuizStrings.yes,
|
||||||
description: 'Usa ou usou chupeta com frequência',
|
description: QuizStrings.q16YesDescription,
|
||||||
weight: 2,
|
weight: 2,
|
||||||
value: 'sim',
|
value: 'sim',
|
||||||
),
|
),
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Não',
|
title: QuizStrings.no,
|
||||||
description: 'Não usa ou não usou chupeta com frequência',
|
description: QuizStrings.q16NoDescription,
|
||||||
weight: 1,
|
weight: 1,
|
||||||
value: 'nao',
|
value: 'nao',
|
||||||
),
|
),
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Não sei',
|
title: QuizStrings.dontKnow,
|
||||||
description: 'Não tenho a certeza',
|
description: QuizStrings.dontKnowDescription,
|
||||||
weight: 1,
|
weight: 1,
|
||||||
value: 'nao_sei',
|
value: 'nao_sei',
|
||||||
helpVideoId: 12,
|
helpVideoId: 12,
|
||||||
@@ -762,25 +762,25 @@ class Quiz17Screen extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return QuizQuestionScreen(
|
return QuizQuestionScreen(
|
||||||
title: 'Quiz 17/29',
|
title: QuizUiStrings.quizProgress(17, 29),
|
||||||
fallbackColor: const Color(0xFFFF55A7),
|
fallbackColor: AppColors.pink,
|
||||||
question: 'O seu filho/a chucha ou já chuchou o dedo com frequência?',
|
question: QuizStrings.q17Question,
|
||||||
answers: const [
|
answers: const [
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Sim',
|
title: QuizStrings.yes,
|
||||||
description: 'Chucha ou já chuchou o dedo com frequência',
|
description: QuizStrings.q17YesDescription,
|
||||||
weight: 2,
|
weight: 2,
|
||||||
value: 'sim',
|
value: 'sim',
|
||||||
),
|
),
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Não',
|
title: QuizStrings.no,
|
||||||
description: 'Não chucha ou não chuchou o dedo com frequência',
|
description: QuizStrings.q17NoDescription,
|
||||||
weight: 1,
|
weight: 1,
|
||||||
value: 'nao',
|
value: 'nao',
|
||||||
),
|
),
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Não sei',
|
title: QuizStrings.dontKnow,
|
||||||
description: 'Não tenho a certeza',
|
description: QuizStrings.dontKnowDescription,
|
||||||
weight: 1,
|
weight: 1,
|
||||||
value: 'nao_sei',
|
value: 'nao_sei',
|
||||||
helpVideoId: 13,
|
helpVideoId: 13,
|
||||||
@@ -814,23 +814,22 @@ class Quiz18Screen extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return QuizQuestionScreen(
|
return QuizQuestionScreen(
|
||||||
title: 'Quiz 18/29',
|
title: QuizUiStrings.quizProgress(18, 29),
|
||||||
fallbackColor: const Color(0xFFFF55A7),
|
fallbackColor: AppColors.pink,
|
||||||
answerImageAspectRatio: 1.5,
|
answerImageAspectRatio: 1.5,
|
||||||
question:
|
question: QuizStrings.q18Question,
|
||||||
'Qual das seguintes imagens é mais parecida com a postura do seu filho/a?',
|
|
||||||
answers: const [
|
answers: const [
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Postura inadequada',
|
title: QuizStrings.q18Answer1Title,
|
||||||
description: 'Postura curvada, ombros e pescoço projetados',
|
description: QuizStrings.q18Answer1Description,
|
||||||
weight: 2,
|
weight: 2,
|
||||||
hideTitle: true,
|
hideTitle: true,
|
||||||
value: 'postura_inadequada',
|
value: 'postura_inadequada',
|
||||||
imagePath: 'assets/mockup_images/0.1.png',
|
imagePath: 'assets/mockup_images/0.1.png',
|
||||||
),
|
),
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Postura correta',
|
title: QuizStrings.q18Answer2Title,
|
||||||
description: 'Postura ereta, coluna alinhada',
|
description: QuizStrings.q18Answer2Description,
|
||||||
weight: 1,
|
weight: 1,
|
||||||
hideTitle: true,
|
hideTitle: true,
|
||||||
value: 'postura_correta',
|
value: 'postura_correta',
|
||||||
@@ -857,14 +856,13 @@ class Quiz19Screen extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return QuizQuestionScreen(
|
return QuizQuestionScreen(
|
||||||
title: 'Quiz 19/29',
|
title: QuizUiStrings.quizProgress(19, 29),
|
||||||
fallbackColor: const Color(0xFFFF55A7),
|
fallbackColor: AppColors.pink,
|
||||||
question:
|
question: QuizStrings.q19Question,
|
||||||
'Qual das seguintes imagens é mais parecida com o perfil do seu filho/a?',
|
|
||||||
answers: [
|
answers: [
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Perfil convexo',
|
title: QuizStrings.q19Answer1Title,
|
||||||
description: 'Perfil facial convexo',
|
description: QuizStrings.q19Answer1Description,
|
||||||
weight: 2,
|
weight: 2,
|
||||||
hideTitle: true,
|
hideTitle: true,
|
||||||
value: 'perfil_convexo',
|
value: 'perfil_convexo',
|
||||||
@@ -875,8 +873,8 @@ class Quiz19Screen extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Perfil reto',
|
title: QuizStrings.q19Answer2Title,
|
||||||
description: 'Perfil facial reto',
|
description: QuizStrings.q19Answer2Description,
|
||||||
weight: 1,
|
weight: 1,
|
||||||
hideTitle: true,
|
hideTitle: true,
|
||||||
value: 'perfil_reto',
|
value: 'perfil_reto',
|
||||||
@@ -887,8 +885,8 @@ class Quiz19Screen extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Perfil côncavo',
|
title: QuizStrings.q19Answer3Title,
|
||||||
description: 'Perfil facial côncavo',
|
description: QuizStrings.q19Answer3Description,
|
||||||
weight: 2,
|
weight: 2,
|
||||||
hideTitle: true,
|
hideTitle: true,
|
||||||
value: 'perfil_concavo',
|
value: 'perfil_concavo',
|
||||||
@@ -919,21 +917,21 @@ class Quiz20Screen extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return QuizQuestionScreen(
|
return QuizQuestionScreen(
|
||||||
title: 'Quiz 20/29',
|
title: QuizUiStrings.quizProgress(20, 29),
|
||||||
fallbackColor: const Color(0xFF2F9E94),
|
fallbackColor: AppColors.teal,
|
||||||
question: 'Qual é a posição da boca do seu filho/a habitualmente?',
|
question: QuizStrings.q20Question,
|
||||||
answers: [
|
answers: [
|
||||||
const QuizAnswer(
|
const QuizAnswer(
|
||||||
title: 'Boca fechada',
|
title: QuizStrings.q20Answer1Title,
|
||||||
description: 'Boca fechada habitualmente',
|
description: QuizStrings.q20Answer1Description,
|
||||||
weight: 1,
|
weight: 1,
|
||||||
hideTitle: true,
|
hideTitle: true,
|
||||||
value: 'boca_fechada',
|
value: 'boca_fechada',
|
||||||
imagePath: 'assets/mockup_images/7.png',
|
imagePath: 'assets/mockup_images/7.png',
|
||||||
),
|
),
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Boca entreaberta',
|
title: QuizStrings.q20Answer2Title,
|
||||||
description: 'Boca entreaberta habitualmente',
|
description: QuizStrings.q20Answer2Description,
|
||||||
weight: 2,
|
weight: 2,
|
||||||
hideTitle: true,
|
hideTitle: true,
|
||||||
value: 'boca_entreaberta',
|
value: 'boca_entreaberta',
|
||||||
@@ -964,14 +962,13 @@ class Quiz21Screen extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return QuizQuestionScreen(
|
return QuizQuestionScreen(
|
||||||
title: 'Quiz 21/29',
|
title: QuizUiStrings.quizProgress(21, 29),
|
||||||
fallbackColor: const Color(0xFFFF55A7),
|
fallbackColor: AppColors.pink,
|
||||||
question:
|
question: QuizStrings.q21Question,
|
||||||
'Qual das imagens, na zona abaixo dos olhos, se assemelha mais ao seu filho/a?',
|
|
||||||
answers: [
|
answers: [
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Sem sinais',
|
title: QuizStrings.q21Answer1Title,
|
||||||
description: 'Sem olheiras visíveis abaixo dos olhos',
|
description: QuizStrings.q21Answer1Description,
|
||||||
weight: 1,
|
weight: 1,
|
||||||
hideTitle: true,
|
hideTitle: true,
|
||||||
value: 'sem_olheiras',
|
value: 'sem_olheiras',
|
||||||
@@ -982,8 +979,8 @@ class Quiz21Screen extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Sinal de risco',
|
title: QuizStrings.q21Answer2Title,
|
||||||
description: 'Olheiras visíveis abaixo dos olhos',
|
description: QuizStrings.q21Answer2Description,
|
||||||
weight: 2,
|
weight: 2,
|
||||||
hideTitle: true,
|
hideTitle: true,
|
||||||
value: 'com_olheiras',
|
value: 'com_olheiras',
|
||||||
@@ -1014,15 +1011,14 @@ class Quiz22Screen extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return QuizQuestionScreen(
|
return QuizQuestionScreen(
|
||||||
title: 'Quiz 22/29',
|
title: QuizUiStrings.quizProgress(22, 29),
|
||||||
fallbackColor: const Color(0xFFFF55A7),
|
fallbackColor: AppColors.pink,
|
||||||
isSignQuestion: true,
|
isSignQuestion: true,
|
||||||
question:
|
question: QuizStrings.q22Question,
|
||||||
'Qual das imagens é mais parecida com o queixo do seu filho/a com a boca fechada?',
|
|
||||||
answers: [
|
answers: [
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Queixo relaxado',
|
title: QuizStrings.q22Answer1Title,
|
||||||
description: 'Queixo liso e relaxado com a boca fechada',
|
description: QuizStrings.q22Answer1Description,
|
||||||
weight: 1,
|
weight: 1,
|
||||||
hideTitle: true,
|
hideTitle: true,
|
||||||
value: 'queixo_correto',
|
value: 'queixo_correto',
|
||||||
@@ -1034,8 +1030,8 @@ class Quiz22Screen extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const QuizAnswer(
|
const QuizAnswer(
|
||||||
title: 'Queixo tenso',
|
title: QuizStrings.q22Answer2Title,
|
||||||
description: 'Queixo tenso/franzido com a boca fechada',
|
description: QuizStrings.q22Answer2Description,
|
||||||
weight: 2,
|
weight: 2,
|
||||||
hideTitle: true,
|
hideTitle: true,
|
||||||
value: 'queixo_tenso',
|
value: 'queixo_tenso',
|
||||||
@@ -1070,31 +1066,30 @@ class Quiz23Screen extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return QuizQuestionScreen(
|
return QuizQuestionScreen(
|
||||||
title: 'Quiz 23/29',
|
title: QuizUiStrings.quizProgress(23, 29),
|
||||||
fallbackColor: const Color(0xFF2F9E94),
|
fallbackColor: AppColors.teal,
|
||||||
isSignQuestion: true,
|
isSignQuestion: true,
|
||||||
question:
|
question: QuizStrings.q23Question,
|
||||||
'Qual das seguintes imagens se assemelha à boca do seu filho/a?',
|
|
||||||
answers: const [
|
answers: const [
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Dentição sobreposta',
|
title: QuizStrings.q23Answer1Title,
|
||||||
description: 'Dentes sobrepostos/tortos',
|
description: QuizStrings.q23Answer1Description,
|
||||||
weight: 2,
|
weight: 2,
|
||||||
hideTitle: true,
|
hideTitle: true,
|
||||||
value: 'dentes_sobrepostos',
|
value: 'dentes_sobrepostos',
|
||||||
imagePath: 'assets/mockup_images/10.png',
|
imagePath: 'assets/mockup_images/10.png',
|
||||||
),
|
),
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Dentição alinhada',
|
title: QuizStrings.q23Answer2Title,
|
||||||
description: 'Dentição bem alinhada, sem apinhamento',
|
description: QuizStrings.q23Answer2Description,
|
||||||
weight: 1,
|
weight: 1,
|
||||||
hideTitle: true,
|
hideTitle: true,
|
||||||
value: 'dentes_alinhados',
|
value: 'dentes_alinhados',
|
||||||
imagePath: 'assets/mockup_images/11.png',
|
imagePath: 'assets/mockup_images/11.png',
|
||||||
),
|
),
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Dentição desalinhada',
|
title: QuizStrings.q23Answer3Title,
|
||||||
description: 'Dentição desalinhada/apinhada',
|
description: QuizStrings.q23Answer3Description,
|
||||||
weight: 2,
|
weight: 2,
|
||||||
hideTitle: true,
|
hideTitle: true,
|
||||||
value: 'dentes_desalinhados',
|
value: 'dentes_desalinhados',
|
||||||
@@ -1121,31 +1116,30 @@ class Quiz24Screen extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return QuizQuestionScreen(
|
return QuizQuestionScreen(
|
||||||
title: 'Quiz 24/29',
|
title: QuizUiStrings.quizProgress(24, 29),
|
||||||
fallbackColor: const Color(0xFF2F9E94),
|
fallbackColor: AppColors.teal,
|
||||||
isSignQuestion: true,
|
isSignQuestion: true,
|
||||||
question:
|
question: QuizStrings.q24Question,
|
||||||
'Qual das seguintes imagens se assemelha à boca do seu filho/a?',
|
|
||||||
answers: const [
|
answers: const [
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Apinhamento moderado',
|
title: QuizStrings.q24Answer1Title,
|
||||||
description: 'Dentes rodados/desalinhados de forma mais visível',
|
description: QuizStrings.q24Answer1Description,
|
||||||
weight: 2,
|
weight: 2,
|
||||||
hideTitle: true,
|
hideTitle: true,
|
||||||
value: 'apinhamento_moderado',
|
value: 'apinhamento_moderado',
|
||||||
imagePath: 'assets/mockup_images/16.png',
|
imagePath: 'assets/mockup_images/16.png',
|
||||||
),
|
),
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Ligeiro desalinhamento',
|
title: QuizStrings.q24Answer2Title,
|
||||||
description: 'Pequeno desalinhamento ou espaço entre alguns dentes',
|
description: QuizStrings.q24Answer2Description,
|
||||||
weight: 1,
|
weight: 1,
|
||||||
hideTitle: true,
|
hideTitle: true,
|
||||||
value: 'apinhamento_leve',
|
value: 'apinhamento_leve',
|
||||||
imagePath: 'assets/mockup_images/15.png',
|
imagePath: 'assets/mockup_images/15.png',
|
||||||
),
|
),
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Apinhamento acentuado',
|
title: QuizStrings.q24Answer3Title,
|
||||||
description: 'Dentes muito sobrepostos entre si',
|
description: QuizStrings.q24Answer3Description,
|
||||||
weight: 2,
|
weight: 2,
|
||||||
hideTitle: true,
|
hideTitle: true,
|
||||||
value: 'apinhamento_acentuado',
|
value: 'apinhamento_acentuado',
|
||||||
@@ -1172,22 +1166,21 @@ class Quiz25Screen extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return QuizQuestionScreen(
|
return QuizQuestionScreen(
|
||||||
title: 'Quiz 25/29',
|
title: QuizUiStrings.quizProgress(25, 29),
|
||||||
fallbackColor: const Color(0xFFFF55A7),
|
fallbackColor: AppColors.pink,
|
||||||
question:
|
question: QuizStrings.q25Question,
|
||||||
'Qual das seguintes imagens se assemelha ao freio labial do seu filho/a?',
|
|
||||||
answers: const [
|
answers: const [
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Freio labial correto',
|
title: QuizStrings.q25Answer1Title,
|
||||||
description: 'Inserção do freio labial mais alta',
|
description: QuizStrings.q25Answer1Description,
|
||||||
weight: 1,
|
weight: 1,
|
||||||
hideTitle: true,
|
hideTitle: true,
|
||||||
value: 'freio_labial_correto',
|
value: 'freio_labial_correto',
|
||||||
imagePath: 'assets/mockup_images/20.png',
|
imagePath: 'assets/mockup_images/20.png',
|
||||||
),
|
),
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Freio labial inadequado',
|
title: QuizStrings.q25Answer2Title,
|
||||||
description: 'Inserção do freio labial baixa, entre os dentes',
|
description: QuizStrings.q25Answer2Description,
|
||||||
weight: 2,
|
weight: 2,
|
||||||
hideTitle: true,
|
hideTitle: true,
|
||||||
value: 'freio_labial_inadequado',
|
value: 'freio_labial_inadequado',
|
||||||
@@ -1214,22 +1207,21 @@ class Quiz26Screen extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return QuizQuestionScreen(
|
return QuizQuestionScreen(
|
||||||
title: 'Quiz 26/29',
|
title: QuizUiStrings.quizProgress(26, 29),
|
||||||
fallbackColor: const Color(0xFFFF55A7),
|
fallbackColor: AppColors.pink,
|
||||||
question:
|
question: QuizStrings.q26Question,
|
||||||
'Qual das seguintes imagens se assemelha ao freio lingual do seu filho/a?',
|
|
||||||
answers: const [
|
answers: const [
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Freio lingual inadequado',
|
title: QuizStrings.q26Answer1Title,
|
||||||
description: 'Freio lingual curto/apertado (língua em coração)',
|
description: QuizStrings.q26Answer1Description,
|
||||||
weight: 2,
|
weight: 2,
|
||||||
hideTitle: true,
|
hideTitle: true,
|
||||||
value: 'freio_lingual_inadequado',
|
value: 'freio_lingual_inadequado',
|
||||||
imagePath: 'assets/mockup_images/17.png',
|
imagePath: 'assets/mockup_images/17.png',
|
||||||
),
|
),
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Freio lingual correto',
|
title: QuizStrings.q26Answer2Title,
|
||||||
description: 'Língua move-se livremente, sem restrição visível',
|
description: QuizStrings.q26Answer2Description,
|
||||||
weight: 1,
|
weight: 1,
|
||||||
hideTitle: true,
|
hideTitle: true,
|
||||||
value: 'freio_lingual_correto',
|
value: 'freio_lingual_correto',
|
||||||
@@ -1256,31 +1248,30 @@ class Quiz27Screen extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return QuizQuestionScreen(
|
return QuizQuestionScreen(
|
||||||
title: 'Quiz 27/29',
|
title: QuizUiStrings.quizProgress(27, 29),
|
||||||
fallbackColor: const Color(0xFF2F9E94),
|
fallbackColor: AppColors.teal,
|
||||||
isSignQuestion: true,
|
isSignQuestion: true,
|
||||||
question:
|
question: QuizStrings.q27Question,
|
||||||
'Qual das seguintes imagens se assemelha com a boca do seu filho/a?',
|
|
||||||
answers: const [
|
answers: const [
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Dentição desalinhada',
|
title: QuizStrings.q27Answer1Title,
|
||||||
description: 'Dentição desalinhada/apinhada',
|
description: QuizStrings.q27Answer1Description,
|
||||||
weight: 2,
|
weight: 2,
|
||||||
hideTitle: true,
|
hideTitle: true,
|
||||||
value: 'dentes_desalinhados_3',
|
value: 'dentes_desalinhados_3',
|
||||||
imagePath: 'assets/mockup_images/23.JPEG',
|
imagePath: 'assets/mockup_images/23.JPEG',
|
||||||
),
|
),
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Dentição sobreposta',
|
title: QuizStrings.q27Answer2Title,
|
||||||
description: 'Dentes sobrepostos/tortos',
|
description: QuizStrings.q27Answer2Description,
|
||||||
weight: 2,
|
weight: 2,
|
||||||
hideTitle: true,
|
hideTitle: true,
|
||||||
value: 'dentes_sobrepostos_3',
|
value: 'dentes_sobrepostos_3',
|
||||||
imagePath: 'assets/mockup_images/24.png',
|
imagePath: 'assets/mockup_images/24.png',
|
||||||
),
|
),
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Dentição alinhada',
|
title: QuizStrings.q27Answer3Title,
|
||||||
description: 'Dentição bem alinhada, sem apinhamento',
|
description: QuizStrings.q27Answer3Description,
|
||||||
weight: 1,
|
weight: 1,
|
||||||
hideTitle: true,
|
hideTitle: true,
|
||||||
value: 'dentes_alinhados_3',
|
value: 'dentes_alinhados_3',
|
||||||
@@ -1307,23 +1298,22 @@ class Quiz28Screen extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return QuizQuestionScreen(
|
return QuizQuestionScreen(
|
||||||
title: 'Quiz 28/29',
|
title: QuizUiStrings.quizProgress(28, 29),
|
||||||
fallbackColor: const Color(0xFF2F9E94),
|
fallbackColor: AppColors.teal,
|
||||||
isSignQuestion: true,
|
isSignQuestion: true,
|
||||||
question:
|
question: QuizStrings.q28Question,
|
||||||
'Qual das seguintes imagens se assemelha à boca do seu filho/a?',
|
|
||||||
answers: const [
|
answers: const [
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Dentição alinhada',
|
title: QuizStrings.q28Answer1Title,
|
||||||
description: 'Dentição bem alinhada, sem apinhamento',
|
description: QuizStrings.q28Answer1Description,
|
||||||
weight: 1,
|
weight: 1,
|
||||||
hideTitle: true,
|
hideTitle: true,
|
||||||
value: 'dentes_alinhados_2',
|
value: 'dentes_alinhados_2',
|
||||||
imagePath: 'assets/mockup_images/29.jpeg',
|
imagePath: 'assets/mockup_images/29.jpeg',
|
||||||
),
|
),
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Dentição desalinhada',
|
title: QuizStrings.q28Answer2Title,
|
||||||
description: 'Dentes sobrepostos/apinhados',
|
description: QuizStrings.q28Answer2Description,
|
||||||
weight: 2,
|
weight: 2,
|
||||||
hideTitle: true,
|
hideTitle: true,
|
||||||
value: 'dentes_desalinhados_2',
|
value: 'dentes_desalinhados_2',
|
||||||
@@ -1350,24 +1340,23 @@ class Quiz29Screen extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return QuizQuestionScreen(
|
return QuizQuestionScreen(
|
||||||
title: 'Quiz 29/29',
|
title: QuizUiStrings.quizProgress(29, 29),
|
||||||
fallbackColor: const Color(0xFFFF55A7),
|
fallbackColor: AppColors.pink,
|
||||||
isSignQuestion: true,
|
isSignQuestion: true,
|
||||||
correctBadgeLabel: 'Posição saudável',
|
correctBadgeLabel: QuizStrings.q29CorrectBadgeLabel,
|
||||||
question:
|
question: QuizStrings.q29Question,
|
||||||
'Qual das seguintes imagens se assemelha ao céu da boca do seu filho/a?',
|
|
||||||
answers: const [
|
answers: const [
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Posição profunda incorreta',
|
title: QuizStrings.q29Answer1Title,
|
||||||
description: 'Palato estreito/profundo, em forma de V',
|
description: QuizStrings.q29Answer1Description,
|
||||||
weight: 2,
|
weight: 2,
|
||||||
hideTitle: true,
|
hideTitle: true,
|
||||||
value: 'palato_v',
|
value: 'palato_v',
|
||||||
imagePath: 'assets/mockup_images/26.png',
|
imagePath: 'assets/mockup_images/26.png',
|
||||||
),
|
),
|
||||||
QuizAnswer(
|
QuizAnswer(
|
||||||
title: 'Posição em U saudável',
|
title: QuizStrings.q29Answer2Title,
|
||||||
description: 'Palato largo, em forma de U',
|
description: QuizStrings.q29Answer2Description,
|
||||||
weight: 1,
|
weight: 1,
|
||||||
hideTitle: true,
|
hideTitle: true,
|
||||||
value: 'palato_u',
|
value: 'palato_u',
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import '../colors/app_colors.dart';
|
||||||
|
|
||||||
|
import '../strings/quiz_ui_strings.dart';
|
||||||
import '../widgets/entrance.dart';
|
import '../widgets/entrance.dart';
|
||||||
import '../widgets/liquid_waves_background.dart';
|
import '../widgets/liquid_waves_background.dart';
|
||||||
import '../widgets/tap_bounce.dart';
|
import '../widgets/tap_bounce.dart';
|
||||||
|
|
||||||
const Color _pink = Color(0xFFFF55A7);
|
const Color _pink = AppColors.pink;
|
||||||
const Color _teal = Color(0xFF2F9E94);
|
const Color _teal = AppColors.teal;
|
||||||
|
|
||||||
/// Ecrã intersticial informativo, mostrado a meio do quiz para preparar o
|
/// Ecrã intersticial informativo, mostrado a meio do quiz para preparar o
|
||||||
/// utilizador antes de continuar (ex.: logo a seguir a um vídeo-guia) —
|
/// utilizador antes de continuar (ex.: logo a seguir a um vídeo-guia) —
|
||||||
@@ -30,7 +32,7 @@ class QuizChecklistScreen extends StatelessWidget {
|
|||||||
body: Stack(
|
body: Stack(
|
||||||
clipBehavior: Clip.none,
|
clipBehavior: Clip.none,
|
||||||
children: [
|
children: [
|
||||||
Positioned.fill(child: Container(color: const Color(0xFFFAFAF7))),
|
Positioned.fill(child: Container(color: AppColors.background)),
|
||||||
const LiquidWavesBackground(),
|
const LiquidWavesBackground(),
|
||||||
SafeArea(
|
SafeArea(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
@@ -129,7 +131,7 @@ class QuizChecklistScreen extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
onPressed: () => onAdvance(context),
|
onPressed: () => onAdvance(context),
|
||||||
child: const Text('Avançar'),
|
child: const Text(QuizUiStrings.advance),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import '../colors/app_colors.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
import '../screens/video_screen.dart';
|
import '../screens/video_screen.dart';
|
||||||
|
import '../strings/quiz_ui_strings.dart';
|
||||||
import '../widgets/entrance.dart';
|
import '../widgets/entrance.dart';
|
||||||
import '../widgets/liquid_waves_background.dart';
|
import '../widgets/liquid_waves_background.dart';
|
||||||
import '../widgets/tap_bounce.dart';
|
import '../widgets/tap_bounce.dart';
|
||||||
@@ -82,7 +84,7 @@ class QuizQuestionScreen extends StatefulWidget {
|
|||||||
this.suggestedVideoTitle,
|
this.suggestedVideoTitle,
|
||||||
this.fallbackColor,
|
this.fallbackColor,
|
||||||
this.answerImageAspectRatio,
|
this.answerImageAspectRatio,
|
||||||
this.correctBadgeLabel = 'Posição certa',
|
this.correctBadgeLabel = QuizUiStrings.defaultCorrectBadgeLabel,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String title;
|
final String title;
|
||||||
@@ -197,7 +199,7 @@ class _QuizQuestionScreenState extends State<QuizQuestionScreen> {
|
|||||||
body: Stack(
|
body: Stack(
|
||||||
clipBehavior: Clip.none,
|
clipBehavior: Clip.none,
|
||||||
children: [
|
children: [
|
||||||
Positioned.fill(child: Container(color: const Color(0xFFFAFAF7))),
|
Positioned.fill(child: Container(color: AppColors.background)),
|
||||||
const LiquidWavesBackground(),
|
const LiquidWavesBackground(),
|
||||||
SafeArea(
|
SafeArea(
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -222,7 +224,7 @@ class _QuizQuestionScreenState extends State<QuizQuestionScreen> {
|
|||||||
padding: EdgeInsets.all(9),
|
padding: EdgeInsets.all(9),
|
||||||
child: Icon(
|
child: Icon(
|
||||||
Icons.arrow_back_rounded,
|
Icons.arrow_back_rounded,
|
||||||
color: Color(0xFF2F9E94),
|
color: AppColors.teal,
|
||||||
size: 20,
|
size: 20,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -241,11 +243,11 @@ class _QuizQuestionScreenState extends State<QuizQuestionScreen> {
|
|||||||
1.0,
|
1.0,
|
||||||
),
|
),
|
||||||
minHeight: 8,
|
minHeight: 8,
|
||||||
backgroundColor: const Color(
|
backgroundColor: AppColors.pink.withValues(
|
||||||
0xFFFF55A7,
|
alpha: 0.15,
|
||||||
).withValues(alpha: 0.15),
|
),
|
||||||
valueColor: const AlwaysStoppedAnimation<Color>(
|
valueColor: const AlwaysStoppedAnimation<Color>(
|
||||||
Color(0xFFFF55A7),
|
AppColors.pink,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -304,7 +306,7 @@ class _QuizQuestionScreenState extends State<QuizQuestionScreen> {
|
|||||||
: _FallbackIconBlock(
|
: _FallbackIconBlock(
|
||||||
color:
|
color:
|
||||||
widget.fallbackColor ??
|
widget.fallbackColor ??
|
||||||
const Color(0xFF2F9E94),
|
AppColors.teal,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
if (hasSuggestedVideo) ...[
|
if (hasSuggestedVideo) ...[
|
||||||
@@ -317,7 +319,8 @@ class _QuizQuestionScreenState extends State<QuizQuestionScreen> {
|
|||||||
title:
|
title:
|
||||||
widget
|
widget
|
||||||
.suggestedVideoTitle ??
|
.suggestedVideoTitle ??
|
||||||
'Vídeo',
|
QuizUiStrings
|
||||||
|
.videoFallbackTitle,
|
||||||
description: '',
|
description: '',
|
||||||
videoPath: widget
|
videoPath: widget
|
||||||
.suggestedVideoPath,
|
.suggestedVideoPath,
|
||||||
@@ -328,13 +331,13 @@ class _QuizQuestionScreenState extends State<QuizQuestionScreen> {
|
|||||||
icon: const Icon(
|
icon: const Icon(
|
||||||
Icons
|
Icons
|
||||||
.play_circle_outline_rounded,
|
.play_circle_outline_rounded,
|
||||||
color: Color(0xFF2F9E94),
|
color: AppColors.teal,
|
||||||
),
|
),
|
||||||
label: Text(
|
label: Text(
|
||||||
widget.suggestedVideoTitle ??
|
widget.suggestedVideoTitle ??
|
||||||
'Ver vídeo (opcional)',
|
QuizUiStrings.watchOptionalVideo,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: Color(0xFF2F9E94),
|
color: AppColors.teal,
|
||||||
fontWeight: FontWeight.w800,
|
fontWeight: FontWeight.w800,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -347,7 +350,7 @@ class _QuizQuestionScreenState extends State<QuizQuestionScreen> {
|
|||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontWeight: FontWeight.w900,
|
fontWeight: FontWeight.w900,
|
||||||
color: Color(0xFFFF55A7),
|
color: AppColors.pink,
|
||||||
height: 1.2,
|
height: 1.2,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -355,11 +358,11 @@ class _QuizQuestionScreenState extends State<QuizQuestionScreen> {
|
|||||||
Text(
|
Text(
|
||||||
widget.answerType ==
|
widget.answerType ==
|
||||||
QuizAnswerType.number
|
QuizAnswerType.number
|
||||||
? 'Insira o número'
|
? QuizUiStrings.enterNumber
|
||||||
: widget.answerType ==
|
: widget.answerType ==
|
||||||
QuizAnswerType.yesNo
|
QuizAnswerType.yesNo
|
||||||
? 'Escolha uma opção'
|
? QuizUiStrings.chooseOneOption
|
||||||
: 'Escolha apenas uma opção',
|
: QuizUiStrings.chooseOnlyOneOption,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.black.withValues(
|
color: Colors.black.withValues(
|
||||||
@@ -495,9 +498,7 @@ class _QuizQuestionScreenState extends State<QuizQuestionScreen> {
|
|||||||
style:
|
style:
|
||||||
FilledButton.styleFrom(
|
FilledButton.styleFrom(
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
const Color(
|
AppColors.pink,
|
||||||
0xFFFF55A7,
|
|
||||||
),
|
|
||||||
foregroundColor:
|
foregroundColor:
|
||||||
Colors.white,
|
Colors.white,
|
||||||
shape:
|
shape:
|
||||||
@@ -628,8 +629,8 @@ class _QuizQuestionScreenState extends State<QuizQuestionScreen> {
|
|||||||
},
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
widget.isFinal
|
widget.isFinal
|
||||||
? 'Concluir'
|
? QuizUiStrings.finish
|
||||||
: 'Avançar',
|
: QuizUiStrings.advance,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -643,9 +644,8 @@ class _QuizQuestionScreenState extends State<QuizQuestionScreen> {
|
|||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
style: TextButton.styleFrom(
|
style: TextButton.styleFrom(
|
||||||
foregroundColor: const Color(
|
foregroundColor:
|
||||||
0xFF2F9E94,
|
AppColors.teal,
|
||||||
),
|
|
||||||
textStyle: const TextStyle(
|
textStyle: const TextStyle(
|
||||||
fontWeight: FontWeight.w800,
|
fontWeight: FontWeight.w800,
|
||||||
),
|
),
|
||||||
@@ -663,7 +663,7 @@ class _QuizQuestionScreenState extends State<QuizQuestionScreen> {
|
|||||||
(route) => route.isFirst,
|
(route) => route.isFirst,
|
||||||
),
|
),
|
||||||
child: const Text(
|
child: const Text(
|
||||||
'Voltar para homepage',
|
QuizUiStrings.backToHomepage,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -724,7 +724,7 @@ class _QuizQuestionScreenState extends State<QuizQuestionScreen> {
|
|||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
fontWeight: FontWeight.w900,
|
fontWeight: FontWeight.w900,
|
||||||
color: Color(0xFF2F9E94),
|
color: AppColors.teal,
|
||||||
),
|
),
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
@@ -750,10 +750,10 @@ class _QuizQuestionScreenState extends State<QuizQuestionScreen> {
|
|||||||
!_numberDontKnow) ...[
|
!_numberDontKnow) ...[
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
Text(
|
Text(
|
||||||
'Um número tão alto assim não é possível.\nO máximo é $_maxTeethCount.',
|
QuizUiStrings.teethCountTooHigh(_maxTeethCount),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: Color(0xFFFF55A7),
|
color: AppColors.pink,
|
||||||
fontWeight: FontWeight.w700,
|
fontWeight: FontWeight.w700,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
),
|
),
|
||||||
@@ -780,12 +780,12 @@ class _QuizQuestionScreenState extends State<QuizQuestionScreen> {
|
|||||||
),
|
),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: _numberDontKnow
|
color: _numberDontKnow
|
||||||
? const Color(0xFF2F9E94)
|
? AppColors.teal
|
||||||
: Colors.white.withValues(alpha: 0.70),
|
: Colors.white.withValues(alpha: 0.70),
|
||||||
borderRadius: BorderRadius.circular(999),
|
borderRadius: BorderRadius.circular(999),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: _numberDontKnow
|
color: _numberDontKnow
|
||||||
? const Color(0xFF2F9E94)
|
? AppColors.teal
|
||||||
: Colors.black.withValues(alpha: 0.12),
|
: Colors.black.withValues(alpha: 0.12),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -801,7 +801,7 @@ class _QuizQuestionScreenState extends State<QuizQuestionScreen> {
|
|||||||
),
|
),
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
Text(
|
Text(
|
||||||
'Não sei',
|
QuizUiStrings.dontKnow,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.w800,
|
fontWeight: FontWeight.w800,
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
@@ -828,7 +828,7 @@ class _QuizAnswerTile extends StatelessWidget {
|
|||||||
required this.onTap,
|
required this.onTap,
|
||||||
this.imageAspectRatio = 4 / 3,
|
this.imageAspectRatio = 4 / 3,
|
||||||
this.reveal = false,
|
this.reveal = false,
|
||||||
this.correctLabel = 'Posição certa',
|
this.correctLabel = QuizUiStrings.defaultCorrectBadgeLabel,
|
||||||
});
|
});
|
||||||
|
|
||||||
final QuizAnswer answer;
|
final QuizAnswer answer;
|
||||||
@@ -848,9 +848,9 @@ class _QuizAnswerTile extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final bool isCorrect = answer.weight == 1;
|
final bool isCorrect = answer.weight == 1;
|
||||||
final borderColor = reveal
|
final borderColor = reveal
|
||||||
? (isCorrect ? const Color(0xFF2F9E94) : const Color(0xFFFF55A7))
|
? (isCorrect ? AppColors.teal : AppColors.pink)
|
||||||
: selected
|
: selected
|
||||||
? const Color(0xFF2F9E94)
|
? AppColors.teal
|
||||||
: Colors.black.withValues(alpha: 0.12);
|
: Colors.black.withValues(alpha: 0.12);
|
||||||
final bg = selected
|
final bg = selected
|
||||||
? Colors.white.withValues(alpha: 0.88)
|
? Colors.white.withValues(alpha: 0.88)
|
||||||
@@ -934,7 +934,7 @@ class _QuizAnswerTile extends StatelessWidget {
|
|||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontWeight: FontWeight.w900,
|
fontWeight: FontWeight.w900,
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
color: Color(0xFF2F9E94),
|
color: AppColors.teal,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -955,7 +955,7 @@ class _QuizAnswerTile extends StatelessWidget {
|
|||||||
width: 22,
|
width: 22,
|
||||||
height: 22,
|
height: 22,
|
||||||
decoration: const BoxDecoration(
|
decoration: const BoxDecoration(
|
||||||
color: Color(0xFF2F9E94),
|
color: AppColors.teal,
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
),
|
),
|
||||||
child: const Icon(
|
child: const Icon(
|
||||||
@@ -981,8 +981,8 @@ class _QuizAnswerTile extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: isCorrect
|
color: isCorrect
|
||||||
? const Color(0xFF2F9E94)
|
? AppColors.teal
|
||||||
: const Color(0xFFFF55A7),
|
: AppColors.pink,
|
||||||
borderRadius: BorderRadius.circular(999),
|
borderRadius: BorderRadius.circular(999),
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
@@ -1004,7 +1004,7 @@ class _QuizAnswerTile extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
Text(
|
Text(
|
||||||
isCorrect ? correctLabel : 'Posição inadequada',
|
isCorrect ? correctLabel : QuizUiStrings.incorrectBadgeLabel,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontWeight: FontWeight.w800,
|
fontWeight: FontWeight.w800,
|
||||||
@@ -1057,7 +1057,7 @@ class _QuestionReferenceImages extends StatelessWidget {
|
|||||||
borderRadius: BorderRadius.circular(999),
|
borderRadius: BorderRadius.circular(999),
|
||||||
),
|
),
|
||||||
child: const Text(
|
child: const Text(
|
||||||
'Imagem de referência',
|
QuizUiStrings.referenceImage,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontWeight: FontWeight.w700,
|
fontWeight: FontWeight.w700,
|
||||||
@@ -1159,12 +1159,12 @@ class _QuizAnswerPill extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final accent = _isYes
|
final accent = _isYes
|
||||||
? const Color(0xFF2F9E94)
|
? AppColors.teal
|
||||||
: _isNo
|
: _isNo
|
||||||
? const Color(0xFFFF55A7)
|
? AppColors.pink
|
||||||
: Colors.black.withValues(alpha: 0.35);
|
: Colors.black.withValues(alpha: 0.35);
|
||||||
final borderColor = selected
|
final borderColor = selected
|
||||||
? const Color(0xFF2F9E94)
|
? AppColors.teal
|
||||||
: Colors.black.withValues(alpha: 0.10);
|
: Colors.black.withValues(alpha: 0.10);
|
||||||
final helpVideo = _helpVideo;
|
final helpVideo = _helpVideo;
|
||||||
final showHelp = selected && helpVideo != null;
|
final showHelp = selected && helpVideo != null;
|
||||||
@@ -1228,7 +1228,7 @@ class _QuizAnswerPill extends StatelessWidget {
|
|||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontWeight: FontWeight.w800,
|
fontWeight: FontWeight.w800,
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
color: Color(0xFF2F9E94),
|
color: AppColors.teal,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -1239,11 +1239,11 @@ class _QuizAnswerPill extends StatelessWidget {
|
|||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
color: selected
|
color: selected
|
||||||
? const Color(0xFF2F9E94)
|
? AppColors.teal
|
||||||
: Colors.transparent,
|
: Colors.transparent,
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: selected
|
color: selected
|
||||||
? const Color(0xFF2F9E94)
|
? AppColors.teal
|
||||||
: Colors.black.withValues(alpha: 0.25),
|
: Colors.black.withValues(alpha: 0.25),
|
||||||
width: 1.6,
|
width: 1.6,
|
||||||
),
|
),
|
||||||
@@ -1291,7 +1291,7 @@ class _HelpVideoButton extends StatelessWidget {
|
|||||||
return TapBounce(
|
return TapBounce(
|
||||||
scale: 0.97,
|
scale: 0.97,
|
||||||
child: Material(
|
child: Material(
|
||||||
color: const Color(0xFF2F9E94).withValues(alpha: 0.10),
|
color: AppColors.teal.withValues(alpha: 0.10),
|
||||||
borderRadius: BorderRadius.circular(14),
|
borderRadius: BorderRadius.circular(14),
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
borderRadius: BorderRadius.circular(14),
|
borderRadius: BorderRadius.circular(14),
|
||||||
@@ -1302,23 +1302,23 @@ class _HelpVideoButton extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
const Icon(
|
const Icon(
|
||||||
Icons.play_circle_fill_rounded,
|
Icons.play_circle_fill_rounded,
|
||||||
color: Color(0xFF2F9E94),
|
color: AppColors.teal,
|
||||||
size: 22,
|
size: 22,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
'Não tem a certeza? Veja o "${video.title}" para ajudar a responder',
|
QuizUiStrings.notSureSeeVideo(video.title),
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontWeight: FontWeight.w700,
|
fontWeight: FontWeight.w700,
|
||||||
fontSize: 12.5,
|
fontSize: 12.5,
|
||||||
color: Color(0xFF2F9E94),
|
color: AppColors.teal,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Icon(
|
const Icon(
|
||||||
Icons.chevron_right_rounded,
|
Icons.chevron_right_rounded,
|
||||||
color: Color(0xFF2F9E94),
|
color: AppColors.teal,
|
||||||
size: 20,
|
size: 20,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import '../colors/app_colors.dart';
|
||||||
|
|
||||||
import '../main.dart' show supabase;
|
import '../main.dart' show supabase;
|
||||||
import '../widgets/app_gradients.dart';
|
import '../colors/app_gradients.dart';
|
||||||
|
import '../strings/quiz_result_strings.dart';
|
||||||
|
import '../strings/quiz_ui_strings.dart';
|
||||||
import '../widgets/entrance.dart';
|
import '../widgets/entrance.dart';
|
||||||
import '../widgets/liquid_waves_background.dart';
|
import '../widgets/liquid_waves_background.dart';
|
||||||
import '../widgets/tap_bounce.dart';
|
import '../widgets/tap_bounce.dart';
|
||||||
@@ -106,20 +109,8 @@ class _QuizResultScreenState extends State<QuizResultScreen> {
|
|||||||
MaterialPageRoute<void>(
|
MaterialPageRoute<void>(
|
||||||
builder: (_) => QuizVideoGuideScreen(
|
builder: (_) => QuizVideoGuideScreen(
|
||||||
youtubeId: _resultGuideYoutubeId,
|
youtubeId: _resultGuideYoutubeId,
|
||||||
heading: 'Visualize o seguinte vídeo',
|
heading: QuizResultStrings.watchTheFollowingVideo,
|
||||||
caption:
|
caption: QuizResultStrings.resultVideoCaption,
|
||||||
'Assista para compreender a importância de um diagnóstico '
|
|
||||||
'precoce e de realizar tratamento ortodôntico intercetivo, '
|
|
||||||
'ou seja, uma intervenção realizada na infância, tipicamente '
|
|
||||||
'durante a fase de dentição mista (presença tanto de dentes '
|
|
||||||
'de leite como de permanentes).\n\nO seu propósito é '
|
|
||||||
'identificar, prevenir ou reduzir a gravidade de maloclusões '
|
|
||||||
'e problemas no desenvolvimento dos maxilares enquanto os '
|
|
||||||
'ossos e dentes ainda estão em crescimento.\n\nEsta '
|
|
||||||
'intervenção aproveita a fase de desenvolvimento craniofacial '
|
|
||||||
'para influenciar o crescimento ósseo, redirecionar a '
|
|
||||||
'mandíbula, corrigir o alinhamento dos maxilares ou criar '
|
|
||||||
'espaço para a erupção correta dos dentes permanentes.',
|
|
||||||
onAdvance: (context) =>
|
onAdvance: (context) =>
|
||||||
Navigator.of(context).popUntil((r) => r.isFirst),
|
Navigator.of(context).popUntil((r) => r.isFirst),
|
||||||
),
|
),
|
||||||
@@ -137,7 +128,7 @@ class _QuizResultScreenState extends State<QuizResultScreen> {
|
|||||||
body: Stack(
|
body: Stack(
|
||||||
clipBehavior: Clip.none,
|
clipBehavior: Clip.none,
|
||||||
children: [
|
children: [
|
||||||
Positioned.fill(child: Container(color: const Color(0xFFFAFAF7))),
|
Positioned.fill(child: Container(color: AppColors.background)),
|
||||||
const LiquidWavesBackground(),
|
const LiquidWavesBackground(),
|
||||||
SafeArea(
|
SafeArea(
|
||||||
child: Center(
|
child: Center(
|
||||||
@@ -157,12 +148,12 @@ class _QuizResultScreenState extends State<QuizResultScreen> {
|
|||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
FadeSlideIn(
|
FadeSlideIn(
|
||||||
child: const Text(
|
child: const Text(
|
||||||
'O resultado avaliado é de:',
|
QuizResultStrings.resultHeading,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontWeight: FontWeight.w900,
|
fontWeight: FontWeight.w900,
|
||||||
color: Color(0xFFFF55A7),
|
color: AppColors.pink,
|
||||||
height: 1.2,
|
height: 1.2,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -177,15 +168,16 @@ class _QuizResultScreenState extends State<QuizResultScreen> {
|
|||||||
_ResultRing(
|
_ResultRing(
|
||||||
value: signs,
|
value: signs,
|
||||||
max: kSignsMax,
|
max: kSignsMax,
|
||||||
color: const Color(0xFF2F9E94),
|
color: AppColors.teal,
|
||||||
label:
|
label:
|
||||||
'Sinais de má\noclusão instalados',
|
QuizResultStrings.signsRingLabel,
|
||||||
),
|
),
|
||||||
_ResultRing(
|
_ResultRing(
|
||||||
value: factors,
|
value: factors,
|
||||||
max: kFactorsMax,
|
max: kFactorsMax,
|
||||||
color: const Color(0xFFFF55A7),
|
color: AppColors.pink,
|
||||||
label: 'Fatores de\nrisco associados',
|
label:
|
||||||
|
QuizResultStrings.factorsRingLabel,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -194,14 +186,15 @@ class _QuizResultScreenState extends State<QuizResultScreen> {
|
|||||||
FadeSlideIn(
|
FadeSlideIn(
|
||||||
delay: const Duration(milliseconds: 100),
|
delay: const Duration(milliseconds: 100),
|
||||||
child: _ResultSection(
|
child: _ResultSection(
|
||||||
heading: 'Conclusões:',
|
heading:
|
||||||
body:
|
QuizResultStrings.conclusionsHeading,
|
||||||
'Foram identificados $signs de $kSignsMax '
|
body: QuizResultStrings.conclusionsBody(
|
||||||
'sinais de má oclusão já instalada e '
|
signs: signs,
|
||||||
'$factors de $kFactorsMax fatores de '
|
signsMax: kSignsMax,
|
||||||
'risco frequentemente associados a '
|
factors: factors,
|
||||||
'má oclusão.',
|
factorsMax: kFactorsMax,
|
||||||
bodyColor: const Color(0xFFFF55A7),
|
),
|
||||||
|
bodyColor: AppColors.pink,
|
||||||
bodyWeight: FontWeight.w800,
|
bodyWeight: FontWeight.w800,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -209,19 +202,23 @@ class _QuizResultScreenState extends State<QuizResultScreen> {
|
|||||||
FadeSlideIn(
|
FadeSlideIn(
|
||||||
delay: const Duration(milliseconds: 140),
|
delay: const Duration(milliseconds: 140),
|
||||||
child: _ResultSection(
|
child: _ResultSection(
|
||||||
heading: 'O que deve fazer agora',
|
heading:
|
||||||
|
QuizResultStrings.whatToDoNowHeading,
|
||||||
body: recommend
|
body: recommend
|
||||||
? 'Com base neste resultado, recomendamos a marcação de uma consulta com Odontopediatra ou Ortodontista para uma avaliação presencial.'
|
? QuizResultStrings
|
||||||
: 'Não foram encontrados sinais ou fatores de risco relevantes. Continue a acompanhar a saúde oral do seu filho/a com as consultas de rotina habituais.',
|
.whatToDoNowRecommend
|
||||||
|
: QuizResultStrings
|
||||||
|
.whatToDoNowNoConcerns,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
FadeSlideIn(
|
FadeSlideIn(
|
||||||
delay: const Duration(milliseconds: 180),
|
delay: const Duration(milliseconds: 180),
|
||||||
child: _ResultSection(
|
child: _ResultSection(
|
||||||
heading: 'Importante saber',
|
heading: QuizResultStrings
|
||||||
body:
|
.importantToKnowHeading,
|
||||||
'Este resultado não substitui uma avaliação presencial, nem tem função de diagnóstico. O objetivo é ajudá-lo a estar mais atento aos sinais identificados e a não adiar uma consulta que pode fazer a diferença no desenvolvimento oral do seu filho/a.',
|
body: QuizResultStrings
|
||||||
|
.importantToKnowBody,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -254,7 +251,7 @@ class _QuizResultScreenState extends State<QuizResultScreen> {
|
|||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
_goToVideo();
|
_goToVideo();
|
||||||
},
|
},
|
||||||
child: const Text('Avançar'),
|
child: const Text(QuizUiStrings.advance),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import '../colors/app_colors.dart';
|
||||||
import 'package:youtube_player_flutter/youtube_player_flutter.dart';
|
import 'package:youtube_player_flutter/youtube_player_flutter.dart';
|
||||||
|
|
||||||
|
import '../strings/quiz_ui_strings.dart';
|
||||||
import '../widgets/entrance.dart';
|
import '../widgets/entrance.dart';
|
||||||
import '../widgets/liquid_waves_background.dart';
|
import '../widgets/liquid_waves_background.dart';
|
||||||
import '../widgets/tap_bounce.dart';
|
import '../widgets/tap_bounce.dart';
|
||||||
|
|
||||||
const Color _pink = Color(0xFFFF55A7);
|
const Color _pink = AppColors.pink;
|
||||||
const Color _teal = Color(0xFF2F9E94);
|
const Color _teal = AppColors.teal;
|
||||||
|
|
||||||
/// Player do YouTube incorporado (sem AppBar/tela cheia própria) — usado
|
/// Player do YouTube incorporado (sem AppBar/tela cheia própria) — usado
|
||||||
/// tanto no ecrã intersticial [QuizVideoGuideScreen] como embutido
|
/// tanto no ecrã intersticial [QuizVideoGuideScreen] como embutido
|
||||||
@@ -78,9 +80,9 @@ class QuizVideoGuideScreen extends StatefulWidget {
|
|||||||
super.key,
|
super.key,
|
||||||
required this.youtubeId,
|
required this.youtubeId,
|
||||||
required this.onAdvance,
|
required this.onAdvance,
|
||||||
this.heading = 'Antes de continuar, veja o vídeo educativo...',
|
this.heading = QuizUiStrings.defaultVideoGuideHeading,
|
||||||
this.caption =
|
this.caption =
|
||||||
'Veja o vídeo para compreender melhor as próximas perguntas do questionário.',
|
QuizUiStrings.defaultVideoGuideCaption,
|
||||||
this.extraHeading,
|
this.extraHeading,
|
||||||
this.extraSubtitle,
|
this.extraSubtitle,
|
||||||
});
|
});
|
||||||
@@ -122,7 +124,7 @@ class _QuizVideoGuideScreenState extends State<QuizVideoGuideScreen> {
|
|||||||
body: Stack(
|
body: Stack(
|
||||||
clipBehavior: Clip.none,
|
clipBehavior: Clip.none,
|
||||||
children: [
|
children: [
|
||||||
Positioned.fill(child: Container(color: const Color(0xFFFAFAF7))),
|
Positioned.fill(child: Container(color: AppColors.background)),
|
||||||
const LiquidWavesBackground(),
|
const LiquidWavesBackground(),
|
||||||
SafeArea(
|
SafeArea(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
@@ -135,7 +137,7 @@ class _QuizVideoGuideScreenState extends State<QuizVideoGuideScreen> {
|
|||||||
child: TextButton(
|
child: TextButton(
|
||||||
onPressed: _advance,
|
onPressed: _advance,
|
||||||
child: const Text(
|
child: const Text(
|
||||||
'Pular',
|
QuizUiStrings.skip,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.black45,
|
color: Colors.black45,
|
||||||
fontWeight: FontWeight.w700,
|
fontWeight: FontWeight.w700,
|
||||||
@@ -243,7 +245,7 @@ class _QuizVideoGuideScreenState extends State<QuizVideoGuideScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
onPressed: _videoWatched ? _advance : null,
|
onPressed: _videoWatched ? _advance : null,
|
||||||
child: const Text('Avançar'),
|
child: const Text(QuizUiStrings.advance),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import '../colors/app_colors.dart';
|
||||||
|
|
||||||
import '../widgets/app_gradients.dart';
|
import '../colors/app_gradients.dart';
|
||||||
|
import '../strings/credits_strings.dart';
|
||||||
import '../widgets/entrance.dart';
|
import '../widgets/entrance.dart';
|
||||||
|
|
||||||
const Color _pink = Color(0xFFFF55A7);
|
const Color _pink = AppColors.pink;
|
||||||
const Color _teal = Color(0xFF2F9E94);
|
const Color _teal = AppColors.teal;
|
||||||
|
|
||||||
class _CreditPerson {
|
class _CreditPerson {
|
||||||
const _CreditPerson(this.name, this.role, {this.logoPath});
|
const _CreditPerson(this.name, this.role, {this.logoPath});
|
||||||
@@ -25,21 +27,27 @@ class _CreditSection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const List<_CreditSection> _kCreditSections = [
|
const List<_CreditSection> _kCreditSections = [
|
||||||
_CreditSection('Criadora original', [
|
_CreditSection(CreditsStrings.originalCreatorSection, [
|
||||||
_CreditPerson('Francisca Salgado Ferreira Pacheco Silva', 'Criadora original'),
|
_CreditPerson(
|
||||||
|
'Francisca Salgado Ferreira Pacheco Silva',
|
||||||
|
CreditsStrings.originalCreatorRole,
|
||||||
|
),
|
||||||
]),
|
]),
|
||||||
_CreditSection('Desenvolvimento informático', [
|
_CreditSection(CreditsStrings.developmentSection, [
|
||||||
_CreditPerson('Carlos Correia', 'Desenvolvedor'),
|
_CreditPerson('Carlos Correia', CreditsStrings.developerRole),
|
||||||
_CreditPerson('Fábio Ceia', 'Desenvolvedor'),
|
_CreditPerson('Fábio Ceia', CreditsStrings.developerRole),
|
||||||
_CreditPerson('Ruben Grandra', 'Desenvolvedor'),
|
_CreditPerson('Ruben Grandra', CreditsStrings.developerRole),
|
||||||
_CreditPerson('Dinis Maria', 'Desenvolvedor'),
|
_CreditPerson('Dinis Maria', CreditsStrings.developerRole),
|
||||||
]),
|
]),
|
||||||
_CreditSection('Orientadores', [
|
_CreditSection(CreditsStrings.advisorsSection, [
|
||||||
_CreditPerson('Augusta Pureza Alves Silveira', 'Orientador(a)'),
|
_CreditPerson('Augusta Pureza Alves Silveira', CreditsStrings.advisorRole),
|
||||||
_CreditPerson('Cristina Lopes Cardoso Silva', 'Orientador(a)'),
|
_CreditPerson('Cristina Lopes Cardoso Silva', CreditsStrings.advisorRole),
|
||||||
_CreditPerson('João Carlos Rodrigues L. Miranda', 'Orientador(a)'),
|
_CreditPerson(
|
||||||
|
'João Carlos Rodrigues L. Miranda',
|
||||||
|
CreditsStrings.advisorRole,
|
||||||
|
),
|
||||||
]),
|
]),
|
||||||
_CreditSection('Instituição colaboradora', [
|
_CreditSection(CreditsStrings.institutionSection, [
|
||||||
_CreditPerson('Universidade Fernando Pessoa', ''),
|
_CreditPerson('Universidade Fernando Pessoa', ''),
|
||||||
_CreditPerson(
|
_CreditPerson(
|
||||||
'Escola Profissional de Vila do Conde',
|
'Escola Profissional de Vila do Conde',
|
||||||
@@ -66,14 +74,14 @@ class CreditsScreen extends StatelessWidget {
|
|||||||
elevation: 0,
|
elevation: 0,
|
||||||
scrolledUnderElevation: 0,
|
scrolledUnderElevation: 0,
|
||||||
title: const Text(
|
title: const Text(
|
||||||
'Criadores e Colaboradores',
|
CreditsStrings.pageTitle,
|
||||||
style: TextStyle(fontWeight: FontWeight.w900),
|
style: TextStyle(fontWeight: FontWeight.w900),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
body: Container(
|
body: Container(
|
||||||
color: const Color(0xFFFAFAF7),
|
color: AppColors.background,
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
padding: const EdgeInsets.fromLTRB(20, 24, 20, 24),
|
padding: const EdgeInsets.fromLTRB(20, 24, 20, 24),
|
||||||
@@ -97,7 +105,7 @@ class CreditsScreen extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
const Text(
|
const Text(
|
||||||
'Quem fez a Check-Teeth Kids',
|
CreditsStrings.header,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import '../colors/app_colors.dart';
|
||||||
|
|
||||||
import '../widgets/app_gradients.dart';
|
import '../colors/app_gradients.dart';
|
||||||
|
import '../strings/curiosidade_strings.dart';
|
||||||
import '../widgets/entrance.dart';
|
import '../widgets/entrance.dart';
|
||||||
import '../widgets/liquid_waves_background.dart';
|
import '../widgets/liquid_waves_background.dart';
|
||||||
import '../widgets/tap_bounce.dart';
|
import '../widgets/tap_bounce.dart';
|
||||||
@@ -22,7 +24,7 @@ class CuriosidadeScreen extends StatelessWidget {
|
|||||||
elevation: 0,
|
elevation: 0,
|
||||||
scrolledUnderElevation: 0,
|
scrolledUnderElevation: 0,
|
||||||
title: const Text(
|
title: const Text(
|
||||||
'Curiosidades',
|
CuriosidadeStrings.pageTitle,
|
||||||
style: TextStyle(fontWeight: FontWeight.w900),
|
style: TextStyle(fontWeight: FontWeight.w900),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -31,7 +33,7 @@ class CuriosidadeScreen extends StatelessWidget {
|
|||||||
body: Stack(
|
body: Stack(
|
||||||
clipBehavior: Clip.none,
|
clipBehavior: Clip.none,
|
||||||
children: [
|
children: [
|
||||||
Positioned.fill(child: Container(color: const Color(0xFFFAFAF7))),
|
Positioned.fill(child: Container(color: AppColors.background)),
|
||||||
const LiquidWavesBackground(),
|
const LiquidWavesBackground(),
|
||||||
SafeArea(
|
SafeArea(
|
||||||
child: Align(
|
child: Align(
|
||||||
@@ -45,9 +47,8 @@ class CuriosidadeScreen extends StatelessWidget {
|
|||||||
child: TapBounce(
|
child: TapBounce(
|
||||||
scale: 0.97,
|
scale: 0.97,
|
||||||
child: _CuriosityTopicTile(
|
child: _CuriosityTopicTile(
|
||||||
title: 'Tema X',
|
title: CuriosidadeStrings.topicXTitle,
|
||||||
description:
|
description: CuriosidadeStrings.topicXDescription,
|
||||||
'Aprenda dicas rápidas e simples para cuidar dos dentes no dia a dia.',
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -57,8 +58,8 @@ class CuriosidadeScreen extends StatelessWidget {
|
|||||||
child: const TapBounce(
|
child: const TapBounce(
|
||||||
scale: 0.97,
|
scale: 0.97,
|
||||||
child: _CuriosityTopicTile(
|
child: _CuriosityTopicTile(
|
||||||
title: 'Tema Y',
|
title: CuriosidadeStrings.topicYTitle,
|
||||||
description: 'Conteúdo em breve.',
|
description: CuriosidadeStrings.contentComingSoon,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -68,8 +69,8 @@ class CuriosidadeScreen extends StatelessWidget {
|
|||||||
child: const TapBounce(
|
child: const TapBounce(
|
||||||
scale: 0.97,
|
scale: 0.97,
|
||||||
child: _CuriosityTopicTile(
|
child: _CuriosityTopicTile(
|
||||||
title: 'Tema Z',
|
title: CuriosidadeStrings.topicZTitle,
|
||||||
description: 'Conteúdo em breve.',
|
description: CuriosidadeStrings.contentComingSoon,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -79,8 +80,8 @@ class CuriosidadeScreen extends StatelessWidget {
|
|||||||
child: const TapBounce(
|
child: const TapBounce(
|
||||||
scale: 0.97,
|
scale: 0.97,
|
||||||
child: _CuriosityTopicTile(
|
child: _CuriosityTopicTile(
|
||||||
title: 'Tema U',
|
title: CuriosidadeStrings.topicUTitle,
|
||||||
description: 'Conteúdo em breve.',
|
description: CuriosidadeStrings.contentComingSoon,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -114,7 +115,7 @@ class _CuriosityTopicTile extends StatelessWidget {
|
|||||||
showModalBottomSheet<void>(
|
showModalBottomSheet<void>(
|
||||||
context: context,
|
context: context,
|
||||||
showDragHandle: true,
|
showDragHandle: true,
|
||||||
backgroundColor: const Color(0xFFFFE6F1),
|
backgroundColor: AppColors.pinkBackground,
|
||||||
shape: const RoundedRectangleBorder(
|
shape: const RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
|
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
|
||||||
),
|
),
|
||||||
@@ -132,7 +133,7 @@ class _CuriosityTopicTile extends StatelessWidget {
|
|||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontWeight: FontWeight.w900,
|
fontWeight: FontWeight.w900,
|
||||||
color: Color(0xFFFF55A7),
|
color: AppColors.pink,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
@@ -174,7 +175,7 @@ class _CuriosityTopicTile extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
onPressed: () => Navigator.of(ctx).pop(),
|
onPressed: () => Navigator.of(ctx).pop(),
|
||||||
child: const Text('Fechar'),
|
child: const Text(CuriosidadeStrings.close),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -195,12 +196,12 @@ class _CuriosityTopicTile extends StatelessWidget {
|
|||||||
width: 36,
|
width: 36,
|
||||||
height: 36,
|
height: 36,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: const Color(0xFFFF55A7).withValues(alpha: 0.12),
|
color: AppColors.pink.withValues(alpha: 0.12),
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
),
|
),
|
||||||
child: const Icon(
|
child: const Icon(
|
||||||
Icons.lightbulb_rounded,
|
Icons.lightbulb_rounded,
|
||||||
color: Color(0xFFFF55A7),
|
color: AppColors.pink,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
@@ -209,7 +210,7 @@ class _CuriosityTopicTile extends StatelessWidget {
|
|||||||
title,
|
title,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontWeight: FontWeight.w900,
|
fontWeight: FontWeight.w900,
|
||||||
color: Color(0xFF2F9E94),
|
color: AppColors.teal,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import '../colors/app_colors.dart';
|
||||||
|
import '../strings/splash_strings.dart';
|
||||||
|
|
||||||
class HelloSplashScreen extends StatefulWidget {
|
class HelloSplashScreen extends StatefulWidget {
|
||||||
const HelloSplashScreen({super.key, required this.onFinished, this.duration = const Duration(seconds: 5)});
|
const HelloSplashScreen({super.key, required this.onFinished, this.duration = const Duration(seconds: 5)});
|
||||||
@@ -76,7 +78,7 @@ class _HelloSplashScreenState extends State<HelloSplashScreen> with TickerProvid
|
|||||||
child: Container(
|
child: Container(
|
||||||
width: size.width,
|
width: size.width,
|
||||||
height: size.height,
|
height: size.height,
|
||||||
color: const Color(0xFFFAFAF7),
|
color: AppColors.background,
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -85,12 +87,12 @@ class _HelloSplashScreenState extends State<HelloSplashScreen> with TickerProvid
|
|||||||
ScaleTransition(
|
ScaleTransition(
|
||||||
scale: _pop,
|
scale: _pop,
|
||||||
child: const Text(
|
child: const Text(
|
||||||
'Olá',
|
SplashStrings.greeting,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 64,
|
fontSize: 64,
|
||||||
fontWeight: FontWeight.w900,
|
fontWeight: FontWeight.w900,
|
||||||
color: Color(0xFFFF9AD0),
|
color: AppColors.pinkLight,
|
||||||
height: 1.0,
|
height: 1.0,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import '../colors/app_colors.dart';
|
||||||
|
|
||||||
import '../main.dart' show supabase;
|
import '../main.dart' show supabase;
|
||||||
import '../privacy_gate_prefs.dart';
|
import '../privacy_gate_prefs.dart';
|
||||||
|
import '../strings/privacy_strings.dart';
|
||||||
import '../terms_gate_prefs.dart';
|
import '../terms_gate_prefs.dart';
|
||||||
import '../widgets/entrance.dart';
|
import '../widgets/entrance.dart';
|
||||||
import '../widgets/liquid_waves_background.dart';
|
import '../widgets/liquid_waves_background.dart';
|
||||||
@@ -87,7 +89,7 @@ class _PrivacyGateScreenState extends State<PrivacyGateScreen> {
|
|||||||
body: Stack(
|
body: Stack(
|
||||||
clipBehavior: Clip.none,
|
clipBehavior: Clip.none,
|
||||||
children: [
|
children: [
|
||||||
Positioned.fill(child: Container(color: const Color(0xFFFAFAF7))),
|
Positioned.fill(child: Container(color: AppColors.background)),
|
||||||
const LiquidWavesBackground(),
|
const LiquidWavesBackground(),
|
||||||
SafeArea(
|
SafeArea(
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -129,7 +131,7 @@ class _PrivacyGateScreenState extends State<PrivacyGateScreen> {
|
|||||||
child: TextButton(
|
child: TextButton(
|
||||||
onPressed: _declining ? null : _acceptAll,
|
onPressed: _declining ? null : _acceptAll,
|
||||||
child: const Text(
|
child: const Text(
|
||||||
'Aceitar tudo',
|
PrivacyStrings.acceptAll,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.w800,
|
fontWeight: FontWeight.w800,
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
@@ -250,8 +252,8 @@ class _PrivacyConsentRow extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: item.required
|
text: item.required
|
||||||
? '(Obrigatório) '
|
? PrivacyStrings.requiredPrefix
|
||||||
: '(Opcional) ',
|
: PrivacyStrings.optionalPrefix,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.w900,
|
fontWeight: FontWeight.w900,
|
||||||
color: item.required
|
color: item.required
|
||||||
@@ -302,7 +304,7 @@ class _AdvanceButton extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
onPressed: enabled ? onPressed : null,
|
onPressed: enabled ? onPressed : null,
|
||||||
child: const Text('Avançar'),
|
child: const Text(PrivacyStrings.advance),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import '../colors/app_colors.dart';
|
||||||
|
|
||||||
import '../main.dart' show supabase;
|
import '../main.dart' show supabase;
|
||||||
|
import '../strings/settings_strings.dart';
|
||||||
import '../widgets/app_dialogs.dart';
|
import '../widgets/app_dialogs.dart';
|
||||||
import '../widgets/entrance.dart';
|
import '../widgets/entrance.dart';
|
||||||
import '../widgets/pill_snackbar.dart';
|
import '../widgets/pill_snackbar.dart';
|
||||||
@@ -8,8 +10,8 @@ import '../widgets/tap_bounce.dart';
|
|||||||
import 'credits_screen.dart';
|
import 'credits_screen.dart';
|
||||||
import 'terms_screen.dart';
|
import 'terms_screen.dart';
|
||||||
|
|
||||||
const Color _teal = Color(0xFF2F9E94);
|
const Color _teal = AppColors.teal;
|
||||||
const Color _accentPink = Color(0xFFFF55A7);
|
const Color _accentPink = AppColors.pink;
|
||||||
|
|
||||||
/// Conteúdo da aba de Configurações, para ser embutido na bottom navigation
|
/// Conteúdo da aba de Configurações, para ser embutido na bottom navigation
|
||||||
/// do LoggedHomeScreen (sem Scaffold/AppBar próprios).
|
/// do LoggedHomeScreen (sem Scaffold/AppBar próprios).
|
||||||
@@ -32,13 +34,9 @@ class _SettingsBodyState extends State<SettingsBody> {
|
|||||||
Future<void> _confirmDeleteAccountData() async {
|
Future<void> _confirmDeleteAccountData() async {
|
||||||
final confirmed = await showConfirmDialog(
|
final confirmed = await showConfirmDialog(
|
||||||
context,
|
context,
|
||||||
title: 'Apagar dados da conta',
|
title: SettingsStrings.deleteAccountData,
|
||||||
message:
|
message: SettingsStrings.deleteAccountMessage,
|
||||||
'Isto remove permanentemente a sua conta, perfil, crianças '
|
confirmLabel: SettingsStrings.delete,
|
||||||
'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,
|
confirmColor: _accentPink,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -56,7 +54,8 @@ class _SettingsBodyState extends State<SettingsBody> {
|
|||||||
final errorMessage = (data is Map) ? data['error']?.toString() : null;
|
final errorMessage = (data is Map) ? data['error']?.toString() : null;
|
||||||
if (response.status != 200 || errorMessage != null) {
|
if (response.status != 200 || errorMessage != null) {
|
||||||
throw StateError(
|
throw StateError(
|
||||||
errorMessage ?? 'Erro ao apagar conta (status ${response.status})',
|
errorMessage ??
|
||||||
|
SettingsStrings.errorDeletingAccount(response.status),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,7 +63,7 @@ class _SettingsBodyState extends State<SettingsBody> {
|
|||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
Navigator.of(context).popUntil((route) => route.isFirst);
|
Navigator.of(context).popUntil((route) => route.isFirst);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (mounted) showPillSnackBar(context, 'Erro ao apagar: $e');
|
if (mounted) showPillSnackBar(context, SettingsStrings.errorDeleting(e));
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) setState(() => _deletingAccount = false);
|
if (mounted) setState(() => _deletingAccount = false);
|
||||||
}
|
}
|
||||||
@@ -83,18 +82,18 @@ class _SettingsBodyState extends State<SettingsBody> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
_SectionLabel('Conta'),
|
_SectionLabel(SettingsStrings.account),
|
||||||
_SettingsCard(
|
_SettingsCard(
|
||||||
children: [
|
children: [
|
||||||
_InfoTile(
|
_InfoTile(
|
||||||
icon: Icons.person_outline_rounded,
|
icon: Icons.person_outline_rounded,
|
||||||
title: name.isEmpty ? 'Sem nome' : name,
|
title: name.isEmpty ? SettingsStrings.noName : name,
|
||||||
subtitle: email,
|
subtitle: email,
|
||||||
),
|
),
|
||||||
const Divider(height: 1),
|
const Divider(height: 1),
|
||||||
_ActionTile(
|
_ActionTile(
|
||||||
icon: Icons.logout_rounded,
|
icon: Icons.logout_rounded,
|
||||||
title: 'Sair',
|
title: SettingsStrings.signOut,
|
||||||
onTap: _signOut,
|
onTap: _signOut,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -108,12 +107,12 @@ class _SettingsBodyState extends State<SettingsBody> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
_SectionLabel('Sobre'),
|
_SectionLabel(SettingsStrings.about),
|
||||||
_SettingsCard(
|
_SettingsCard(
|
||||||
children: [
|
children: [
|
||||||
_ActionTile(
|
_ActionTile(
|
||||||
icon: Icons.description_outlined,
|
icon: Icons.description_outlined,
|
||||||
title: 'Termos de Serviço',
|
title: SettingsStrings.termsOfService,
|
||||||
onTap: () => Navigator.of(context).push(
|
onTap: () => Navigator.of(context).push(
|
||||||
MaterialPageRoute<void>(
|
MaterialPageRoute<void>(
|
||||||
builder: (_) => const TermsScreen(),
|
builder: (_) => const TermsScreen(),
|
||||||
@@ -123,7 +122,7 @@ class _SettingsBodyState extends State<SettingsBody> {
|
|||||||
const Divider(height: 1),
|
const Divider(height: 1),
|
||||||
_ActionTile(
|
_ActionTile(
|
||||||
icon: Icons.diversity_3_outlined,
|
icon: Icons.diversity_3_outlined,
|
||||||
title: 'Criadores e colaboradores',
|
title: SettingsStrings.creatorsAndContributors,
|
||||||
onTap: () => Navigator.of(context).push(
|
onTap: () => Navigator.of(context).push(
|
||||||
MaterialPageRoute<void>(
|
MaterialPageRoute<void>(
|
||||||
builder: (_) => const CreditsScreen(),
|
builder: (_) => const CreditsScreen(),
|
||||||
@@ -133,7 +132,7 @@ class _SettingsBodyState extends State<SettingsBody> {
|
|||||||
const Divider(height: 1),
|
const Divider(height: 1),
|
||||||
const _InfoTile(
|
const _InfoTile(
|
||||||
icon: Icons.info_outline_rounded,
|
icon: Icons.info_outline_rounded,
|
||||||
title: 'Versão do app',
|
title: SettingsStrings.appVersion,
|
||||||
subtitle: '1.0.0',
|
subtitle: '1.0.0',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -147,12 +146,12 @@ class _SettingsBodyState extends State<SettingsBody> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
_SectionLabel('Zona de risco'),
|
_SectionLabel(SettingsStrings.dangerZone),
|
||||||
_SettingsCard(
|
_SettingsCard(
|
||||||
children: [
|
children: [
|
||||||
_ActionTile(
|
_ActionTile(
|
||||||
icon: Icons.delete_forever_rounded,
|
icon: Icons.delete_forever_rounded,
|
||||||
title: 'Apagar dados da conta',
|
title: SettingsStrings.deleteAccountData,
|
||||||
titleColor: _accentPink,
|
titleColor: _accentPink,
|
||||||
loading: _deletingAccount,
|
loading: _deletingAccount,
|
||||||
onTap: _deletingAccount ? null : _confirmDeleteAccountData,
|
onTap: _deletingAccount ? null : _confirmDeleteAccountData,
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import '../colors/app_colors.dart';
|
||||||
|
|
||||||
import '../main.dart' show supabase;
|
import '../main.dart' show supabase;
|
||||||
|
import '../strings/terms_strings.dart';
|
||||||
import '../terms_gate_prefs.dart';
|
import '../terms_gate_prefs.dart';
|
||||||
import '../widgets/entrance.dart';
|
import '../widgets/entrance.dart';
|
||||||
import '../widgets/liquid_waves_background.dart';
|
import '../widgets/liquid_waves_background.dart';
|
||||||
@@ -62,7 +64,7 @@ class _TermsGateScreenState extends State<TermsGateScreen> {
|
|||||||
body: Stack(
|
body: Stack(
|
||||||
clipBehavior: Clip.none,
|
clipBehavior: Clip.none,
|
||||||
children: [
|
children: [
|
||||||
Positioned.fill(child: Container(color: const Color(0xFFFAFAF7))),
|
Positioned.fill(child: Container(color: AppColors.background)),
|
||||||
const LiquidWavesBackground(),
|
const LiquidWavesBackground(),
|
||||||
SafeArea(
|
SafeArea(
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -173,7 +175,7 @@ class _AcceptCheckboxRow extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
const Text(
|
const Text(
|
||||||
'Aceitar tudo',
|
TermsStrings.acceptAll,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.w800,
|
fontWeight: FontWeight.w800,
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
@@ -212,7 +214,7 @@ class _AdvanceButton extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
onPressed: enabled ? onPressed : null,
|
onPressed: enabled ? onPressed : null,
|
||||||
child: const Text('Avançar'),
|
child: const Text(TermsStrings.advance),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import '../colors/app_colors.dart';
|
||||||
|
|
||||||
import '../widgets/app_gradients.dart';
|
import '../colors/app_gradients.dart';
|
||||||
|
import '../strings/terms_strings.dart';
|
||||||
import '../widgets/terms_content.dart';
|
import '../widgets/terms_content.dart';
|
||||||
|
|
||||||
class TermsScreen extends StatelessWidget {
|
class TermsScreen extends StatelessWidget {
|
||||||
@@ -20,14 +22,14 @@ class TermsScreen extends StatelessWidget {
|
|||||||
elevation: 0,
|
elevation: 0,
|
||||||
scrolledUnderElevation: 0,
|
scrolledUnderElevation: 0,
|
||||||
title: const Text(
|
title: const Text(
|
||||||
'Termos de Serviço',
|
TermsStrings.pageTitle,
|
||||||
style: TextStyle(fontWeight: FontWeight.w900),
|
style: TextStyle(fontWeight: FontWeight.w900),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
body: Container(
|
body: Container(
|
||||||
color: const Color(0xFFFAFAF7),
|
color: AppColors.background,
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
padding: const EdgeInsets.fromLTRB(20, 24, 20, 24),
|
padding: const EdgeInsets.fromLTRB(20, 24, 20, 24),
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import '../colors/app_colors.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:video_player/video_player.dart';
|
import 'package:video_player/video_player.dart';
|
||||||
import 'package:youtube_player_flutter/youtube_player_flutter.dart';
|
import 'package:youtube_player_flutter/youtube_player_flutter.dart';
|
||||||
|
|
||||||
import '../watched_videos_prefs.dart';
|
import '../watched_videos_prefs.dart';
|
||||||
import '../widgets/app_gradients.dart';
|
import '../colors/app_gradients.dart';
|
||||||
|
import '../strings/video_strings.dart';
|
||||||
import '../widgets/entrance.dart';
|
import '../widgets/entrance.dart';
|
||||||
import '../widgets/liquid_waves_background.dart';
|
import '../widgets/liquid_waves_background.dart';
|
||||||
import '../widgets/pill_snackbar.dart';
|
import '../widgets/pill_snackbar.dart';
|
||||||
@@ -36,83 +38,80 @@ class VideoData {
|
|||||||
final List<VideoData> videoList = [
|
final List<VideoData> videoList = [
|
||||||
VideoData(
|
VideoData(
|
||||||
id: 1,
|
id: 1,
|
||||||
title: 'Episódio 1',
|
title: VideoStrings.episodeTitle(1),
|
||||||
description: 'Qual a Influência do nariz entupido na má oclusão',
|
description: VideoStrings.episodeDescriptions[0],
|
||||||
youtubeId: 'PJ58CZv4ECw',
|
youtubeId: 'PJ58CZv4ECw',
|
||||||
),
|
),
|
||||||
VideoData(
|
VideoData(
|
||||||
id: 2,
|
id: 2,
|
||||||
title: 'Episódio 2',
|
title: VideoStrings.episodeTitle(2),
|
||||||
description: 'Qual a Influência das alergias sazionais na má oclusão',
|
description: VideoStrings.episodeDescriptions[1],
|
||||||
youtubeId: 'y4_kWmZtAtg',
|
youtubeId: 'y4_kWmZtAtg',
|
||||||
),
|
),
|
||||||
VideoData(
|
VideoData(
|
||||||
id: 3,
|
id: 3,
|
||||||
title: 'Episódio 3',
|
title: VideoStrings.episodeTitle(3),
|
||||||
description: 'Qual a Influência das Otites frequentes na má oclusão',
|
description: VideoStrings.episodeDescriptions[2],
|
||||||
youtubeId: 'nD75Y5PuKTo',
|
youtubeId: 'nD75Y5PuKTo',
|
||||||
),
|
),
|
||||||
VideoData(
|
VideoData(
|
||||||
id: 4,
|
id: 4,
|
||||||
title: 'Episódio 4',
|
title: VideoStrings.episodeTitle(4),
|
||||||
description: 'Qual a Influência das Amigdalites recorrentes na má oclusão',
|
description: VideoStrings.episodeDescriptions[3],
|
||||||
youtubeId: 'yvFllWYeuLw',
|
youtubeId: 'yvFllWYeuLw',
|
||||||
),
|
),
|
||||||
VideoData(
|
VideoData(
|
||||||
id: 5,
|
id: 5,
|
||||||
title: 'Episódio 5',
|
title: VideoStrings.episodeTitle(5),
|
||||||
description:
|
description: VideoStrings.episodeDescriptions[4],
|
||||||
'Qual a Influência das Bronquiolites recorrentes na má oclusão',
|
|
||||||
youtubeId: 'DnhUa-T8_Ps',
|
youtubeId: 'DnhUa-T8_Ps',
|
||||||
),
|
),
|
||||||
VideoData(
|
VideoData(
|
||||||
id: 6,
|
id: 6,
|
||||||
title: 'Episódio 6',
|
title: VideoStrings.episodeTitle(6),
|
||||||
description: 'Qual a Influência dos problemas respitatórios na má oclusão',
|
description: VideoStrings.episodeDescriptions[5],
|
||||||
youtubeId: 'zKt_iwkrjvo',
|
youtubeId: 'zKt_iwkrjvo',
|
||||||
),
|
),
|
||||||
VideoData(
|
VideoData(
|
||||||
id: 7,
|
id: 7,
|
||||||
title: 'Episódio 7',
|
title: VideoStrings.episodeTitle(7),
|
||||||
description:
|
description: VideoStrings.episodeDescriptions[6],
|
||||||
'Qual a Influência das interrupções respiratórias na má oclusão',
|
|
||||||
youtubeId: 'NpmQ2brap5A',
|
youtubeId: 'NpmQ2brap5A',
|
||||||
),
|
),
|
||||||
VideoData(
|
VideoData(
|
||||||
id: 8,
|
id: 8,
|
||||||
title: 'Episódio 8',
|
title: VideoStrings.episodeTitle(8),
|
||||||
description: 'Qual a Influência do ressonar na má oclusão',
|
description: VideoStrings.episodeDescriptions[7],
|
||||||
youtubeId: 'Wj3KYw9pBi0',
|
youtubeId: 'Wj3KYw9pBi0',
|
||||||
),
|
),
|
||||||
VideoData(
|
VideoData(
|
||||||
id: 9,
|
id: 9,
|
||||||
title: 'Episódio 9',
|
title: VideoStrings.episodeTitle(9),
|
||||||
description:
|
description: VideoStrings.episodeDescriptions[8],
|
||||||
'Qual a Influência de acordar com saliva seca na boca ou na almofada na saúde oral',
|
|
||||||
youtubeId: 'bOm9t61cT_U',
|
youtubeId: 'bOm9t61cT_U',
|
||||||
),
|
),
|
||||||
VideoData(
|
VideoData(
|
||||||
id: 10,
|
id: 10,
|
||||||
title: 'Episódio 10',
|
title: VideoStrings.episodeTitle(10),
|
||||||
description: 'Qual a Influência da respiração oral na má oclusão',
|
description: VideoStrings.episodeDescriptions[9],
|
||||||
youtubeId: 'fAitMizbcms',
|
youtubeId: 'fAitMizbcms',
|
||||||
),
|
),
|
||||||
VideoData(
|
VideoData(
|
||||||
id: 11,
|
id: 11,
|
||||||
title: 'Episódio 11',
|
title: VideoStrings.episodeTitle(11),
|
||||||
description: 'Qual a influência do uso exagerado da chupeta na má oclusão',
|
description: VideoStrings.episodeDescriptions[10],
|
||||||
youtubeId: '6sYoBUjks_I',
|
youtubeId: '6sYoBUjks_I',
|
||||||
),
|
),
|
||||||
VideoData(
|
VideoData(
|
||||||
id: 12,
|
id: 12,
|
||||||
title: 'Episódio 12',
|
title: VideoStrings.episodeTitle(12),
|
||||||
description: 'Qual a influência do uso exagerado da chupeta na má oclusão',
|
description: VideoStrings.episodeDescriptions[11],
|
||||||
youtubeId: 'eznKrErQbHo',
|
youtubeId: 'eznKrErQbHo',
|
||||||
),
|
),
|
||||||
VideoData(
|
VideoData(
|
||||||
id: 13,
|
id: 13,
|
||||||
title: 'Episódio 13',
|
title: VideoStrings.episodeTitle(13),
|
||||||
description: 'Qual a influência do hábito de chuchar o dedo na má oclusão',
|
description: VideoStrings.episodeDescriptions[12],
|
||||||
youtubeId: 'VO9CNqHRdeM',
|
youtubeId: 'VO9CNqHRdeM',
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
@@ -176,7 +175,7 @@ Future<void> showVideoPlayerDialog(
|
|||||||
}) {
|
}) {
|
||||||
if (video.youtubeId != null) {
|
if (video.youtubeId != null) {
|
||||||
if (video.youtubeId!.isEmpty) {
|
if (video.youtubeId!.isEmpty) {
|
||||||
showPillSnackBar(context, 'Vídeo ainda não disponível');
|
showPillSnackBar(context, VideoStrings.videoNotAvailable);
|
||||||
return Future.value();
|
return Future.value();
|
||||||
}
|
}
|
||||||
return Navigator.of(context).push<void>(
|
return Navigator.of(context).push<void>(
|
||||||
@@ -199,8 +198,8 @@ class VideoScreen extends StatefulWidget {
|
|||||||
/// guardar localmente quais episódios ela já assistiu até ao fim.
|
/// guardar localmente quais episódios ela já assistiu até ao fim.
|
||||||
final String? scopeId;
|
final String? scopeId;
|
||||||
|
|
||||||
static const Color _teal = Color(0xFF2F9E94);
|
static const Color _teal = AppColors.teal;
|
||||||
static const Color _accentPink = Color(0xFFFF55A7);
|
static const Color _accentPink = AppColors.pink;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<VideoScreen> createState() => _VideoScreenState();
|
State<VideoScreen> createState() => _VideoScreenState();
|
||||||
@@ -253,7 +252,7 @@ class _VideoScreenState extends State<VideoScreen> {
|
|||||||
elevation: 0,
|
elevation: 0,
|
||||||
scrolledUnderElevation: 0,
|
scrolledUnderElevation: 0,
|
||||||
title: const Text(
|
title: const Text(
|
||||||
'Vídeos Educativos',
|
VideoStrings.pageTitle,
|
||||||
style: TextStyle(fontWeight: FontWeight.w900),
|
style: TextStyle(fontWeight: FontWeight.w900),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -262,7 +261,7 @@ class _VideoScreenState extends State<VideoScreen> {
|
|||||||
body: Stack(
|
body: Stack(
|
||||||
clipBehavior: Clip.none,
|
clipBehavior: Clip.none,
|
||||||
children: [
|
children: [
|
||||||
Positioned.fill(child: Container(color: const Color(0xFFFAFAF7))),
|
Positioned.fill(child: Container(color: AppColors.background)),
|
||||||
const LiquidWavesBackground(),
|
const LiquidWavesBackground(),
|
||||||
SafeArea(
|
SafeArea(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
@@ -285,7 +284,7 @@ class _VideoScreenState extends State<VideoScreen> {
|
|||||||
child: TextField(
|
child: TextField(
|
||||||
controller: _searchController,
|
controller: _searchController,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: 'Pesquisar vídeos...',
|
hintText: VideoStrings.searchHint,
|
||||||
prefixIcon: const Icon(
|
prefixIcon: const Icon(
|
||||||
Icons.search,
|
Icons.search,
|
||||||
color: VideoScreen._teal,
|
color: VideoScreen._teal,
|
||||||
@@ -307,7 +306,7 @@ class _VideoScreenState extends State<VideoScreen> {
|
|||||||
child: _filteredVideos.isEmpty
|
child: _filteredVideos.isEmpty
|
||||||
? Center(
|
? Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
'Nenhum vídeo encontrado',
|
VideoStrings.noVideosFound,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
@@ -546,7 +545,7 @@ class _VideoButton extends StatelessWidget {
|
|||||||
fit: StackFit.expand,
|
fit: StackFit.expand,
|
||||||
children: [
|
children: [
|
||||||
ColoredBox(
|
ColoredBox(
|
||||||
color: const Color(0xFFFFE6F1),
|
color: AppColors.pinkBackground,
|
||||||
child: VideoThumbnail(
|
child: VideoThumbnail(
|
||||||
video: video,
|
video: video,
|
||||||
borderRadius: 0,
|
borderRadius: 0,
|
||||||
@@ -593,7 +592,7 @@ class _VideoButton extends StatelessWidget {
|
|||||||
borderRadius: BorderRadius.circular(999),
|
borderRadius: BorderRadius.circular(999),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
'EP. ${video.id}',
|
VideoStrings.episodeBadge(video.id),
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontWeight: FontWeight.w800,
|
fontWeight: FontWeight.w800,
|
||||||
@@ -731,7 +730,7 @@ class _YoutubePlayerPageState extends State<_YoutubePlayerPage>
|
|||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
backgroundColor: value.isFullScreen
|
backgroundColor: value.isFullScreen
|
||||||
? Colors.black
|
? Colors.black
|
||||||
: const Color(0xFFFAFAF7),
|
: AppColors.background,
|
||||||
appBar: value.isFullScreen
|
appBar: value.isFullScreen
|
||||||
? null
|
? null
|
||||||
: PreferredSize(
|
: PreferredSize(
|
||||||
@@ -808,7 +807,7 @@ class _YoutubePlayerPageState extends State<_YoutubePlayerPage>
|
|||||||
MainAxisAlignment.spaceBetween,
|
MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
const Text(
|
const Text(
|
||||||
'Próximos',
|
VideoStrings.nextVideos,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.w900,
|
fontWeight: FontWeight.w900,
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
@@ -835,7 +834,7 @@ class _YoutubePlayerPageState extends State<_YoutubePlayerPage>
|
|||||||
vertical: 2,
|
vertical: 2,
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
'Ver mais',
|
VideoStrings.seeMore,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.w800,
|
fontWeight: FontWeight.w800,
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
@@ -1033,7 +1032,7 @@ class _VideoPlayerDialogState extends State<_VideoPlayerDialog> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_isInitialized = false;
|
_isInitialized = false;
|
||||||
});
|
});
|
||||||
showPillSnackBar(context, 'Erro ao carregar vídeo: $e');
|
showPillSnackBar(context, VideoStrings.errorLoadingVideo(e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1188,7 +1187,7 @@ class _VideoControlsState extends State<_VideoControls> {
|
|||||||
Icons.replay_10_rounded,
|
Icons.replay_10_rounded,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
),
|
),
|
||||||
tooltip: 'Retroceder 10s',
|
tooltip: VideoStrings.rewind10s,
|
||||||
onPressed: () => _seekBy(const Duration(seconds: -10)),
|
onPressed: () => _seekBy(const Duration(seconds: -10)),
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
@@ -1211,7 +1210,7 @@ class _VideoControlsState extends State<_VideoControls> {
|
|||||||
Icons.forward_10_rounded,
|
Icons.forward_10_rounded,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
),
|
),
|
||||||
tooltip: 'Avançar 10s',
|
tooltip: VideoStrings.forward10s,
|
||||||
onPressed: () => _seekBy(const Duration(seconds: 10)),
|
onPressed: () => _seekBy(const Duration(seconds: 10)),
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
@@ -1294,7 +1293,7 @@ class _FullscreenVideoPlayerState extends State<_FullscreenVideoPlayer> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_isInitialized = false;
|
_isInitialized = false;
|
||||||
});
|
});
|
||||||
showPillSnackBar(context, 'Erro ao carregar vídeo: $e');
|
showPillSnackBar(context, VideoStrings.errorLoadingVideo(e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
38
lib/strings/auth_strings.dart
Normal file
38
lib/strings/auth_strings.dart
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
/// Texto do ecrã de login/criação de conta ([HomeScreen]).
|
||||||
|
class AuthStrings {
|
||||||
|
const AuthStrings._();
|
||||||
|
|
||||||
|
static const String appTitle = 'Check-Teeth Kids';
|
||||||
|
static const String appSubtitle =
|
||||||
|
'Deteção e Prevenção da Má Oclusão Infantil';
|
||||||
|
|
||||||
|
static const String login = 'Entrar';
|
||||||
|
static const String createAccount = 'Criar Conta';
|
||||||
|
|
||||||
|
static const String nameHint = 'Introduza o seu nome';
|
||||||
|
static const String nameRequired = 'Indique o seu nome';
|
||||||
|
static const String nameTooShort = 'Nome muito curto';
|
||||||
|
static const String nameNoNumbers = 'O nome não pode conter números';
|
||||||
|
|
||||||
|
static const String emailHint = 'Introduza o seu email';
|
||||||
|
static const String emailRequired = 'Indique o seu email';
|
||||||
|
static const String emailInvalid = 'Email inválido';
|
||||||
|
|
||||||
|
static const String passwordHint = 'Introduza a sua palavra-passe';
|
||||||
|
static const String passwordRequired = 'Indique a sua palavra-passe';
|
||||||
|
static const String passwordTooShort = 'Mínimo de 6 caracteres';
|
||||||
|
|
||||||
|
static const String userNotFoundAfterSignUp =
|
||||||
|
'Utilizador não encontrado após criar a conta.';
|
||||||
|
static const String accountNoLongerExists =
|
||||||
|
'Esta conta já não existe. Verifique o email ou crie uma nova conta.';
|
||||||
|
static const String timeoutError =
|
||||||
|
'Tempo esgotado. Verifique a sua ligação e tente novamente.';
|
||||||
|
static const String invalidCredentials = 'Email ou palavra-passe incorretos.';
|
||||||
|
static const String userNotFound = 'Utilizador não encontrado.';
|
||||||
|
static const String emailAlreadyInUse = 'Este email já está em uso.';
|
||||||
|
static const String weakPassword =
|
||||||
|
'Palavra-passe fraca. Utilize pelo menos 6 caracteres.';
|
||||||
|
|
||||||
|
static String genericError(Object e) => 'Erro: $e';
|
||||||
|
}
|
||||||
7
lib/strings/common_strings.dart
Normal file
7
lib/strings/common_strings.dart
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/// Texto de interface genérico, partilhado por widgets reutilizáveis que não
|
||||||
|
/// pertencem a um ecrã específico (ex.: [showConfirmDialog]).
|
||||||
|
class CommonStrings {
|
||||||
|
const CommonStrings._();
|
||||||
|
|
||||||
|
static const String cancel = 'Cancelar';
|
||||||
|
}
|
||||||
18
lib/strings/credits_strings.dart
Normal file
18
lib/strings/credits_strings.dart
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
/// Texto fixo do ecrã de créditos ([CreditsScreen]) — os nomes próprios e
|
||||||
|
/// da instituição em [_kCreditSections] ficam de fora de propósito (não são
|
||||||
|
/// texto de interface a traduzir, são dados/factos).
|
||||||
|
class CreditsStrings {
|
||||||
|
const CreditsStrings._();
|
||||||
|
|
||||||
|
static const String pageTitle = 'Criadores e Colaboradores';
|
||||||
|
static const String header = 'Quem fez a Check-Teeth Kids';
|
||||||
|
|
||||||
|
static const String originalCreatorSection = 'Criadora original';
|
||||||
|
static const String developmentSection = 'Desenvolvimento informático';
|
||||||
|
static const String advisorsSection = 'Orientadores';
|
||||||
|
static const String institutionSection = 'Instituição colaboradora';
|
||||||
|
|
||||||
|
static const String originalCreatorRole = 'Criadora original';
|
||||||
|
static const String developerRole = 'Desenvolvedor';
|
||||||
|
static const String advisorRole = 'Orientador(a)';
|
||||||
|
}
|
||||||
16
lib/strings/curiosidade_strings.dart
Normal file
16
lib/strings/curiosidade_strings.dart
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
/// Texto do ecrã de curiosidades ([CuriosidadeScreen]).
|
||||||
|
class CuriosidadeStrings {
|
||||||
|
const CuriosidadeStrings._();
|
||||||
|
|
||||||
|
static const String pageTitle = 'Curiosidades';
|
||||||
|
static const String close = 'Fechar';
|
||||||
|
|
||||||
|
static const String topicXTitle = 'Tema X';
|
||||||
|
static const String topicXDescription =
|
||||||
|
'Aprenda dicas rápidas e simples para cuidar dos dentes no dia a dia.';
|
||||||
|
|
||||||
|
static const String topicYTitle = 'Tema Y';
|
||||||
|
static const String topicZTitle = 'Tema Z';
|
||||||
|
static const String topicUTitle = 'Tema U';
|
||||||
|
static const String contentComingSoon = 'Conteúdo em breve.';
|
||||||
|
}
|
||||||
109
lib/strings/home_strings.dart
Normal file
109
lib/strings/home_strings.dart
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
/// Texto do ecrã principal depois de autenticado ([LoggedHomeScreen] e as
|
||||||
|
/// suas três abas: Início, Perfil, Ajustes).
|
||||||
|
class HomeStrings {
|
||||||
|
const HomeStrings._();
|
||||||
|
|
||||||
|
static const String noName = 'Sem nome';
|
||||||
|
static const String goodMorning = 'Bom dia';
|
||||||
|
static const String goodAfternoon = 'Boa tarde';
|
||||||
|
static const String goodEvening = 'Boa noite';
|
||||||
|
|
||||||
|
static const String navHome = 'Início';
|
||||||
|
static const String navProfile = 'Perfil';
|
||||||
|
static const String navSettings = 'Ajustes';
|
||||||
|
static const String settingsTitle = 'Configurações';
|
||||||
|
|
||||||
|
static const String signsGaugeLabel = 'Sinais de má\noclusão';
|
||||||
|
static const String factorsGaugeLabel = 'Fatores de risco';
|
||||||
|
|
||||||
|
static String forChild(String name) => 'Para $name';
|
||||||
|
|
||||||
|
static const String educationalVideos = 'Vídeos educativos';
|
||||||
|
static const String moreFeaturesSoon = 'Mais funcionalidades em breve';
|
||||||
|
|
||||||
|
static String brushingLimitReached(int maxPerDay) =>
|
||||||
|
'Já registou as $maxPerDay escovagens de hoje!';
|
||||||
|
static const String brushingLogged = 'Escovagem registada!';
|
||||||
|
static const String brushingThisWeek = 'Escovagens esta semana';
|
||||||
|
static const String completedEpisodes = 'Episódios completos';
|
||||||
|
|
||||||
|
static String errorAddingChild(Object e) => 'Erro ao adicionar criança: $e';
|
||||||
|
static const String registerAChild = 'Registe uma criança';
|
||||||
|
static const String registerAChildMessage =
|
||||||
|
'Antes de iniciar o quiz, adicione uma criança ao seu perfil.';
|
||||||
|
static const String addChild = 'Adicionar criança';
|
||||||
|
static const String whichChildIsTheQuizFor = 'Para qual criança é o quiz?';
|
||||||
|
static String childNameWithAge(String name, int age) => '$name • $age anos';
|
||||||
|
static const String cancel = 'Cancelar';
|
||||||
|
|
||||||
|
static const String freeAssessmentBadge = 'Avaliação gratuita';
|
||||||
|
static const String assessmentTitle = 'Avaliação de má oclusão dentária';
|
||||||
|
static const String assessmentSubtitle =
|
||||||
|
'29 perguntas rápidas · menos de 3 minutos';
|
||||||
|
static const String startQuiz = 'Iniciar Quiz';
|
||||||
|
|
||||||
|
static const String videoLibraryBadge = 'Biblioteca de vídeos';
|
||||||
|
static String watchedEpisodesSummary(int watchedCount, int totalCount) =>
|
||||||
|
'$watchedCount episódio${watchedCount == 1 ? '' : 's'} completo${watchedCount == 1 ? '' : 's'} · $totalCount no total';
|
||||||
|
static String allEpisodesSummary(int totalCount) =>
|
||||||
|
'$totalCount episódios sobre saúde oral para toda a família';
|
||||||
|
static const String watchVideos = 'Ver vídeos';
|
||||||
|
|
||||||
|
static const String profilePhoto = 'Foto de perfil';
|
||||||
|
static const String camera = 'Câmara';
|
||||||
|
static const String gallery = 'Galeria';
|
||||||
|
static String errorUploadingPhoto(Object e) => 'Erro ao enviar foto: $e';
|
||||||
|
|
||||||
|
static const String removeChild = 'Remover criança';
|
||||||
|
static String removeChildConfirmMessage(String childName) =>
|
||||||
|
'Tem a certeza que quer remover "$childName"? Esta ação não pode ser desfeita.';
|
||||||
|
static const String remove = 'Remover';
|
||||||
|
static const String noPermissionToRemoveChild =
|
||||||
|
'Sem permissão para remover esta criança.';
|
||||||
|
static const String childRemoved = 'Criança removida';
|
||||||
|
static String errorRemovingChild(Object e) => 'Erro ao remover: $e';
|
||||||
|
|
||||||
|
static String weeklyGoalOf(String childName) => 'Meta semanal de $childName';
|
||||||
|
static const String brushingsPerWeek = 'Escovagens por semana';
|
||||||
|
static const String brushingGoalRange = 'Entre 1 e 21 (até 3 por dia)';
|
||||||
|
static const String save = 'Guardar';
|
||||||
|
|
||||||
|
static const String childAdded = 'Criança adicionada';
|
||||||
|
static const String addAnotherChildQuestion = 'Adicionar outra criança?';
|
||||||
|
static const String notNow = 'Agora não';
|
||||||
|
static const String addAnother = 'Adicionar outra';
|
||||||
|
static const String timeoutAdding =
|
||||||
|
'Tempo esgotado ao adicionar. Tente novamente.';
|
||||||
|
static String errorAdding(Object e) => 'Erro ao adicionar: $e';
|
||||||
|
|
||||||
|
static const String myChildren = 'Meus filhos';
|
||||||
|
static const String noChildrenYet = 'Nenhuma criança adicionada ainda.';
|
||||||
|
static String childFallbackName(int index) => 'Criança ${index + 1}';
|
||||||
|
static String ageLabel(int age) => 'Idade: $age';
|
||||||
|
static String genderLabel(String gender) => 'Género: $gender';
|
||||||
|
static const String weeklyBrushingGoalTooltip = 'Meta semanal de escovagens';
|
||||||
|
|
||||||
|
static const String signOut = 'Sair';
|
||||||
|
static const String addAnotherChildTitle = 'Adicionar outra criança';
|
||||||
|
static const String birthDate = 'Data de nascimento';
|
||||||
|
static const String confirm = 'Confirmar';
|
||||||
|
static const String childName = 'Nome da criança';
|
||||||
|
static const String nameRequired = 'Indique o nome';
|
||||||
|
static const String nameTooShort = 'Nome muito curto';
|
||||||
|
static const String nameNoNumbers = 'O nome não pode conter números';
|
||||||
|
static const String selectDate = 'Selecione a data';
|
||||||
|
static const String male = 'Masculino';
|
||||||
|
static const String female = 'Feminino';
|
||||||
|
static const String other = 'Outro';
|
||||||
|
static const String gender = 'Género';
|
||||||
|
static const String genderRequired = 'Selecione o género';
|
||||||
|
static const String birthDateRequired = 'Indique a data de nascimento';
|
||||||
|
static const String add = 'Adicionar';
|
||||||
|
|
||||||
|
static const String childCode = 'Código numérico';
|
||||||
|
static const String childCodeRequired = 'Indique o código';
|
||||||
|
static const String childCodeDigitsOnly = 'O código só pode conter números';
|
||||||
|
static const String childCodeAlreadyInUse =
|
||||||
|
'Este código já está atribuído a outra criança';
|
||||||
|
static String codeLabel(String code) => 'Cód. $code';
|
||||||
|
}
|
||||||
23
lib/strings/privacy_strings.dart
Normal file
23
lib/strings/privacy_strings.dart
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
/// Texto fixo da Política de Privacidade, mostrado no ecrã de bloqueio no
|
||||||
|
/// cadastro ([PrivacyGateScreen]).
|
||||||
|
class PrivacyStrings {
|
||||||
|
const PrivacyStrings._();
|
||||||
|
|
||||||
|
static const String headerTitle = 'Política de Privacidade';
|
||||||
|
static const String acceptAll = 'Aceitar tudo';
|
||||||
|
static const String advance = 'Avançar';
|
||||||
|
static const String requiredPrefix = '(Obrigatório) ';
|
||||||
|
static const String optionalPrefix = '(Opcional) ';
|
||||||
|
|
||||||
|
static const String healthDataConsent =
|
||||||
|
'Confirmo que sou encarregado(a) de educação ou responsável legal '
|
||||||
|
'da criança avaliada e concordo com o processamento dos dados de '
|
||||||
|
'saúde que introduzo na Check-Teeth Kids, exclusivamente para gerar '
|
||||||
|
'o resultado da triagem digital.';
|
||||||
|
static const String privacyPolicyConsent =
|
||||||
|
'Li e aceito os Termos e Condições apresentados anteriormente e as '
|
||||||
|
'condições de privacidade descritas nesta página.';
|
||||||
|
static const String trackingConsent =
|
||||||
|
'Ajude-nos a melhorar a Check-Teeth Kids, autorizando a recolha de '
|
||||||
|
'dados técnicos de utilização.';
|
||||||
|
}
|
||||||
47
lib/strings/quiz_result_strings.dart
Normal file
47
lib/strings/quiz_result_strings.dart
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
/// Texto do ecrã de resultado do quiz ([QuizResultScreen]) e do vídeo-guia
|
||||||
|
/// mostrado logo a seguir.
|
||||||
|
class QuizResultStrings {
|
||||||
|
const QuizResultStrings._();
|
||||||
|
|
||||||
|
static const String resultHeading = 'O resultado avaliado é de:';
|
||||||
|
static const String signsRingLabel = 'Sinais de má\noclusão instalados';
|
||||||
|
static const String factorsRingLabel = 'Fatores de\nrisco associados';
|
||||||
|
|
||||||
|
static const String conclusionsHeading = 'Conclusões:';
|
||||||
|
static String conclusionsBody({
|
||||||
|
required int signs,
|
||||||
|
required int signsMax,
|
||||||
|
required int factors,
|
||||||
|
required int factorsMax,
|
||||||
|
}) =>
|
||||||
|
'Foram identificados $signs de $signsMax '
|
||||||
|
'sinais de má oclusão já instalada e '
|
||||||
|
'$factors de $factorsMax fatores de '
|
||||||
|
'risco frequentemente associados a '
|
||||||
|
'má oclusão.';
|
||||||
|
|
||||||
|
static const String whatToDoNowHeading = 'O que deve fazer agora';
|
||||||
|
static const String whatToDoNowRecommend =
|
||||||
|
'Com base neste resultado, recomendamos a marcação de uma consulta com Odontopediatra ou Ortodontista para uma avaliação presencial.';
|
||||||
|
static const String whatToDoNowNoConcerns =
|
||||||
|
'Não foram encontrados sinais ou fatores de risco relevantes. Continue a acompanhar a saúde oral do seu filho/a com as consultas de rotina habituais.';
|
||||||
|
|
||||||
|
static const String importantToKnowHeading = 'Importante saber';
|
||||||
|
static const String importantToKnowBody =
|
||||||
|
'Este resultado não substitui uma avaliação presencial, nem tem função de diagnóstico. O objetivo é ajudá-lo a estar mais atento aos sinais identificados e a não adiar uma consulta que pode fazer a diferença no desenvolvimento oral do seu filho/a.';
|
||||||
|
|
||||||
|
static const String watchTheFollowingVideo = 'Visualize o seguinte vídeo';
|
||||||
|
static const String resultVideoCaption =
|
||||||
|
'Assista para compreender a importância de um diagnóstico '
|
||||||
|
'precoce e de realizar tratamento ortodôntico intercetivo, '
|
||||||
|
'ou seja, uma intervenção realizada na infância, tipicamente '
|
||||||
|
'durante a fase de dentição mista (presença tanto de dentes '
|
||||||
|
'de leite como de permanentes).\n\nO seu propósito é '
|
||||||
|
'identificar, prevenir ou reduzir a gravidade de maloclusões '
|
||||||
|
'e problemas no desenvolvimento dos maxilares enquanto os '
|
||||||
|
'ossos e dentes ainda estão em crescimento.\n\nEsta '
|
||||||
|
'intervenção aproveita a fase de desenvolvimento craniofacial '
|
||||||
|
'para influenciar o crescimento ósseo, redirecionar a '
|
||||||
|
'mandíbula, corrigir o alinhamento dos maxilares ou criar '
|
||||||
|
'espaço para a erupção correta dos dentes permanentes.';
|
||||||
|
}
|
||||||
254
lib/strings/quiz_strings.dart
Normal file
254
lib/strings/quiz_strings.dart
Normal file
@@ -0,0 +1,254 @@
|
|||||||
|
/// Todo o texto do quiz de triagem (checklist inicial + 29 perguntas).
|
||||||
|
/// Para traduzir o quiz, é só editar os valores aqui — nenhum texto de
|
||||||
|
/// pergunta/resposta deve viver directamente dentro de `quiz1.dart`.
|
||||||
|
class QuizStrings {
|
||||||
|
const QuizStrings._();
|
||||||
|
|
||||||
|
// Ecrã de checklist inicial.
|
||||||
|
static const String checklistHeading = 'Vamos ajudá-lo/a a compreender:';
|
||||||
|
static const String checklistItem1 =
|
||||||
|
'Sinais de alerta podem passar despercebidos';
|
||||||
|
static const String checklistItem2 = 'Prevenir é melhor do que tratar';
|
||||||
|
static const String checklistItem3 =
|
||||||
|
'Quando deve procurar um dentista (urgência vs vigilância)';
|
||||||
|
|
||||||
|
// Rótulos partilhados pelas perguntas Sim/Não.
|
||||||
|
static const String yes = 'Sim';
|
||||||
|
static const String no = 'Não';
|
||||||
|
static const String dontKnow = 'Não sei';
|
||||||
|
static const String dontKnowDescription = 'Não tenho a certeza';
|
||||||
|
|
||||||
|
// Pergunta 1: Problemas respiratórios
|
||||||
|
static const String q1Question =
|
||||||
|
'O seu filho/a tem problemas respiratórios diagnosticados?';
|
||||||
|
static const String q1YesDescription =
|
||||||
|
'Problemas respiratórios diagnosticados';
|
||||||
|
static const String q1NoDescription =
|
||||||
|
'Sem problemas respiratórios diagnosticados';
|
||||||
|
|
||||||
|
// Pergunta 2: Respira pela boca
|
||||||
|
static const String q2Question =
|
||||||
|
'O seu filho/a respira habitualmente pela boca?';
|
||||||
|
static const String q2YesDescription = 'Respira habitualmente pela boca';
|
||||||
|
static const String q2NoDescription = 'Não respira habitualmente pela boca';
|
||||||
|
|
||||||
|
// Pergunta 3: Ressonar
|
||||||
|
static const String q3Question =
|
||||||
|
'O seu filho/a ressona habitualmente durante a noite?';
|
||||||
|
static const String q3YesDescription =
|
||||||
|
'Ressonar habitualmente durante a noite';
|
||||||
|
static const String q3NoDescription = 'Não ressona habitualmente';
|
||||||
|
|
||||||
|
// Pergunta 4: Nariz tapado
|
||||||
|
static const String q4Question =
|
||||||
|
'O seu filho/a sente habitualmente o nariz "tapado"?';
|
||||||
|
static const String q4YesDescription = 'Sente habitualmente o nariz tapado';
|
||||||
|
static const String q4NoDescription =
|
||||||
|
'Não sente habitualmente o nariz tapado';
|
||||||
|
|
||||||
|
// Pergunta 5: Interrupções da respiração
|
||||||
|
static const String q5Question =
|
||||||
|
'Durante o sono, o seu filho/a tem habitualmente interrupções da respiração?';
|
||||||
|
static const String q5YesDescription =
|
||||||
|
'Tem habitualmente interrupções da respiração durante o sono';
|
||||||
|
static const String q5NoDescription =
|
||||||
|
'Não tem interrupções da respiração durante o sono';
|
||||||
|
|
||||||
|
// Pergunta 6: Range os dentes
|
||||||
|
static const String q6Question =
|
||||||
|
'O seu filho/a range os dentes com frequência?';
|
||||||
|
static const String q6YesDescription = 'Range os dentes com frequência';
|
||||||
|
static const String q6NoDescription = 'Não range os dentes com frequência';
|
||||||
|
|
||||||
|
// Pergunta 7: Alergias sazonais
|
||||||
|
static const String q7Question =
|
||||||
|
'O seu filho/a habitualmente tem alergias sazonais?';
|
||||||
|
static const String q7YesDescription = 'Habitualmente tem alergias sazonais';
|
||||||
|
static const String q7NoDescription = 'Não tem alergias sazonais';
|
||||||
|
|
||||||
|
// Pergunta 8: Saliva seca
|
||||||
|
static const String q8Question =
|
||||||
|
'O seu filho/a acorda com saliva seca na cara ou na almofada?';
|
||||||
|
static const String q8YesDescription =
|
||||||
|
'Acorda com saliva seca na cara ou na almofada';
|
||||||
|
static const String q8NoDescription = 'Não acorda com saliva seca';
|
||||||
|
|
||||||
|
// Pergunta 9: Otites
|
||||||
|
static const String q9Question =
|
||||||
|
'O seu filho/a teve ou costuma ter com frequência otites?';
|
||||||
|
static const String q9YesDescription =
|
||||||
|
'Teve ou costuma ter com frequência otites';
|
||||||
|
static const String q9NoDescription = 'Não teve ou não costuma ter otites';
|
||||||
|
|
||||||
|
// Pergunta 10: Amigdalites
|
||||||
|
static const String q10Question =
|
||||||
|
'O seu filho/a teve ou costuma ter com frequência amigdalites?';
|
||||||
|
static const String q10YesDescription =
|
||||||
|
'Teve ou costuma ter com frequência amigdalites';
|
||||||
|
static const String q10NoDescription =
|
||||||
|
'Não teve ou não costuma ter amigdalites';
|
||||||
|
|
||||||
|
// Pergunta 11: Bronquiolites
|
||||||
|
static const String q11Question =
|
||||||
|
'O seu filho/a teve ou costuma ter com frequência bronquiolites?';
|
||||||
|
static const String q11YesDescription =
|
||||||
|
'Teve ou costuma ter com frequência bronquiolites';
|
||||||
|
static const String q11NoDescription =
|
||||||
|
'Não teve ou não costuma ter bronquiolites';
|
||||||
|
|
||||||
|
// Pergunta 12: Dificuldades a mastigar
|
||||||
|
static const String q12Question =
|
||||||
|
'O seu filho/a apresenta dificuldades a mastigar?';
|
||||||
|
static const String q12YesDescription = 'Apresenta dificuldades a mastigar';
|
||||||
|
static const String q12NoDescription = 'Não apresenta dificuldades a mastigar';
|
||||||
|
|
||||||
|
// Pergunta 13: Lento a comer
|
||||||
|
static const String q13Question = 'O seu filho/a habitualmente é lento a comer?';
|
||||||
|
static const String q13YesDescription = 'Habitualmente é lento a comer';
|
||||||
|
static const String q13NoDescription = 'Não é lento a comer';
|
||||||
|
|
||||||
|
// Pergunta 14: Prefere alimentos moles
|
||||||
|
static const String q14Question =
|
||||||
|
'O seu filho/a habitualmente prefere comer alimentos moles?';
|
||||||
|
static const String q14YesDescription =
|
||||||
|
'Habitualmente prefere comer alimentos moles';
|
||||||
|
static const String q14NoDescription = 'Não prefere alimentos moles';
|
||||||
|
|
||||||
|
// Pergunta 15: Alimentado por biberão
|
||||||
|
static const String q15Question = 'Em bebé apenas foi alimentado por biberão?';
|
||||||
|
static const String q15YesDescription =
|
||||||
|
'Em bebé apenas foi alimentado por biberão';
|
||||||
|
static const String q15NoDescription = 'Não foi apenas alimentado por biberão';
|
||||||
|
|
||||||
|
// Pergunta 16: Chupeta
|
||||||
|
static const String q16Question =
|
||||||
|
'O seu filho/a usa ou usou chupeta com frequência?';
|
||||||
|
static const String q16YesDescription = 'Usa ou usou chupeta com frequência';
|
||||||
|
static const String q16NoDescription =
|
||||||
|
'Não usa ou não usou chupeta com frequência';
|
||||||
|
|
||||||
|
// Pergunta 17: Chucha o dedo
|
||||||
|
static const String q17Question =
|
||||||
|
'O seu filho/a chucha ou já chuchou o dedo com frequência?';
|
||||||
|
static const String q17YesDescription =
|
||||||
|
'Chucha ou já chuchou o dedo com frequência';
|
||||||
|
static const String q17NoDescription =
|
||||||
|
'Não chucha ou não chuchou o dedo com frequência';
|
||||||
|
|
||||||
|
// Pergunta 18: Postura (imagem)
|
||||||
|
static const String q18Question =
|
||||||
|
'Qual das seguintes imagens é mais parecida com a postura do seu filho/a?';
|
||||||
|
static const String q18Answer1Title = 'Postura inadequada';
|
||||||
|
static const String q18Answer1Description =
|
||||||
|
'Postura curvada, ombros e pescoço projetados';
|
||||||
|
static const String q18Answer2Title = 'Postura correta';
|
||||||
|
static const String q18Answer2Description = 'Postura ereta, coluna alinhada';
|
||||||
|
|
||||||
|
// Pergunta 19: Perfil (imagem)
|
||||||
|
static const String q19Question =
|
||||||
|
'Qual das seguintes imagens é mais parecida com o perfil do seu filho/a?';
|
||||||
|
static const String q19Answer1Title = 'Perfil convexo';
|
||||||
|
static const String q19Answer1Description = 'Perfil facial convexo';
|
||||||
|
static const String q19Answer2Title = 'Perfil reto';
|
||||||
|
static const String q19Answer2Description = 'Perfil facial reto';
|
||||||
|
static const String q19Answer3Title = 'Perfil côncavo';
|
||||||
|
static const String q19Answer3Description = 'Perfil facial côncavo';
|
||||||
|
|
||||||
|
// Pergunta 20: Boca habitual (imagem)
|
||||||
|
static const String q20Question =
|
||||||
|
'Qual é a posição da boca do seu filho/a habitualmente?';
|
||||||
|
static const String q20Answer1Title = 'Boca fechada';
|
||||||
|
static const String q20Answer1Description = 'Boca fechada habitualmente';
|
||||||
|
static const String q20Answer2Title = 'Boca entreaberta';
|
||||||
|
static const String q20Answer2Description = 'Boca entreaberta habitualmente';
|
||||||
|
|
||||||
|
// Pergunta 21: Zona abaixo dos olhos (imagem)
|
||||||
|
static const String q21Question =
|
||||||
|
'Qual das imagens, na zona abaixo dos olhos, se assemelha mais ao seu filho/a?';
|
||||||
|
static const String q21Answer1Title = 'Sem sinais';
|
||||||
|
static const String q21Answer1Description = 'Sem olheiras visíveis abaixo dos olhos';
|
||||||
|
static const String q21Answer2Title = 'Sinal de risco';
|
||||||
|
static const String q21Answer2Description = 'Olheiras visíveis abaixo dos olhos';
|
||||||
|
|
||||||
|
// Pergunta 22: Queixo com a boca fechada (imagem)
|
||||||
|
static const String q22Question =
|
||||||
|
'Qual das imagens é mais parecida com o queixo do seu filho/a com a boca fechada?';
|
||||||
|
static const String q22Answer1Title = 'Queixo relaxado';
|
||||||
|
static const String q22Answer1Description =
|
||||||
|
'Queixo liso e relaxado com a boca fechada';
|
||||||
|
static const String q22Answer2Title = 'Queixo tenso';
|
||||||
|
static const String q22Answer2Description =
|
||||||
|
'Queixo tenso/franzido com a boca fechada';
|
||||||
|
|
||||||
|
// Pergunta 23: Boca / dentição (imagem)
|
||||||
|
static const String q23Question =
|
||||||
|
'Qual das seguintes imagens se assemelha à boca do seu filho/a?';
|
||||||
|
static const String q23Answer1Title = 'Dentição sobreposta';
|
||||||
|
static const String q23Answer1Description = 'Dentes sobrepostos/tortos';
|
||||||
|
static const String q23Answer2Title = 'Dentição alinhada';
|
||||||
|
static const String q23Answer2Description =
|
||||||
|
'Dentição bem alinhada, sem apinhamento';
|
||||||
|
static const String q23Answer3Title = 'Dentição desalinhada';
|
||||||
|
static const String q23Answer3Description = 'Dentição desalinhada/apinhada';
|
||||||
|
|
||||||
|
// Pergunta 24: Apinhamento visto de perto (imagem)
|
||||||
|
static const String q24Question =
|
||||||
|
'Qual das seguintes imagens se assemelha à boca do seu filho/a?';
|
||||||
|
static const String q24Answer1Title = 'Apinhamento moderado';
|
||||||
|
static const String q24Answer1Description =
|
||||||
|
'Dentes rodados/desalinhados de forma mais visível';
|
||||||
|
static const String q24Answer2Title = 'Ligeiro desalinhamento';
|
||||||
|
static const String q24Answer2Description =
|
||||||
|
'Pequeno desalinhamento ou espaço entre alguns dentes';
|
||||||
|
static const String q24Answer3Title = 'Apinhamento acentuado';
|
||||||
|
static const String q24Answer3Description = 'Dentes muito sobrepostos entre si';
|
||||||
|
|
||||||
|
// Pergunta 25: Freio labial (imagem)
|
||||||
|
static const String q25Question =
|
||||||
|
'Qual das seguintes imagens se assemelha ao freio labial do seu filho/a?';
|
||||||
|
static const String q25Answer1Title = 'Freio labial correto';
|
||||||
|
static const String q25Answer1Description = 'Inserção do freio labial mais alta';
|
||||||
|
static const String q25Answer2Title = 'Freio labial inadequado';
|
||||||
|
static const String q25Answer2Description =
|
||||||
|
'Inserção do freio labial baixa, entre os dentes';
|
||||||
|
|
||||||
|
// Pergunta 26: Freio lingual (imagem)
|
||||||
|
static const String q26Question =
|
||||||
|
'Qual das seguintes imagens se assemelha ao freio lingual do seu filho/a?';
|
||||||
|
static const String q26Answer1Title = 'Freio lingual inadequado';
|
||||||
|
static const String q26Answer1Description =
|
||||||
|
'Freio lingual curto/apertado (língua em coração)';
|
||||||
|
static const String q26Answer2Title = 'Freio lingual correto';
|
||||||
|
static const String q26Answer2Description =
|
||||||
|
'Língua move-se livremente, sem restrição visível';
|
||||||
|
|
||||||
|
// Pergunta 27: Boca / dentição 3 (imagem)
|
||||||
|
static const String q27Question =
|
||||||
|
'Qual das seguintes imagens se assemelha com a boca do seu filho/a?';
|
||||||
|
static const String q27Answer1Title = 'Dentição desalinhada';
|
||||||
|
static const String q27Answer1Description = 'Dentição desalinhada/apinhada';
|
||||||
|
static const String q27Answer2Title = 'Dentição sobreposta';
|
||||||
|
static const String q27Answer2Description = 'Dentes sobrepostos/tortos';
|
||||||
|
static const String q27Answer3Title = 'Dentição alinhada';
|
||||||
|
static const String q27Answer3Description =
|
||||||
|
'Dentição bem alinhada, sem apinhamento';
|
||||||
|
|
||||||
|
// Pergunta 28: Boca / dentição 2 (imagem)
|
||||||
|
static const String q28Question =
|
||||||
|
'Qual das seguintes imagens se assemelha à boca do seu filho/a?';
|
||||||
|
static const String q28Answer1Title = 'Dentição alinhada';
|
||||||
|
static const String q28Answer1Description =
|
||||||
|
'Dentição bem alinhada, sem apinhamento';
|
||||||
|
static const String q28Answer2Title = 'Dentição desalinhada';
|
||||||
|
static const String q28Answer2Description = 'Dentes sobrepostos/apinhados';
|
||||||
|
|
||||||
|
// Pergunta 29: Céu da boca (imagem, final)
|
||||||
|
static const String q29Question =
|
||||||
|
'Qual das seguintes imagens se assemelha ao céu da boca do seu filho/a?';
|
||||||
|
static const String q29CorrectBadgeLabel = 'Posição saudável';
|
||||||
|
static const String q29Answer1Title = 'Posição profunda incorreta';
|
||||||
|
static const String q29Answer1Description =
|
||||||
|
'Palato estreito/profundo, em forma de V';
|
||||||
|
static const String q29Answer2Title = 'Posição em U saudável';
|
||||||
|
static const String q29Answer2Description = 'Palato largo, em forma de U';
|
||||||
|
}
|
||||||
36
lib/strings/quiz_ui_strings.dart
Normal file
36
lib/strings/quiz_ui_strings.dart
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
/// Texto fixo da "moldura" do quiz (botões, validações, selos) — separado
|
||||||
|
/// de [QuizStrings], que tem só o conteúdo das perguntas/respostas.
|
||||||
|
class QuizUiStrings {
|
||||||
|
const QuizUiStrings._();
|
||||||
|
|
||||||
|
static const String defaultCorrectBadgeLabel = 'Posição certa';
|
||||||
|
static const String incorrectBadgeLabel = 'Posição inadequada';
|
||||||
|
static const String referenceImage = 'Imagem de referência';
|
||||||
|
|
||||||
|
static const String videoFallbackTitle = 'Vídeo';
|
||||||
|
static const String watchOptionalVideo = 'Ver vídeo (opcional)';
|
||||||
|
|
||||||
|
static const String enterNumber = 'Insira o número';
|
||||||
|
static const String chooseOneOption = 'Escolha uma opção';
|
||||||
|
static const String chooseOnlyOneOption = 'Escolha apenas uma opção';
|
||||||
|
|
||||||
|
static const String finish = 'Concluir';
|
||||||
|
static const String advance = 'Avançar';
|
||||||
|
static const String backToHomepage = 'Voltar para homepage';
|
||||||
|
|
||||||
|
static const String dontKnow = 'Não sei';
|
||||||
|
|
||||||
|
static const String skip = 'Pular';
|
||||||
|
static const String defaultVideoGuideHeading =
|
||||||
|
'Antes de continuar, veja o vídeo educativo...';
|
||||||
|
static const String defaultVideoGuideCaption =
|
||||||
|
'Veja o vídeo para compreender melhor as próximas perguntas do questionário.';
|
||||||
|
|
||||||
|
static String teethCountTooHigh(int max) =>
|
||||||
|
'Um número tão alto assim não é possível.\nO máximo é $max.';
|
||||||
|
|
||||||
|
static String notSureSeeVideo(String videoTitle) =>
|
||||||
|
'Não tem a certeza? Veja o "$videoTitle" para ajudar a responder';
|
||||||
|
|
||||||
|
static String quizProgress(int current, int total) => 'Quiz $current/$total';
|
||||||
|
}
|
||||||
26
lib/strings/settings_strings.dart
Normal file
26
lib/strings/settings_strings.dart
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
/// Texto do separador de Ajustes ([SettingsBody]).
|
||||||
|
class SettingsStrings {
|
||||||
|
const SettingsStrings._();
|
||||||
|
|
||||||
|
static const String deleteAccountData = 'Apagar dados da conta';
|
||||||
|
static const String deleteAccountMessage =
|
||||||
|
'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?';
|
||||||
|
static const String delete = 'Apagar';
|
||||||
|
static String errorDeletingAccount(int status) =>
|
||||||
|
'Erro ao apagar conta (status $status)';
|
||||||
|
static String errorDeleting(Object e) => 'Erro ao apagar: $e';
|
||||||
|
|
||||||
|
static const String account = 'Conta';
|
||||||
|
static const String noName = 'Sem nome';
|
||||||
|
static const String signOut = 'Sair';
|
||||||
|
|
||||||
|
static const String about = 'Sobre';
|
||||||
|
static const String termsOfService = 'Termos de Serviço';
|
||||||
|
static const String creatorsAndContributors = 'Criadores e colaboradores';
|
||||||
|
static const String appVersion = 'Versão do app';
|
||||||
|
|
||||||
|
static const String dangerZone = 'Zona de risco';
|
||||||
|
}
|
||||||
6
lib/strings/splash_strings.dart
Normal file
6
lib/strings/splash_strings.dart
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
/// Texto do ecrã de boas-vindas ([HelloSplashScreen]).
|
||||||
|
class SplashStrings {
|
||||||
|
const SplashStrings._();
|
||||||
|
|
||||||
|
static const String greeting = 'Olá';
|
||||||
|
}
|
||||||
33
lib/strings/terms_strings.dart
Normal file
33
lib/strings/terms_strings.dart
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
/// Texto fixo dos Termos e Condições, partilhado entre o ecrã de bloqueio
|
||||||
|
/// no cadastro ([TermsGateScreen]) e o ecrã informativo em Ajustes
|
||||||
|
/// ([TermsScreen]).
|
||||||
|
class TermsStrings {
|
||||||
|
const TermsStrings._();
|
||||||
|
|
||||||
|
static const String headerTitle = 'Termos e Condições';
|
||||||
|
static const String pageTitle = 'Termos de Serviço';
|
||||||
|
static const String acceptAll = 'Aceitar tudo';
|
||||||
|
static const String advance = 'Avançar';
|
||||||
|
|
||||||
|
static const List<String> paragraphs = [
|
||||||
|
'© 2026, Francisca Salgado Ferreira Pacheco Silva. A aplicação, o método '
|
||||||
|
'de avaliação, arquitetura e conteúdos são propriedade intelectual '
|
||||||
|
'exclusiva da autora.',
|
||||||
|
'A Check-Teeth Kids é uma aplicação desenvolvida com o propósito de '
|
||||||
|
'promover a literacia em saúde oral infantil, através de conteúdos '
|
||||||
|
'educativos e de um quiz de triagem digital, com especial enfoque na '
|
||||||
|
'identificação precoce de sinais associados à má oclusão dentária.',
|
||||||
|
'Com base nas respostas fornecidas, é apresentado um resultado que '
|
||||||
|
'identifica sinais de má oclusão já instalados e fatores de risco '
|
||||||
|
'associados, destinado a auxiliar o utilizador na decisão de '
|
||||||
|
'procurar uma avaliação clínica especializada.',
|
||||||
|
'Esta ferramenta não tem como objetivo realizar diagnósticos clínicos, '
|
||||||
|
'nem substituir a avaliação por um profissional de saúde oral. '
|
||||||
|
'Destina-se exclusivamente a uma triagem inicial e informativa, '
|
||||||
|
'promovendo o encaminhamento atempado para um médico dentista '
|
||||||
|
'especialista (Odontopediatra ou Ortodontista).',
|
||||||
|
'Esta aplicação foi desenvolvida no âmbito de um projeto de dissertação '
|
||||||
|
'de Mestrado, encontrando-se numa fase de desenvolvimento e ainda '
|
||||||
|
'sem validação clínica formal concluída.',
|
||||||
|
];
|
||||||
|
}
|
||||||
35
lib/strings/video_strings.dart
Normal file
35
lib/strings/video_strings.dart
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
/// Texto do ecrã de vídeos educativos ([VideoScreen]) e dos seus players.
|
||||||
|
class VideoStrings {
|
||||||
|
const VideoStrings._();
|
||||||
|
|
||||||
|
static const String pageTitle = 'Vídeos Educativos';
|
||||||
|
static const String searchHint = 'Pesquisar vídeos...';
|
||||||
|
static const String noVideosFound = 'Nenhum vídeo encontrado';
|
||||||
|
static const String videoNotAvailable = 'Vídeo ainda não disponível';
|
||||||
|
static String errorLoadingVideo(Object e) => 'Erro ao carregar vídeo: $e';
|
||||||
|
static const String rewind10s = 'Retroceder 10s';
|
||||||
|
static const String forward10s = 'Avançar 10s';
|
||||||
|
static const String nextVideos = 'Próximos';
|
||||||
|
static const String seeMore = 'Ver mais';
|
||||||
|
static String episodeBadge(int id) => 'EP. $id';
|
||||||
|
|
||||||
|
static String episodeTitle(int id) => 'Episódio $id';
|
||||||
|
|
||||||
|
/// Descrições de cada episódio, na mesma ordem dos ids (índice 0 = ep. 1).
|
||||||
|
static const List<String> episodeDescriptions = [
|
||||||
|
'Qual a Influência do nariz entupido na má oclusão',
|
||||||
|
'Qual a Influência das alergias sazionais na má oclusão',
|
||||||
|
'Qual a Influência das Otites frequentes na má oclusão',
|
||||||
|
'Qual a Influência das Amigdalites recorrentes na má oclusão',
|
||||||
|
'Qual a Influência das Bronquiolites recorrentes na má oclusão',
|
||||||
|
'Qual a Influência dos problemas respitatórios na má oclusão',
|
||||||
|
'Qual a Influência das interrupções respiratórias na má oclusão',
|
||||||
|
'Qual a Influência do ressonar na má oclusão',
|
||||||
|
'Qual a Influência de acordar com saliva seca na boca ou na almofada '
|
||||||
|
'na saúde oral',
|
||||||
|
'Qual a Influência da respiração oral na má oclusão',
|
||||||
|
'Qual a influência do uso exagerado da chupeta na má oclusão',
|
||||||
|
'Qual a influência do uso exagerado da chupeta na má oclusão',
|
||||||
|
'Qual a influência do hábito de chuchar o dedo na má oclusão',
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import '../colors/app_colors.dart';
|
||||||
|
import '../strings/common_strings.dart';
|
||||||
|
|
||||||
import 'tap_bounce.dart';
|
import 'tap_bounce.dart';
|
||||||
|
|
||||||
const Color _teal = Color(0xFF2F9E94);
|
const Color _teal = AppColors.teal;
|
||||||
const Color _accentPink = Color(0xFFFF55A7);
|
const Color _accentPink = AppColors.pink;
|
||||||
|
|
||||||
/// Diálogo de confirmação com a identidade visual do app (título rosa,
|
/// Diálogo de confirmação com a identidade visual do app (título rosa,
|
||||||
/// botões em pílula), usado para todas as confirmações destrutivas/decisórias.
|
/// botões em pílula), usado para todas as confirmações destrutivas/decisórias.
|
||||||
@@ -11,7 +13,7 @@ Future<bool?> showConfirmDialog(
|
|||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
required String title,
|
required String title,
|
||||||
String? message,
|
String? message,
|
||||||
String cancelLabel = 'Cancelar',
|
String cancelLabel = CommonStrings.cancel,
|
||||||
required String confirmLabel,
|
required String confirmLabel,
|
||||||
Color confirmColor = _teal,
|
Color confirmColor = _teal,
|
||||||
}) {
|
}) {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import '../colors/app_colors.dart';
|
||||||
|
|
||||||
/// Notificação em formato de pílula flutuante (fundo branco, texto rosa,
|
/// Notificação em formato de pílula flutuante (fundo branco, texto rosa,
|
||||||
/// cantos totalmente arredondados, com um pequeno "pop" de entrada), usada
|
/// cantos totalmente arredondados, com um pequeno "pop" de entrada), usada
|
||||||
@@ -62,7 +63,7 @@ class _AnimatedPill extends StatelessWidget {
|
|||||||
message,
|
message,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: Color(0xFFFF55A7),
|
color: AppColors.pink,
|
||||||
fontWeight: FontWeight.w800,
|
fontWeight: FontWeight.w800,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import '../colors/app_colors.dart';
|
||||||
|
import '../strings/privacy_strings.dart';
|
||||||
|
|
||||||
const Color kPrivacyTeal = Color(0xFF2F9E94);
|
const Color kPrivacyTeal = AppColors.teal;
|
||||||
|
|
||||||
/// Um dos itens de consentimento mostrados no ecrã de privacidade. Itens
|
/// Um dos itens de consentimento mostrados no ecrã de privacidade. Itens
|
||||||
/// com [required] a false (ex.: recolha de dados técnicos de utilização)
|
/// com [required] a false (ex.: recolha de dados técnicos de utilização)
|
||||||
@@ -19,25 +21,14 @@ class PrivacyConsentItem {
|
|||||||
|
|
||||||
/// Texto fixo dos consentimentos de privacidade, mostrado no cadastro.
|
/// Texto fixo dos consentimentos de privacidade, mostrado no cadastro.
|
||||||
const List<PrivacyConsentItem> kPrivacyConsentItems = [
|
const List<PrivacyConsentItem> kPrivacyConsentItems = [
|
||||||
PrivacyConsentItem(
|
PrivacyConsentItem(id: 'health_data', text: PrivacyStrings.healthDataConsent),
|
||||||
id: 'health_data',
|
|
||||||
text:
|
|
||||||
'Confirmo que sou encarregado(a) de educação ou responsável legal '
|
|
||||||
'da criança avaliada e concordo com o processamento dos dados de '
|
|
||||||
'saúde que introduzo na Check-Teeth Kids, exclusivamente para gerar '
|
|
||||||
'o resultado da triagem digital.',
|
|
||||||
),
|
|
||||||
PrivacyConsentItem(
|
PrivacyConsentItem(
|
||||||
id: 'privacy_policy',
|
id: 'privacy_policy',
|
||||||
text:
|
text: PrivacyStrings.privacyPolicyConsent,
|
||||||
'Li e aceito os Termos e Condições apresentados anteriormente e as '
|
|
||||||
'condições de privacidade descritas nesta página.',
|
|
||||||
),
|
),
|
||||||
PrivacyConsentItem(
|
PrivacyConsentItem(
|
||||||
id: 'tracking',
|
id: 'tracking',
|
||||||
text:
|
text: PrivacyStrings.trackingConsent,
|
||||||
'Ajude-nos a melhorar a Check-Teeth Kids, autorizando a recolha de '
|
|
||||||
'dados técnicos de utilização.',
|
|
||||||
required: false,
|
required: false,
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
@@ -66,7 +57,7 @@ class PrivacyHeader extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
const Text(
|
const Text(
|
||||||
'Política de Privacidade',
|
PrivacyStrings.headerTitle,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
|
|||||||
@@ -1,30 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import '../colors/app_colors.dart';
|
||||||
|
import '../strings/terms_strings.dart';
|
||||||
|
|
||||||
const Color kTermsPink = Color(0xFFFF55A7);
|
const Color kTermsPink = AppColors.pink;
|
||||||
|
|
||||||
/// Parágrafos dos Termos e Condições — texto fixo, igual em todo o app
|
|
||||||
/// (ecrã de bloqueio no cadastro e ecrã informativo em Ajustes).
|
|
||||||
const List<String> kTermsParagraphs = [
|
|
||||||
'© 2026, Francisca Salgado Ferreira Pacheco Silva. A aplicação, o método '
|
|
||||||
'de avaliação, arquitetura e conteúdos são propriedade intelectual '
|
|
||||||
'exclusiva da autora.',
|
|
||||||
'A Check-Teeth Kids é uma aplicação desenvolvida com o propósito de '
|
|
||||||
'promover a literacia em saúde oral infantil, através de conteúdos '
|
|
||||||
'educativos e de um quiz de triagem digital, com especial enfoque na '
|
|
||||||
'identificação precoce de sinais associados à má oclusão dentária.',
|
|
||||||
'Com base nas respostas fornecidas, é apresentado um resultado que '
|
|
||||||
'identifica sinais de má oclusão já instalados e fatores de risco '
|
|
||||||
'associados, destinado a auxiliar o utilizador na decisão de procurar '
|
|
||||||
'uma avaliação clínica especializada.',
|
|
||||||
'Esta ferramenta não tem como objetivo realizar diagnósticos clínicos, '
|
|
||||||
'nem substituir a avaliação por um profissional de saúde oral. '
|
|
||||||
'Destina-se exclusivamente a uma triagem inicial e informativa, '
|
|
||||||
'promovendo o encaminhamento atempado para um médico dentista '
|
|
||||||
'especialista (Odontopediatra ou Ortodontista).',
|
|
||||||
'Esta aplicação foi desenvolvida no âmbito de um projeto de dissertação '
|
|
||||||
'de Mestrado, encontrando-se numa fase de desenvolvimento e ainda sem '
|
|
||||||
'validação clínica formal concluída.',
|
|
||||||
];
|
|
||||||
|
|
||||||
/// Emblema circular rosa + título "Termos e Condições", reutilizado nos
|
/// Emblema circular rosa + título "Termos e Condições", reutilizado nos
|
||||||
/// dois ecrãs que mostram este conteúdo.
|
/// dois ecrãs que mostram este conteúdo.
|
||||||
@@ -50,7 +28,7 @@ class TermsHeader extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
const Text(
|
const Text(
|
||||||
'Termos e Condições',
|
TermsStrings.headerTitle,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
@@ -85,10 +63,10 @@ class TermsBody extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
for (var i = 0; i < kTermsParagraphs.length; i++) ...[
|
for (var i = 0; i < TermsStrings.paragraphs.length; i++) ...[
|
||||||
if (i > 0) const SizedBox(height: 14),
|
if (i > 0) const SizedBox(height: 14),
|
||||||
Text(
|
Text(
|
||||||
kTermsParagraphs[i],
|
TermsStrings.paragraphs[i],
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
|
|||||||
Reference in New Issue
Block a user