diff --git a/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png b/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..de70fda Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png b/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..112c6f0 Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png b/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..139c3ec Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png b/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..57e2f55 Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png b/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..c1201fd Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..5f349f7 --- /dev/null +++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png index f541127..b1832db 100644 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png index bdb1162..314ca14 100644 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png index b4effbb..ff918aa 100644 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index 2e6584e..7f7bd95 100644 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index 1228bff..a89d0a4 100644 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..c5d5899 --- /dev/null +++ b/android/app/src/main/res/values/colors.xml @@ -0,0 +1,4 @@ + + + #FFFFFF + \ No newline at end of file diff --git a/assets/CTK-ICON-adaptive.png b/assets/CTK-ICON-adaptive.png new file mode 100644 index 0000000..9e184d9 Binary files /dev/null and b/assets/CTK-ICON-adaptive.png differ diff --git a/assets/CTK-ICON.jpeg b/assets/CTK-ICON.jpeg new file mode 100644 index 0000000..7e197f6 Binary files /dev/null and b/assets/CTK-ICON.jpeg differ diff --git a/assets/quiz-icon.png b/assets/quiz-icon.png new file mode 100644 index 0000000..eb9271a Binary files /dev/null and b/assets/quiz-icon.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png index 2ed57c6..800f533 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png index 4b07745..6fe5d62 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png index 2dd3b6a..e2be0a7 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png index ac30cca..7f70a7b 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png index 31cbd51..12b0573 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png index d20b624..abbd595 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png index ce13fda..ce34ad8 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png index 2dd3b6a..e2be0a7 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png index 7fa34f4..74acb4e 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png index 911d6ee..d28156d 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png index 657f38f..f9b1b63 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png index f42cf94..21a776f 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png index e88de74..295b579 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png index 2ced103..b39a38d 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png index 911d6ee..d28156d 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png index f145ff0..cb0a0db 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png index f541127..b1832db 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png index 2e6584e..7f7bd95 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png index e1911f7..f8591b6 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png index 62ad537..747e015 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png index 2d8df94..45b0ebd 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/lib/auth_gate.dart b/lib/auth_gate.dart index 8b7de35..a26ed95 100644 --- a/lib/auth_gate.dart +++ b/lib/auth_gate.dart @@ -15,12 +15,14 @@ final ValueNotifier forceHomeScreen = ValueNotifier(false); /// Quando um cadastro (signUp) acabou de acontecer, guarda o uid da conta /// nova aqui — o AuthGate mostra o [TermsGateScreen] em vez do LoggedHome -/// enquanto este valor corresponder à sessão ativa. Aceitar limpa o valor -/// (avança para a Home); recusar apaga a conta e termina a sessão. +/// enquanto este valor corresponder à sessão ativa. É o primeiro dos dois +/// gates a ser mostrado, antes do [PrivacyGateScreen]. Aceitar limpa o +/// valor (avança para o próximo passo); recusar apaga a conta e termina a +/// sessão. final ValueNotifier pendingTermsUserId = ValueNotifier(null); /// Igual a [pendingTermsUserId], mas para o consentimento de privacidade -/// que o AuthGate mostra primeiro, antes do TermsGateScreen. +/// que o AuthGate mostra depois do TermsGateScreen. final ValueNotifier pendingPrivacyUserId = ValueNotifier( null, ); @@ -126,15 +128,6 @@ class _AuthGateState extends State { child = const SizedBox.shrink(); } else if (forcedHome || user == null || user.id != _validatedUserId) { child = const HomeScreen(key: ValueKey('home_screen')); - } else if (pendingPrivacyUid == user.id) { - child = PrivacyGateScreen( - key: const ValueKey('privacy_gate_screen'), - userId: user.id, - onAccepted: () { - pendingPrivacyUserId.value = null; - PrivacyGatePrefs.clearPendingUid(); - }, - ); } else if (pendingUid == user.id) { child = TermsGateScreen( key: const ValueKey('terms_gate_screen'), @@ -144,6 +137,15 @@ class _AuthGateState extends State { TermsGatePrefs.clearPendingUid(); }, ); + } else if (pendingPrivacyUid == user.id) { + child = PrivacyGateScreen( + key: const ValueKey('privacy_gate_screen'), + userId: user.id, + onAccepted: () { + pendingPrivacyUserId.value = null; + PrivacyGatePrefs.clearPendingUid(); + }, + ); } else { child = const LoggedHomeScreen(key: ValueKey('logged_home_screen')); } diff --git a/lib/home_screen.dart b/lib/home_screen.dart index b928b79..0db9b93 100644 --- a/lib/home_screen.dart +++ b/lib/home_screen.dart @@ -117,10 +117,10 @@ class _HomeScreenState extends State { } // Antes de persistir o perfil (o que já faz o AuthGate considerar a - // sessão válida), marca esta conta como pendente de consentimento - // de privacidade e de aceitação dos Termos — o AuthGate mostra - // primeiro o PrivacyGateScreen e, só depois de aceite, o - // TermsGateScreen, em vez do LoggedHome, enquanto estes uids + // sessão válida), marca esta conta como pendente de aceitação dos + // Termos e de consentimento de privacidade — o AuthGate mostra + // primeiro o TermsGateScreen e, só depois de aceite, o + // PrivacyGateScreen, em vez do LoggedHome, enquanto estes uids // estiverem marcados. Grava também em disco (não só em memória) // para os gates sobreviverem caso o processo seja morto antes de o // utilizador responder. @@ -210,7 +210,7 @@ class _HomeScreenState extends State { FadeSlideIn( delay: const Duration(milliseconds: 80), child: Text( - 'Organize a rotina de saúde oral com inteligência', + 'Deteção e Prevenção da Má Oclusão Infantil', textAlign: TextAlign.center, style: TextStyle( fontSize: 13.5, diff --git a/lib/logged_home.dart b/lib/logged_home.dart index a979a5c..ce52f3c 100644 --- a/lib/logged_home.dart +++ b/lib/logged_home.dart @@ -1340,7 +1340,7 @@ class _HeroQuizCard extends StatelessWidget { ), const SizedBox(height: 14), const Text( - 'Avaliação de saúde oral', + 'Avaliação de má oclusão dentária', style: TextStyle( fontWeight: FontWeight.w900, fontSize: 22, diff --git a/lib/quiz/quiz1.dart b/lib/quiz/quiz1.dart index c76df8d..45066db 100644 --- a/lib/quiz/quiz1.dart +++ b/lib/quiz/quiz1.dart @@ -84,9 +84,6 @@ class Quiz1Screen extends StatelessWidget { Widget build(BuildContext context) { return QuizQuestionScreen( title: 'Quiz 1/28', - category: 'Saúde respiratória', - categoryIcon: Icons.medical_information_rounded, - fallbackIcon: Icons.medical_information_rounded, fallbackColor: const Color(0xFFFF55A7), question: 'O seu filho/a tem problemas respiratórios diagnosticados?', answers: const [ @@ -131,9 +128,6 @@ class Quiz2Screen extends StatelessWidget { Widget build(BuildContext context) { return QuizQuestionScreen( title: 'Quiz 2/28', - category: 'Respiração', - categoryIcon: Icons.air_rounded, - fallbackIcon: Icons.air_rounded, fallbackColor: const Color(0xFF2F9E94), question: 'O seu filho/a respira habitualmente pela boca?', answers: const [ @@ -178,10 +172,7 @@ class Quiz3Screen extends StatelessWidget { Widget build(BuildContext context) { return QuizQuestionScreen( title: 'Quiz 3/28', - category: 'Sono', - categoryIcon: Icons.bedtime_rounded, - fallbackIcon: Icons.bedtime_rounded, - fallbackColor: const Color(0xFF8E7CC3), + fallbackColor: const Color(0xFFFF55A7), question: 'O seu filho/a ressona habitualmente durante a noite?', answers: const [ QuizAnswer( @@ -225,9 +216,6 @@ class Quiz4Screen extends StatelessWidget { Widget build(BuildContext context) { return QuizQuestionScreen( title: 'Quiz 4/28', - category: 'Respiração', - categoryIcon: Icons.sick_rounded, - fallbackIcon: Icons.sick_rounded, fallbackColor: const Color(0xFFFF55A7), question: 'O seu filho/a sente habitualmente o nariz "tapado"?', answers: const [ @@ -272,10 +260,7 @@ class Quiz5Screen extends StatelessWidget { Widget build(BuildContext context) { return QuizQuestionScreen( title: 'Quiz 5/28', - category: 'Sono', - categoryIcon: Icons.nights_stay_rounded, - fallbackIcon: Icons.nights_stay_rounded, - fallbackColor: const Color(0xFF8E7CC3), + fallbackColor: const Color(0xFFFF55A7), question: 'Durante o sono, o seu filho/a tem habitualmente interrupções da respiração?', answers: const [ @@ -321,9 +306,6 @@ class Quiz6Screen extends StatelessWidget { Widget build(BuildContext context) { return QuizQuestionScreen( title: 'Quiz 6/28', - category: 'Hábitos noturnos', - categoryIcon: Icons.nights_stay_rounded, - fallbackIcon: Icons.nights_stay_rounded, fallbackColor: const Color(0xFF2F9E94), question: 'O seu filho/a range os dentes com frequência?', answers: const [ @@ -361,9 +343,6 @@ class Quiz7Screen extends StatelessWidget { Widget build(BuildContext context) { return QuizQuestionScreen( title: 'Quiz 7/28', - category: 'Saúde geral', - categoryIcon: Icons.local_florist_rounded, - fallbackIcon: Icons.local_florist_rounded, fallbackColor: const Color(0xFFFF55A7), question: 'O seu filho/a habitualmente tem alergias sazonais?', answers: const [ @@ -408,10 +387,7 @@ class Quiz8Screen extends StatelessWidget { Widget build(BuildContext context) { return QuizQuestionScreen( title: 'Quiz 8/28', - category: 'Sono', - categoryIcon: Icons.water_drop_rounded, - fallbackIcon: Icons.water_drop_rounded, - fallbackColor: const Color(0xFF8E7CC3), + fallbackColor: const Color(0xFFFF55A7), question: 'O seu filho/a acorda com saliva seca na cara ou na almofada?', answers: const [ QuizAnswer( @@ -455,9 +431,6 @@ class Quiz9Screen extends StatelessWidget { Widget build(BuildContext context) { return QuizQuestionScreen( title: 'Quiz 9/28', - category: 'Saúde geral', - categoryIcon: Icons.hearing_rounded, - fallbackIcon: Icons.hearing_rounded, fallbackColor: const Color(0xFF2F9E94), question: 'O seu filho/a teve ou costuma ter com frequência otites?', answers: const [ @@ -502,9 +475,6 @@ class Quiz10Screen extends StatelessWidget { Widget build(BuildContext context) { return QuizQuestionScreen( title: 'Quiz 10/28', - category: 'Saúde geral', - categoryIcon: Icons.healing_rounded, - fallbackIcon: Icons.healing_rounded, fallbackColor: const Color(0xFFFF55A7), question: 'O seu filho/a teve ou costuma ter com frequência amigdalites?', answers: const [ @@ -549,9 +519,6 @@ class Quiz11Screen extends StatelessWidget { Widget build(BuildContext context) { return QuizQuestionScreen( title: 'Quiz 11/28', - category: 'Saúde respiratória', - categoryIcon: Icons.air_rounded, - fallbackIcon: Icons.air_rounded, fallbackColor: const Color(0xFF2F9E94), question: 'O seu filho/a teve ou costuma ter com frequência bronquiolites?', @@ -597,9 +564,6 @@ class Quiz12Screen extends StatelessWidget { Widget build(BuildContext context) { return QuizQuestionScreen( title: 'Quiz 12/28', - category: 'Hábitos alimentares', - categoryIcon: Icons.restaurant_rounded, - fallbackIcon: Icons.restaurant_rounded, fallbackColor: const Color(0xFFFF55A7), question: 'O seu filho/a apresenta dificuldades a mastigar?', answers: const [ @@ -637,9 +601,6 @@ class Quiz13Screen extends StatelessWidget { Widget build(BuildContext context) { return QuizQuestionScreen( title: 'Quiz 13/28', - category: 'Hábitos alimentares', - categoryIcon: Icons.schedule_rounded, - fallbackIcon: Icons.schedule_rounded, fallbackColor: const Color(0xFF2F9E94), question: 'O seu filho/a habitualmente é lento a comer?', answers: const [ @@ -677,10 +638,7 @@ class Quiz14Screen extends StatelessWidget { Widget build(BuildContext context) { return QuizQuestionScreen( title: 'Quiz 14/28', - category: 'Hábitos alimentares', - categoryIcon: Icons.restaurant_menu_rounded, - fallbackIcon: Icons.restaurant_menu_rounded, - fallbackColor: const Color(0xFF8E7CC3), + fallbackColor: const Color(0xFFFF55A7), question: 'O seu filho/a habitualmente prefere comer alimentos moles?', answers: const [ QuizAnswer( @@ -717,9 +675,6 @@ class Quiz15Screen extends StatelessWidget { Widget build(BuildContext context) { return QuizQuestionScreen( title: 'Quiz 15/28', - category: 'Hábitos alimentares', - categoryIcon: Icons.local_drink_rounded, - fallbackIcon: Icons.local_drink_rounded, fallbackColor: const Color(0xFFFF55A7), question: 'Em bebé apenas foi alimentado por biberão?', answers: const [ @@ -764,9 +719,6 @@ class Quiz16Screen extends StatelessWidget { Widget build(BuildContext context) { return QuizQuestionScreen( title: 'Quiz 16/28', - category: 'Hábitos orais', - categoryIcon: Icons.child_care_rounded, - fallbackIcon: Icons.child_care_rounded, fallbackColor: const Color(0xFF2F9E94), question: 'O seu filho/a usa ou usou chupeta com frequência?', answers: const [ @@ -811,10 +763,7 @@ class Quiz17Screen extends StatelessWidget { Widget build(BuildContext context) { return QuizQuestionScreen( title: 'Quiz 17/28', - category: 'Hábitos orais', - categoryIcon: Icons.back_hand_rounded, - fallbackIcon: Icons.back_hand_rounded, - fallbackColor: const Color(0xFF8E7CC3), + fallbackColor: const Color(0xFFFF55A7), question: 'O seu filho/a chucha ou já chuchou o dedo com frequência?', answers: const [ QuizAnswer( @@ -866,10 +815,7 @@ class Quiz18Screen extends StatelessWidget { Widget build(BuildContext context) { return QuizQuestionScreen( title: 'Quiz 18/28', - category: 'Avaliação postural', - categoryIcon: Icons.accessibility_new_rounded, - fallbackIcon: Icons.accessibility_new_rounded, - fallbackColor: const Color(0xFF8E7CC3), + fallbackColor: const Color(0xFFFF55A7), answerImageAspectRatio: 1.5, question: 'Qual das seguintes imagens é mais parecida com a postura do seu filho/a?', @@ -912,9 +858,6 @@ class Quiz19Screen extends StatelessWidget { Widget build(BuildContext context) { return QuizQuestionScreen( title: 'Quiz 19/28', - category: 'Avaliação facial', - categoryIcon: Icons.face_rounded, - fallbackIcon: Icons.face_rounded, fallbackColor: const Color(0xFFFF55A7), question: 'Qual das seguintes imagens é mais parecida com o perfil do seu filho/a?', @@ -977,9 +920,6 @@ class Quiz20Screen extends StatelessWidget { Widget build(BuildContext context) { return QuizQuestionScreen( title: 'Quiz 20/28', - category: 'Avaliação facial', - categoryIcon: Icons.sentiment_neutral_rounded, - fallbackIcon: Icons.sentiment_neutral_rounded, fallbackColor: const Color(0xFF2F9E94), question: 'Qual é a posição da boca do seu filho/a habitualmente?', answers: [ @@ -1006,15 +946,7 @@ class Quiz20Screen extends StatelessWidget { ], currentScore: currentScore, nextRoute: (context, nextScore) => quizPageRoute( - builder: (_) => QuizVideoGuideScreen( - youtubeId: 'msKYr7nPxcw', - onAdvance: (ctx) => Navigator.of(ctx).pushReplacement( - quizPageRoute( - builder: (_) => - Quiz21Screen(currentScore: nextScore, scopeId: scopeId), - ), - ), - ), + builder: (_) => Quiz21Screen(currentScore: nextScore, scopeId: scopeId), ), answerType: QuizAnswerType.image, showBackButton: true, @@ -1033,9 +965,6 @@ class Quiz21Screen extends StatelessWidget { Widget build(BuildContext context) { return QuizQuestionScreen( title: 'Quiz 21/28', - category: 'Avaliação facial', - categoryIcon: Icons.visibility_rounded, - fallbackIcon: Icons.visibility_rounded, fallbackColor: const Color(0xFFFF55A7), question: 'Qual das imagens, na zona abaixo dos olhos, se assemelha mais ao seu filho/a?', @@ -1086,10 +1015,7 @@ class Quiz22Screen extends StatelessWidget { Widget build(BuildContext context) { return QuizQuestionScreen( title: 'Quiz 22/28', - category: 'Avaliação facial', - categoryIcon: Icons.face_rounded, - fallbackIcon: Icons.face_rounded, - fallbackColor: const Color(0xFF8E7CC3), + fallbackColor: const Color(0xFFFF55A7), isSignQuestion: true, question: 'Qual das imagens é mais parecida com o queixo do seu filho/a com a boca fechada?', @@ -1118,7 +1044,15 @@ class Quiz22Screen extends StatelessWidget { ], currentScore: currentScore, nextRoute: (context, nextScore) => quizPageRoute( - builder: (_) => Quiz23Screen(currentScore: nextScore, scopeId: scopeId), + builder: (_) => QuizVideoGuideScreen( + youtubeId: 'msKYr7nPxcw', + onAdvance: (ctx) => Navigator.of(ctx).pushReplacement( + quizPageRoute( + builder: (_) => + Quiz23Screen(currentScore: nextScore, scopeId: scopeId), + ), + ), + ), ), answerType: QuizAnswerType.image, showBackButton: true, @@ -1137,9 +1071,6 @@ class Quiz23Screen extends StatelessWidget { Widget build(BuildContext context) { return QuizQuestionScreen( title: 'Quiz 23/28', - category: 'Avaliação facial', - categoryIcon: Icons.sentiment_neutral_rounded, - fallbackIcon: Icons.sentiment_neutral_rounded, fallbackColor: const Color(0xFF2F9E94), isSignQuestion: true, question: @@ -1191,9 +1122,6 @@ class Quiz24Screen extends StatelessWidget { Widget build(BuildContext context) { return QuizQuestionScreen( title: 'Quiz 24/28', - category: 'Avaliação facial', - categoryIcon: Icons.zoom_in_rounded, - fallbackIcon: Icons.zoom_in_rounded, fallbackColor: const Color(0xFF2F9E94), isSignQuestion: true, question: @@ -1245,9 +1173,6 @@ class Quiz25Screen extends StatelessWidget { Widget build(BuildContext context) { return QuizQuestionScreen( title: 'Quiz 25/28', - category: 'Avaliação facial', - categoryIcon: Icons.record_voice_over_rounded, - fallbackIcon: Icons.record_voice_over_rounded, fallbackColor: const Color(0xFFFF55A7), question: 'Qual das seguintes imagens se assemelha ao freio labial do seu filho/a?', @@ -1290,10 +1215,7 @@ class Quiz26Screen extends StatelessWidget { Widget build(BuildContext context) { return QuizQuestionScreen( title: 'Quiz 26/28', - category: 'Avaliação facial', - categoryIcon: Icons.record_voice_over_rounded, - fallbackIcon: Icons.record_voice_over_rounded, - fallbackColor: const Color(0xFF8E7CC3), + fallbackColor: const Color(0xFFFF55A7), question: 'Qual das seguintes imagens se assemelha ao freio lingual do seu filho/a?', answers: const [ @@ -1335,9 +1257,6 @@ class Quiz27Screen extends StatelessWidget { Widget build(BuildContext context) { return QuizQuestionScreen( title: 'Quiz 27/28', - category: 'Avaliação facial', - categoryIcon: Icons.sentiment_neutral_rounded, - fallbackIcon: Icons.sentiment_neutral_rounded, fallbackColor: const Color(0xFF2F9E94), isSignQuestion: true, question: @@ -1381,9 +1300,6 @@ class Quiz28Screen extends StatelessWidget { Widget build(BuildContext context) { return QuizQuestionScreen( title: 'Quiz 28/28', - category: 'Avaliação facial', - categoryIcon: Icons.architecture_rounded, - fallbackIcon: Icons.architecture_rounded, fallbackColor: const Color(0xFFFF55A7), isSignQuestion: true, question: diff --git a/lib/quiz/quiz_question_screen.dart b/lib/quiz/quiz_question_screen.dart index 97e3334..7884fda 100644 --- a/lib/quiz/quiz_question_screen.dart +++ b/lib/quiz/quiz_question_screen.dart @@ -80,9 +80,6 @@ class QuizQuestionScreen extends StatefulWidget { this.suggestedVideoPath, this.suggestedYoutubeId, this.suggestedVideoTitle, - this.category, - this.categoryIcon, - this.fallbackIcon, this.fallbackColor, this.answerImageAspectRatio, }); @@ -114,14 +111,8 @@ class QuizQuestionScreen extends StatefulWidget { /// retrato, para a postura). final double? answerImageAspectRatio; - /// Rótulo pequeno do tema da pergunta (ex.: "Avaliação facial"), mostrado - /// num badge acima da imagem/pergunta. - final String? category; - final IconData? categoryIcon; - - /// Usados só quando [questionImagePaths] está vazio: em vez de o bloco de - /// imagem colapsar, mostra-se um bloco colorido com este ícone. - final IconData? fallbackIcon; + /// Usado só quando [questionImagePaths] está vazio: em vez de o bloco de + /// imagem colapsar, mostra-se um bloco colorido com o ícone da app. final Color? fallbackColor; @override @@ -265,44 +256,6 @@ class _QuizQuestionScreenState extends State { ], ), ), - if ((widget.category ?? '').isNotEmpty) - Padding( - padding: const EdgeInsets.fromLTRB(16, 8, 16, 0), - child: Align( - alignment: Alignment.centerLeft, - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 6, - ), - decoration: BoxDecoration( - color: Colors.white.withValues(alpha: 0.75), - borderRadius: BorderRadius.circular(999), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - if (widget.categoryIcon != null) ...[ - Icon( - widget.categoryIcon, - size: 15, - color: const Color(0xFF2F9E94), - ), - const SizedBox(width: 6), - ], - Text( - widget.category!, - style: const TextStyle( - fontWeight: FontWeight.w800, - fontSize: 12, - color: Color(0xFF2F9E94), - ), - ), - ], - ), - ), - ), - ), Expanded( child: LayoutBuilder( builder: (context, constraints) { @@ -343,10 +296,6 @@ class _QuizQuestionScreenState extends State { .questionImagePaths, ) : _FallbackIconBlock( - icon: - widget.fallbackIcon ?? - Icons - .info_outline_rounded, color: widget.fallbackColor ?? const Color(0xFF2F9E94), @@ -1148,22 +1097,22 @@ class _QuestionReferenceImages extends StatelessWidget { /// relevante ao tema, sem ocupar a largura toda (esse destaque é reservado /// para as perguntas que realmente têm imagem). class _FallbackIconBlock extends StatelessWidget { - const _FallbackIconBlock({required this.icon, required this.color}); + const _FallbackIconBlock({required this.color}); - final IconData icon; final Color color; @override Widget build(BuildContext context) { return Center( child: Container( - width: 52, - height: 52, + width: 72, + height: 72, + padding: const EdgeInsets.all(11), decoration: BoxDecoration( color: color, borderRadius: BorderRadius.circular(16), ), - child: Icon(icon, size: 26, color: Colors.white), + child: Image.asset('assets/quiz-icon.png', fit: BoxFit.contain), ), ); } diff --git a/lib/quiz/quiz_result.dart b/lib/quiz/quiz_result.dart index 28a67a0..e1f8a76 100644 --- a/lib/quiz/quiz_result.dart +++ b/lib/quiz/quiz_result.dart @@ -1,6 +1,7 @@ -import 'package:flutter/material.dart'; import 'dart:async'; +import 'package:flutter/material.dart'; + import '../main.dart' show supabase; import '../widgets/app_gradients.dart'; import '../widgets/entrance.dart'; @@ -97,9 +98,33 @@ class _QuizResultScreenState extends State { } } + void _goToVideo() { + Navigator.of(context).pushReplacement( + MaterialPageRoute( + builder: (_) => QuizVideoGuideScreen( + youtubeId: _resultGuideYoutubeId, + heading: 'Visualize o seguinte vídeo', + caption: + '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.', + onAdvance: (context) => + Navigator.of(context).popUntil((r) => r.isFirst), + ), + ), + ); + } + @override Widget build(BuildContext context) { final recommend = _recommendsEvaluation(widget.finalScore); + final signs = widget.finalScore.signs; + final factors = widget.finalScore.factors; return Scaffold( body: Stack( @@ -116,31 +141,16 @@ class _QuizResultScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - Align( - alignment: Alignment.centerRight, - child: TextButton( - onPressed: () => - Navigator.of(context).popUntil((r) => r.isFirst), - child: const Text(''), - ), - ), Expanded( child: Center( child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, children: [ - const SizedBox(height: 6), + const SizedBox(height: 10), FadeSlideIn( - child: const QuizGuideVideoCard( - youtubeId: _resultGuideYoutubeId, - ), - ), - const SizedBox(height: 22), - FadeSlideIn( - delay: const Duration(milliseconds: 60), child: const Text( - 'Resultado da avaliação', + 'O resultado avaliado é de:', textAlign: TextAlign.center, style: TextStyle( fontSize: 18, @@ -152,20 +162,20 @@ class _QuizResultScreenState extends State { ), const SizedBox(height: 18), FadeSlideIn( - delay: const Duration(milliseconds: 100), + delay: const Duration(milliseconds: 60), child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ _ResultRing( - value: widget.finalScore.signs, + value: signs, max: kSignsMax, color: const Color(0xFF2F9E94), label: 'Sinais de má\noclusão instalados', ), _ResultRing( - value: widget.finalScore.factors, + value: factors, max: kFactorsMax, color: const Color(0xFFFF55A7), label: 'Fatores de\nrisco associados', @@ -173,35 +183,38 @@ class _QuizResultScreenState extends State { ], ), ), - const SizedBox(height: 20), + const SizedBox(height: 22), FadeSlideIn( - delay: const Duration(milliseconds: 120), - child: Text( - 'Conclusões:', - textAlign: TextAlign.center, - style: TextStyle( - color: Colors.black.withValues( - alpha: 0.75, - ), - fontWeight: FontWeight.w900, - ), + delay: const Duration(milliseconds: 100), + child: _ResultSection( + heading: 'Conclusões:', + body: + 'Foram identificados $signs de $kSignsMax ' + 'sinais de má oclusão já instalada e ' + '$factors de $kFactorsMax fatores de ' + 'risco frequentemente associados a ' + 'má oclusão.', + bodyColor: const Color(0xFFFF55A7), + bodyWeight: FontWeight.w800, ), ), - const SizedBox(height: 10), + const SizedBox(height: 16), FadeSlideIn( - delay: const Duration(milliseconds: 160), - child: Text( - recommend - ? 'Recomenda-se o agendamento de uma consulta com um dentista/ortodontista.\nEsta avaliação é apenas educativa.' - : 'Não foram encontrados sinais ou fatores de risco relevantes.\nEsta avaliação é apenas educativa.', - textAlign: TextAlign.center, - style: TextStyle( - color: Colors.black.withValues( - alpha: 0.70, - ), - fontWeight: FontWeight.w600, - height: 1.25, - ), + delay: const Duration(milliseconds: 140), + child: _ResultSection( + heading: 'O que deve fazer agora', + body: recommend + ? 'Com base neste resultado, recomendamos a marcação de uma consulta com Odontopediatra ou Ortodontista para uma avaliação presencial.' + : '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.', + ), + ), + const SizedBox(height: 16), + FadeSlideIn( + delay: const Duration(milliseconds: 180), + child: _ResultSection( + heading: 'Importante saber', + body: + '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.', ), ), ], @@ -232,9 +245,7 @@ class _QuizResultScreenState extends State { onPressed: () async { await _saveResultFuture; if (!context.mounted) return; - Navigator.of( - context, - ).popUntil((r) => r.isFirst); + _goToVideo(); }, child: const Text('Avançar'), ), @@ -255,6 +266,52 @@ class _QuizResultScreenState extends State { } } +/// Bloco de texto "título + parágrafo" reutilizado nas secções de +/// conclusão do resultado ("Conclusões:", "O que deve fazer agora", +/// "Importante saber"). +class _ResultSection extends StatelessWidget { + const _ResultSection({ + required this.heading, + required this.body, + this.bodyColor, + this.bodyWeight = FontWeight.w600, + }); + + final String heading; + final String body; + final Color? bodyColor; + final FontWeight bodyWeight; + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Text( + heading, + textAlign: TextAlign.center, + style: TextStyle( + color: Colors.black.withValues(alpha: 0.75), + fontWeight: FontWeight.w900, + ), + ), + const SizedBox(height: 8), + Text( + body, + textAlign: TextAlign.center, + style: TextStyle( + color: bodyColor ?? Colors.black.withValues(alpha: 0.70), + fontWeight: bodyWeight, + height: 1.35, + ), + ), + ], + ); + } +} + +/// Anel de progresso — usado para os dois indicadores do resultado +/// (sinais / fatores de risco), com o valor no centro e a legenda por +/// baixo. class _ResultRing extends StatelessWidget { const _ResultRing({ required this.value, @@ -280,14 +337,14 @@ class _ResultRing extends StatelessWidget { tween: Tween(begin: 0, end: progress), builder: (context, animatedProgress, _) { return SizedBox( - width: 150, - height: 150, + width: 130, + height: 130, child: Stack( alignment: Alignment.center, children: [ SizedBox( - width: 132, - height: 132, + width: 130, + height: 130, child: CircularProgressIndicator( value: animatedProgress, strokeWidth: 12, diff --git a/lib/quiz/quiz_video_guide.dart b/lib/quiz/quiz_video_guide.dart index 1934f18..32aad7a 100644 --- a/lib/quiz/quiz_video_guide.dart +++ b/lib/quiz/quiz_video_guide.dart @@ -78,6 +78,7 @@ class QuizVideoGuideScreen extends StatefulWidget { super.key, required this.youtubeId, required this.onAdvance, + this.heading = 'Antes de continuar, veja o vídeo educativo...', this.caption = 'Veja o vídeo para compreender melhor as próximas perguntas do questionário.', this.extraHeading, @@ -86,6 +87,7 @@ class QuizVideoGuideScreen extends StatefulWidget { final String youtubeId; final void Function(BuildContext context) onAdvance; + final String heading; final String caption; /// Quando definidos, mostram um bloco de destaque extra por baixo da @@ -142,10 +144,10 @@ class _QuizVideoGuideScreenState extends State { ), ), const SizedBox(height: 4), - const FadeSlideIn( + FadeSlideIn( child: Text( - 'Antes de continuar, veja o vídeo educativo...', - style: TextStyle( + widget.heading, + style: const TextStyle( fontSize: 21, fontWeight: FontWeight.w900, color: Colors.black87, diff --git a/lib/screens/credits_screen.dart b/lib/screens/credits_screen.dart index 2fc1f17..67f64d0 100644 --- a/lib/screens/credits_screen.dart +++ b/lib/screens/credits_screen.dart @@ -25,24 +25,24 @@ class _CreditSection { } const List<_CreditSection> _kCreditSections = [ - _CreditSection('Desenvolvimento', [ + _CreditSection('Criadora original', [ + _CreditPerson('Francisca Salgado Ferreira Pacheco Silva', 'Criadora original'), + ]), + _CreditSection('Desenvolvimento informático', [ _CreditPerson('Carlos Correia', 'Desenvolvedor'), - _CreditPerson('Fabio Ceia', 'Desenvolvedor'), + _CreditPerson('Fábio Ceia', 'Desenvolvedor'), _CreditPerson('Ruben Grandra', 'Desenvolvedor'), _CreditPerson('Dinis Maria', 'Desenvolvedor'), ]), - _CreditSection('Idealização', [ - _CreditPerson('Francisca Pacheco Silva', 'Idealizadora/Formanda'), - ]), - _CreditSection('Orientação', [ - _CreditPerson( - 'João Carlos Rodrigues L. Nunes Miranda', - 'Professor supervisor', - ), + _CreditSection('Orientadores', [ + _CreditPerson('Augusta Pureza Alves Silveira', 'Orientador(a)'), + _CreditPerson('Cristina Lopes Cardoso Silva', 'Orientador(a)'), + _CreditPerson('João Carlos Rodrigues L. Miranda', 'Orientador(a)'), ]), _CreditSection('Instituição colaboradora', [ + _CreditPerson('Universidade Fernando Pessoa', ''), _CreditPerson( - 'Escola Profissional da Vila do Conde', + 'Escola Profissional de Vila do Conde', '', logoPath: 'assets/logo2.png', ), diff --git a/lib/screens/privacy_gate_screen.dart b/lib/screens/privacy_gate_screen.dart index af5d8fe..213eff7 100644 --- a/lib/screens/privacy_gate_screen.dart +++ b/lib/screens/privacy_gate_screen.dart @@ -10,11 +10,12 @@ import '../widgets/liquid_waves_background.dart'; import '../widgets/tap_bounce.dart'; import '../widgets/privacy_content.dart'; -/// Ecrã de bloqueio mostrado logo após o cadastro (primeira vez), antes do -/// [TermsGateScreen] e antes de entrar na app. Só avança para o próximo -/// passo se o utilizador consentir os três itens de privacidade; se -/// recusar (botão ou seta de voltar), a conta acabada de criar é removida -/// (perfil apagado + sessão terminada) e volta-se ao login. +/// Ecrã de bloqueio mostrado logo após o cadastro (primeira vez), depois +/// de aceites os Termos ([TermsGateScreen]) e antes de entrar na app. Só +/// avança para a Home se o utilizador consentir os itens obrigatórios de +/// privacidade (o terceiro item é opcional); se recusar (botão ou seta de +/// voltar), a conta acabada de criar é removida (perfil apagado + sessão +/// terminada) e volta-se ao login. class PrivacyGateScreen extends StatefulWidget { const PrivacyGateScreen({ super.key, @@ -33,7 +34,9 @@ class _PrivacyGateScreenState extends State { final Set _accepted = {}; bool _declining = false; - bool get _allAccepted => _accepted.length == kPrivacyConsentItems.length; + bool get _allAccepted => kPrivacyConsentItems + .where((item) => item.required) + .every((item) => _accepted.contains(item.id)); void _toggle(String id, bool value) { setState(() { @@ -242,8 +245,23 @@ class _PrivacyConsentRow extends StatelessWidget { ), const SizedBox(width: 12), Expanded( - child: Text( - item.text, + child: Text.rich( + TextSpan( + children: [ + TextSpan( + text: item.required + ? '(Obrigatório) ' + : '(Opcional) ', + style: TextStyle( + fontWeight: FontWeight.w900, + color: item.required + ? kPrivacyTeal + : Colors.black.withValues(alpha: 0.45), + ), + ), + TextSpan(text: item.text), + ], + ), style: TextStyle( fontSize: 13, fontWeight: FontWeight.w700, diff --git a/lib/screens/terms_gate_screen.dart b/lib/screens/terms_gate_screen.dart index ff6e8c2..6f1eaa1 100644 --- a/lib/screens/terms_gate_screen.dart +++ b/lib/screens/terms_gate_screen.dart @@ -9,10 +9,11 @@ import '../widgets/liquid_waves_background.dart'; import '../widgets/tap_bounce.dart'; import '../widgets/terms_content.dart'; -/// Ecrã de bloqueio mostrado logo após o cadastro (primeira vez), antes de -/// entrar na app. Só avança para a Home se o utilizador aceitar os Termos; -/// se recusar (botão ou seta de voltar), a conta acabada de criar é -/// removida (perfil apagado + sessão terminada) e volta-se ao login. +/// Ecrã de bloqueio mostrado logo após o cadastro (primeira vez), antes do +/// [PrivacyGateScreen] e antes de entrar na app. Só avança para o próximo +/// passo se o utilizador aceitar os Termos; se recusar (botão ou seta de +/// voltar), a conta acabada de criar é removida (perfil apagado + sessão +/// terminada) e volta-se ao login. class TermsGateScreen extends StatefulWidget { const TermsGateScreen({ super.key, diff --git a/lib/widgets/privacy_content.dart b/lib/widgets/privacy_content.dart index 8c1d9b6..63f60dc 100644 --- a/lib/widgets/privacy_content.dart +++ b/lib/widgets/privacy_content.dart @@ -2,12 +2,19 @@ import 'package:flutter/material.dart'; const Color kPrivacyTeal = Color(0xFF2F9E94); -/// Um dos três itens de consentimento mostrados no ecrã de privacidade. +/// Um dos itens de consentimento mostrados no ecrã de privacidade. Itens +/// com [required] a false (ex.: recolha de dados técnicos de utilização) +/// não bloqueiam o avanço se não forem marcados. class PrivacyConsentItem { - const PrivacyConsentItem({required this.id, required this.text}); + const PrivacyConsentItem({ + required this.id, + required this.text, + this.required = true, + }); final String id; final String text; + final bool required; } /// Texto fixo dos consentimentos de privacidade, mostrado no cadastro. @@ -15,27 +22,28 @@ const List kPrivacyConsentItems = [ PrivacyConsentItem( id: 'health_data', text: - 'Concordo com o processamento dos meus dados pessoais de saúde para ' - 'ter acesso às funcionalidades da aplicação Check-Teeth Kids. Veja ' - 'mais na Política de Privacidade.', + '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( id: 'privacy_policy', - text: 'Concordo com a Política de Privacidade e com os Termos de Uso.', + text: + 'Li e aceito os Termos e Condições apresentados anteriormente e as ' + 'condições de privacidade descritas nesta página.', ), PrivacyConsentItem( id: 'tracking', text: - 'Autorizo o CK-T-KD a monitorizar a minha atividade na app. Podem ' - 'receber dados como a minha faixa etária, estado da subscrição, ' - 'momento de abertura da app e identificadores técnicos, conforme ' - 'descrito na Política de Privacidade. Isto ajuda o CK-T-KD a ' - 'melhorar as suas campanhas publicitárias. *', + 'Ajude-nos a melhorar a Check-Teeth Kids, autorizando a recolha de ' + 'dados técnicos de utilização.', + required: false, ), ]; -/// Emblema circular + título "Privacidade em primeiro lugar", reutilizado -/// onde este conteúdo for mostrado. +/// Emblema circular + título "Política de Privacidade", reutilizado onde +/// este conteúdo for mostrado. class PrivacyHeader extends StatelessWidget { const PrivacyHeader({super.key}); @@ -58,7 +66,7 @@ class PrivacyHeader extends StatelessWidget { ), const SizedBox(height: 16), const Text( - 'Privacidade em primeiro lugar', + 'Política de Privacidade', textAlign: TextAlign.center, style: TextStyle( fontSize: 20, diff --git a/lib/widgets/terms_content.dart b/lib/widgets/terms_content.dart index 23d6442..beb62f3 100644 --- a/lib/widgets/terms_content.dart +++ b/lib/widgets/terms_content.dart @@ -8,25 +8,22 @@ const List 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 ' + '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 questionários interativos, com especial enfoque na ' + 'educativos e de um quiz de triagem digital, com especial enfoque na ' 'identificação precoce de sinais associados à má oclusão dentária.', - 'Pertence-se uma avaliação orientadora do risco de má oclusão nas ' - 'crianças, com base nas respostas fornecidas. É gerada uma estimativa ' - 'percentual de risco, que pretende auxiliar os utilizadores na tomada ' - 'de decisão quanto à necessidade de procurar uma avaliação clínica ' - 'especializada.', - 'Não tem como objetivo realizar diagnósticos clínicos nem substituir a ' - 'avaliação por um profissional de saúde oral. Esta ferramenta digital ' - 'destina-se exclusivamente a uma triagem inicial e informativa, ' + '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 (ortodontista ou odontopediatra).', - 'A utilização desta aplicação deve ser encarada como um complemento ' - 'educativo e de sensibilização, reforçando a importância do ' - 'acompanhamento regular por profissionais qualificados e contribuindo ' - 'para a deteção precoce de possíveis alterações no desenvolvimento ' - 'oral infantil.', + '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 diff --git a/pubspec.yaml b/pubspec.yaml index 1585e8a..8c54bb5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -73,12 +73,20 @@ flutter: - lottie/ - assets/Check-theeth.png - assets/logo2.png + - assets/quiz-icon.png - assets/mockup_images/ flutter_launcher_icons: android: true ios: true - image_path: "assets/Check-theeth.png" + image_path: "assets/CTK-ICON.jpeg" + # Sem isto, o Android (API 26+) trata o ícone plano como um ícone "legado" + # e o launcher aplica sozinho um encolhimento automático (~66% da tela) + # antes de recortar a máscara — fazendo o ícone parecer pequeno/com + # margem, mesmo a imagem de origem já preenchendo a própria tela. Um + # ícone adaptativo com foreground opaco evita esse encolhimento extra. + adaptive_icon_background: "#FFFFFF" + adaptive_icon_foreground: "assets/CTK-ICON-adaptive.png" # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/to/resolution-aware-images