Skip to content

feat(theme): tema claro/escuro/automático nos apps ACS e Paciente (issue #9) - #11

Merged
hbgit merged 2 commits into
developfrom
feat/theme-light-dark
Sep 29, 2026
Merged

hbgit merged 2 commits into
developfrom
feat/theme-light-dark

Conversation

@vinimartinsufrr

Copy link
Copy Markdown
Collaborator

O que muda

Adiciona suporte a tema Claro/Escuro/Automático nos apps ACS e Paciente,
seguindo a sugestão técnica da própria issue: ThemeMode nativo do Flutter,
sem framework de estado externo, e persistência via shared_preferences
(preferência de UI não sensível, não precisa de flutter_secure_storage).

  • ThemeController (core/services/theme_controller.dart, igual nos dois
    apps): ValueNotifier<ThemeMode> simples, com restore() e
    setThemeMode() via shared_preferences. Falha de leitura ou gravação
    nunca trava a tela, só mantém ThemeMode.system, o padrão inicial.
  • MaterialApp de cada app agora usa theme, darkTheme e themeMode
    (antes só theme, sempre escuro, ignorando a preferência do sistema).
  • Nova tela ThemeSettingsScreen, acessível pela aba "Mais" já existente,
    com os três modos via RadioGroup<ThemeMode>.
  • Paleta clara nova (AcsLightColors/PatientLightColors), calculada e
    testada contra WCAG 2.2 AA em contrast_tokens_light_test.dart, que
    espelha o contrast_tokens_test.dart do escuro já existente.
  • Cores clínicas de RiskLevel (vermelho/amarelo/verde): o preenchimento
    não muda em nenhum tema. Só a variante usada como texto/ícone mudou de
    constante fixa para ThemeExtension (AcsRiskColors/PatientRiskColors,
    acessado via context.acsRisk/context.patientRisk), já que a cor certa
    depende do tema ativo em tempo de execução. Cai para a variante escura
    quando a extensão não está registrada, para não quebrar testes que montam
    um MaterialApp isolado.
  • Corrigidos dois textos do app Paciente que usavam Colors.white70 e
    Colors.white54 fixos (ficariam ilegíveis no tema claro). Agora usam
    Theme.of(context).colorScheme.onSurfaceVariant.

Por quê

Fecha a issue #9. Os apps ignoravam a preferência de tema do sistema
operacional e não ofereciam forma nenhuma de o usuário trocar entre
claro e escuro, uma lacuna de acessibilidade para quem precisa de tema
claro por baixa visão ou sensibilidade a contraste.

Como testar

cd apps/acs      && flutter analyze && flutter test   # 120 testes
cd apps/patient  && flutter analyze && flutter test   # 29 testes

# Verificação visual manual:
flutter run -d web-server --web-port=8765
# abrir http://localhost:8765 manualmente no navegador
# (flutter run -d chrome/edge falha nesta máquina, problema de lançamento
#  do CDP, sem relação com este código)

Na aba "Mais" > "Preferências", alternar entre Claro/Escuro/Automático e
confirmar que a UI inteira muda na hora, que a fila de risco continua
legível nos três modos, e que a escolha persiste ao recarregar o app.

Fora do escopo (conforme a própria issue)

  • apps/admin, não tocado.
  • Temas customizados além de claro/escuro/automático.
  • A paleta clínica (RiskLevel) em si. Só as variantes de texto/ícone
    foram adicionadas, os valores de preenchimento são os mesmos de antes.

Closes #9

🤖 Generated with Claude Code

…ciente

Implementa a issue #9: ThemeMode (claro/escuro/automatico) persistido via
shared_preferences, tela de Preferencias acessivel pela aba Mais, e paleta
clara testada contra WCAG 2.2 AA nos dois apps. As cores clinicas de
RiskLevel mantem o mesmo preenchimento nos dois temas; a variante usada
como texto/icone passou a ser um ThemeExtension (AcsRiskColors /
PatientRiskColors), ja que a cor certa depende do tema ativo em tempo de
execucao.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@hbgit

hbgit commented Sep 28, 2026

Copy link
Copy Markdown
Owner

Revisão da PR #11

Executei a revisão em cima do branch feat/theme-light-dark (commit 45ae667): testei um rebase sobre develop num worktree isolado (depois abortado), li os arquivos-chave, rodei flutter analyze/flutter test nos dois apps e cruzei o diff com os critérios de aceite da issue #9.

Qualidade do código: aprovada

  • theme_controller.dart (idêntico nos dois apps, só muda o name: do log): ValueNotifier<ThemeMode>, restore() com fallback seguro para ThemeMode.system via orElse + try/catch amplo em torno de SharedPreferences.getInstance(), setThemeMode() atualiza a UI antes de tentar persistir (falha de gravação só afeta a próxima abertura do app, nunca trava a tela). Único gap: não há teste unitário cobrindo o controller isoladamente (a PR testa a paleta de contraste, não a restauração/persistência/fallback em si) — sugestão, não bloqueador.
  • acs_theme.dart / patient_theme.dart: ThemeExtension bem implementada (copyWith/lerp corretos), segue a convenção "-400 nos tons *OnSurface" já documentada no projeto, e os tons do tema claro são coerentes entre os dois apps (redOnSurface/dangerOnSurface claro = 0xFFB91C1C nos dois, yellowOnSurface claro = 0xFFB45309 nos dois). Nitpick opcional: PatientRiskColors.dark.yellowOnSurface (0xFFE0A800) é um tom customizado fora do padrão de degrau Tailwind usado no resto da paleta — não é erro, só quebra o padrão visual.
  • app.dart / ThemeSettingsScreen: MaterialApp corretamente ligado a theme/darkTheme/themeMode via ValueListenableBuilder<ThemeMode>, restore() chamado em initState, dispose() só derruba o controller quando não foi injetado. Tela de preferências acessível pela aba "Mais" > "Preferências" nos dois apps, com RadioGroup<ThemeMode> (API Material 3 atual) e Keys (theme_light/theme_dark/theme_system) prontas para teste de widget.
  • Cores fixas: varredura completa em apps/acs/lib e apps/patient/lib por Colors.white70/54/30 e Colors.black87/54 — os únicos Colors.white restantes são os corretos, dentro dos próprios buildXxxDarkTheme(). Nenhum resíduo esquecido além dos dois já corrigidos pela PR.
  • Testes de contraste: contrast_tokens_light_test.dart não é cópia mecânica do teste do escuro — cobre exatamente a inversão de falhas que a própria PR documenta (yellow/green falham como texto no claro, red/accent falham no escuro), com a mesma metodologia de luminância relativa (support/contrast.dart) nos dois apps.
  • Execução local: flutter analyze limpo nos dois apps. flutter test: 120/120 (ACS) e 29/29 (Paciente), batendo exatamente com o que a descrição da PR alega — sem regressão.
  • Critérios de aceite da issue feat: permitir que o usuário alterne entre tema claro e escuro nos apps (ACS e Paciente) #9: os 7 itens do checklist (temas via ColorScheme.fromSeed, MaterialApp com theme/darkTheme/themeMode, tela de preferências na aba "Mais", persistência restaurada no boot, testes de contraste do claro equivalentes, analyze/test passando, paleta clínica de preenchimento inalterada) batem com o diff.

Bloqueador de merge (não é problema de qualidade do código)

A branch está bem atrás de develop (o CI mostra mergeable: CONFLICTING). Verifiquei a causa do acs-app vermelho no CI: é só a ausência do step mkdir -p assets/certs, adicionado a develop depois que esta PR foi aberta — não tem relação com o código de tema (confirmado rodando flutter analyze localmente, que voltou limpo).

O rebase, porém, não é mecânico: testei num worktree isolado e app.dart dos dois apps tem conflitos reais de estrutura, não só de lockfile —

  • ACS: develop adicionou VisitPullService/syncInterval (RF15) exatamente nos mesmos pontos de inserção (construtor, build(), LoginScreen, AcsHomeShell) onde esta PR adiciona ThemeController. Resolver = manter os dois parâmetros lado a lado nos ~7 pontos, não escolher um.
  • Paciente: mais profundo — develop envolveu a árvore de widgets com LocationScope/RemindersScope/ConsentPreferences (feature de lembretes que não existia quando esta branch foi criada). Encaixar o ValueListenableBuilder<ThemeMode> corretamente nessa árvore nova exige atenção manual.
  • pubspec.yaml/pubspec.lock do Paciente: conflito trivial (dependência nova de cada lado) — só manter as duas.

Recomendo rebasear feat/theme-light-dark sobre develop antes do merge.

Sugestões não-bloqueadoras

  1. Teste unitário para ThemeController (restore/persist/fallback).
  2. spec/ui_design.md ainda descreve "Dark Mode Nativo" como decisão permanente, e spec/ux_accessibility_assessment.md escopa o critério 1.4.3 como "texto sobre fundo escuro" — os dois ficam desatualizados se esta PR for mesclada. Pode ser um follow-up separado.

🤖 Gerado com Claude Code

@hbgit

hbgit commented Sep 28, 2026

Copy link
Copy Markdown
Owner

Olá @vinimartinsufrr favor, coorigir os conflitos apresentado para efetuarmos o merge da PR.

Resolve os conflitos apontados pelo professor no PR #11 (issue #9):
apps/acs/lib/app/app.dart, apps/patient/lib/app/app.dart e
apps/patient/pubspec.yaml/.lock. A develop avançou 227 commits desde a
base da branch (RF15, lembretes, Meus Dados/LGPD, onboarding, RBAC,
TLS no RPC etc.); a resolução combina essas features com o suporte a
tema claro/escuro/automático sem perder nenhum dos dois lados.

Também corrige referências remanescentes a PatientColors.dangerOnSurface/
accentOnSurface (API antiga, estática) introduzidas pela develop após
esta branch já ter convertido essas cores em ThemeExtension —
substituídas por context.patientRisk.*, e propaga themeController pelos
dois novos call-sites (_reenter, OnboardingScreen) que a develop criou
sem esse parâmetro.

apps/patient: flutter analyze limpo, flutter test 128/128.
apps/acs: flutter analyze limpo, flutter test 173/173.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@gitguardian

gitguardian Bot commented Sep 29, 2026

Copy link
Copy Markdown

⚠️ GitGuardian has uncovered 1 secret following the scan of your pull request.

Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.

🔎 Detected hardcoded secret in your pull request
GitGuardian id GitGuardian status Secret Commit Filename
37309636 Triggered Generic Password f248908 .github/workflows/ci.yml View secret
🛠 Guidelines to remediate hardcoded secrets
  1. Understand the implications of revoking this secret by investigating where it is used in your code.
  2. Replace and store your secret safely. Learn here the best practices.
  3. Revoke and rotate this secret.
  4. If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.

To avoid such incidents in the future consider


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

@vinimartinsufrr

Copy link
Copy Markdown
Collaborator Author

@hbgit sobre o alerta do GitGuardian: verifiquei e é falso positivo, não um segredo real.

A linha apontada (.github/workflows/ci.yml:314) é um comentário que só cita o nome da variável SINALACS_MQTT_PASSWORD, sem nenhum valor:

# tem a guarda que faz o build falhar sem SINALACS_MQTT_PASSWORD: cobri-lo

O detector "Generic Password" do GitGuardian reage a esse padrão (palavra "PASSWORD" perto de algo que parece atribuição) mesmo sem valor nenhum ali. Confirmei também que:

  • essa linha já existia na develop antes deste merge (commit 3515426), não foi introduzida por este PR;
  • não há segredo hardcoded em ci.yml: CI_POSTGRES_PASSWORD é efêmera (ci-ephemeral-not-a-secret) e GOOGLE_MAPS_API_KEY usa ${{ secrets.GOOGLE_MAPS_API_KEY }} corretamente;
  • a senha real do MQTT nunca é commitada — é gerada por máquina via bootstrap_env.sh e passada por --dart-define, como documentado em apps/CLAUDE.md.

Fica a seu critério marcar o achado como falso positivo no dashboard do GitGuardian.

@hbgit
hbgit merged commit 3a6ce44 into develop Sep 29, 2026
10 checks passed
@hbgit
hbgit deleted the feat/theme-light-dark branch October 1, 2026 23:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants