diff --git a/docs/01-arquitetura.md b/docs/01-arquitetura.md new file mode 100644 index 000000000..ce92e1334 --- /dev/null +++ b/docs/01-arquitetura.md @@ -0,0 +1,184 @@ +# Visão Geral da Arquitetura + +## Introdução + +O aplicativo PenhaS é construído usando Flutter e segue os princípios da Clean Architecture combinados com uma abordagem modular. Este design arquitetural garante separação de responsabilidades, testabilidade e manutenibilidade, ao mesmo tempo que fornece flexibilidade para o desenvolvimento de funcionalidades. + +## Padrões Arquiteturais + +### Clean Architecture + +A aplicação implementa Clean Architecture com clara separação entre: + +1. **Camada de Apresentação** - Componentes de UI, páginas e controllers +2. **Camada de Domínio** - Lógica de negócio, casos de uso e entidades +3. **Camada de Dados** - Repositórios, fontes de dados e modelos + +### Arquitetura Modular + +O app usa [Flutter Modular](https://pub.dev/packages/flutter_modular) para injeção de dependência e roteamento, organizando o código em módulos de funcionalidades. Cada módulo é autocontido com seus próprios: + +- Rotas +- Dependências +- Lógica de negócio +- Componentes de UI + +## Componentes Principais da Arquitetura + +### 1. Sistema de Bootstrap + +A inicialização do app é gerenciada por um sistema de bootstrap que controla: + +```dart +// lib/bootstrap/bootstrap.dart +class Bootstrap { + Future withRunner(Runner runner) async { + await runner.initialize(); + await runner.configure(); + runner.run(); + } +} +``` + +Dois runners principais: +- `MainAppRunner` - Inicializa a aplicação principal +- `BackgroundTasksRunner` - Gerencia serviços em segundo plano + +### 2. Sistema de Módulos + +O módulo raiz (`AppModule`) define: + +- Dependências globais +- Rotas principais +- Serviços centrais + +```dart +class AppModule extends Module { + @override + List get binds => [ + // Bindings de repositórios + Bind.factory((i) => AppStateRepository(...)), + + // Bindings de casos de uso + Bind.factory((i) => AppStateUseCase(...)), + + // Bindings de serviços + Bind.lazySingleton((i) => MigratorLocalStorage(...)), + ]; + + @override + List get routes => [ + ModuleRoute('/', module: SplashModule()), + ModuleRoute('/authentication', module: SignInModule()), + ModuleRoute('/mainboard', module: MainboardModule()), + ]; +} +``` + +### 3. Gerenciamento de Estado + +O app usa [MobX](https://pub.dev/packages/mobx) para gerenciamento reativo de estado: + +- **Stores** - Mantêm o estado da aplicação +- **Actions** - Modificam o estado +- **Computed values** - Estado derivado +- **Reactions** - Efeitos colaterais + +Exemplo de padrão store: +```dart +abstract class _MainboardStoreBase with Store { + @observable + ObservableList pages = ObservableList(); + + @observable + MainboardState selectedPage = const MainboardState.feed(); + + @action + Future changePage({required MainboardState to}) async { + // Lógica de mudança de página + } +} +``` + +### 4. Navegação + +A navegação é gerenciada através do sistema de roteamento do Flutter Modular: + +- Rotas declarativas nos módulos +- Navegação type-safe +- Suporte a deep linking +- Guards de rota para autenticação + +## Arquitetura de Funcionalidades + +Cada funcionalidade segue uma estrutura consistente: + +``` +feature/ +├── data/ +│ ├── models/ # Objetos de transferência de dados +│ └── repositories/ # Implementações de repositórios +├── domain/ +│ ├── entities/ # Entidades de negócio +│ ├── repositories/ # Interfaces de repositórios +│ ├── states/ # Definições de estado +│ └── usecases/ # Lógica de negócio +└── presentation/ + ├── pages/ # Telas da UI + ├── widgets/ # Componentes reutilizáveis + └── controllers/ # Controllers de página (stores MobX) +``` + +## Injeção de Dependência + +Flutter Modular fornece injeção de dependência com diferentes tempos de vida: + +- **Factory** - Nova instância por injeção +- **Singleton** - Instância única durante toda vida do app +- **LazySingleton** - Instância única criada no primeiro uso + +## Fluxo de Dados + +1. **Interação do Usuário** → UI dispara ação do controller +2. **Controller** → Chama caso de uso com parâmetros +3. **Caso de Uso** → Executa lógica de negócio, chama repositório +4. **Repositório** → Busca/armazena dados via API ou storage local +5. **Resposta** → Dados fluem de volta pelas camadas +6. **Atualização da UI** → Reações MobX atualizam a UI + +## Tratamento de Erros + +A arquitetura implementa um sistema robusto de tratamento de erros: + +- Classes de falha customizadas para diferentes tipos de erro +- Tipo Either (usando Dartz) para tratamento funcional de erros +- Mapeamento centralizado de erros e mensagens amigáveis ao usuário + +## Arquitetura de Segurança + +- Armazenamento seguro para dados sensíveis +- Autenticação baseada em token +- Modo camuflado para situações de emergência +- Serviço em segundo plano para funcionalidade do botão de pânico + +## Decisões Arquiteturais Principais + +1. **Clean Architecture** - Garante testabilidade e separação de responsabilidades +2. **Abordagem Modular** - Permite isolamento de funcionalidades e escalabilidade da equipe +3. **MobX para Estado** - Fornece programação reativa com mínimo boilerplate +4. **Padrão Repository** - Abstrai fontes de dados da lógica de negócio +5. **Padrão Use Case** - Encapsula regras de negócio e fluxos de trabalho + +## Benefícios + +- **Testabilidade** - Cada camada pode ser testada independentemente +- **Manutenibilidade** - Clara separação de responsabilidades +- **Escalabilidade** - Fácil adicionar novas funcionalidades como módulos +- **Reusabilidade** - Componentes e lógica de negócio compartilhados +- **Colaboração em Equipe** - Limites claros entre funcionalidades + +## Próximos Passos + +- Revise a [Pilha Tecnológica](02-pilha-tecnologica.md) para detalhes de implementação +- Explore a [Estrutura do Projeto](03-estrutura-projeto.md) para organização do código +- Verifique o [Gerenciamento de Estado](05-gerenciamento-estado.md) para padrões MobX \ No newline at end of file diff --git a/docs/02-pilha-tecnologica.md b/docs/02-pilha-tecnologica.md new file mode 100644 index 000000000..16564f66f --- /dev/null +++ b/docs/02-pilha-tecnologica.md @@ -0,0 +1,256 @@ +# Pilha Tecnológica + +## Visão Geral + +O aplicativo PenhaS utiliza tecnologias modernas e confiáveis para garantir performance, segurança e manutenibilidade. Esta documentação detalha as principais tecnologias, frameworks e bibliotecas utilizadas no projeto. + +## Framework Principal + +### Flutter (SDK 3.2.0+) + +- **Framework multiplataforma** para desenvolvimento de aplicações móveis nativas +- **Dart** como linguagem de programação +- **Hot Reload** para desenvolvimento rápido +- **Widget-based UI** para interfaces consistentes + +## Gerenciamento de Estado e Arquitetura + +### Flutter Modular (^5.0.3) +- Sistema de injeção de dependência +- Roteamento modular +- Organização de código em módulos independentes + +### MobX (^2.0.5) + Flutter MobX (^2.0.5) +- Gerenciamento reativo de estado +- Observáveis e ações +- Integração perfeita com Flutter +- Code generation com `mobx_codegen` + +### Dartz (^0.10.1) +- Programação funcional em Dart +- Tipo `Either` para tratamento de erros +- Operações funcionais + +## Armazenamento e Persistência + +### Hive Flutter (^1.1.0) +- Banco de dados NoSQL local +- Alta performance +- Criptografia nativa +- Type-safe com adaptadores + +### Flutter Secure Storage (^9.2.4) +- Armazenamento seguro de dados sensíveis +- Criptografia AES +- Keychain (iOS) e Keystore (Android) + +### Shared Preferences (^2.0.11) +- Armazenamento de preferências simples +- Dados não sensíveis +- Configurações do usuário + +## Comunicação com Backend + +### HTTP (^1.2.2) +- Cliente HTTP para requisições REST +- Suporte a interceptors +- Gerenciamento de headers + +### Equatable (^2.0.3) +- Comparação de objetos +- Útil para estados e entidades + +## Funcionalidades de Segurança e Emergência + +### Flutter Background Service (^5.1.0) +- Execução de tarefas em segundo plano +- Essencial para botão de pânico +- Notificações persistentes + +### Geolocator (^12.0.0) +- Localização GPS +- Rastreamento de posição +- Permissões de localização + +### Permission Handler (^11.4.0) +- Gerenciamento de permissões do sistema +- Solicitação e verificação de permissões + +## Mapas e Localização + +### Google Maps Flutter (2.2.0) +- Integração com Google Maps +- Marcadores personalizados +- Visualização de pontos de apoio + +### Map Launcher (^3.0.0) +- Integração com apps de mapas nativos +- Navegação turn-by-turn +- Suporte múltiplos apps de mapa + +## Mídia e Comunicação + +### Flutter Sound (^9.25.4) +- Gravação e reprodução de áudio +- Suporte a múltiplos formatos +- Controle de qualidade de áudio + +### Flutter HTML (^3.0.0) +- Renderização de conteúdo HTML +- Suporte a CSS customizado +- Widgets nativos + +### WebView Flutter (^4.9.0) +- Visualização de conteúdo web +- JavaScript bridge +- Navegação controlada + +## Analytics e Monitoramento + +### Firebase Core (3.12.1) +- Infraestrutura Firebase +- Configuração base + +### Firebase Analytics (11.4.3) +- Análise de comportamento do usuário +- Eventos customizados +- Funis de conversão + +### Firebase Crashlytics (4.3.4) +- Relatórios de crash em tempo real +- Stack traces detalhados +- Análise de estabilidade + +### Firebase Remote Config (^5.4.2) +- Feature toggles +- Configuração remota +- A/B testing + +## UI/UX e Design + +### Flutter SVG (^2.0.0) +- Renderização de SVGs +- Ícones escaláveis +- Otimização de assets + +### Badges (^2.0.2) +- Notificações visuais +- Contadores em ícones + +### Asuka (^2.2.1) +- Snackbars e dialogs globais +- Navegação simplificada +- Overlays customizados + +### Flutter Widget from HTML (^0.16.0) +- Conversão HTML para widgets +- Renderização otimizada + +## Utilitários de Desenvolvimento + +### Build Runner (^2.1.5) +- Code generation +- Automatização de tarefas +- Watch mode para desenvolvimento + +### Freezed (^2.0.4) +- Immutable classes +- Union types +- JSON serialization + +### JSON Serializable (^6.3.1) +- Serialização/deserialização JSON +- Type-safe +- Code generation + +### Logger (^2.0.2) +- Sistema de logs estruturado +- Níveis de log configuráveis +- Pretty printing + +## Testes + +### Flutter Test (SDK) +- Framework de testes nativo +- Widget tests +- Unit tests + +### Mocktail (^1.0.0) +- Mocking framework +- Sintaxe limpa +- Null safety + +### Golden Toolkit (^0.15.0) +- Golden tests +- Comparação visual +- Testes de regressão visual + +### Alchemist (^0.11.0) +- Golden tests avançados +- CI/CD integration +- Múltiplas plataformas + +## Ferramentas de Build e Deploy + +### Fastlane +- Automação de deploy +- Build e distribuição +- Integração com lojas + +### Firebase App Distribution +- Distribuição beta +- Gestão de testers +- Feedback integrado + +## Dependências Auxiliares + +### UUID (^4.3.3) +- Geração de identificadores únicos + +### Intl (^0.19.0) +- Internacionalização +- Formatação de datas/números + +### Path Provider (^2.0.8) +- Acesso a diretórios do sistema +- Paths platform-specific + +### Device Info Plus +- Informações do dispositivo +- Identificação de plataforma + +### Package Info Plus +- Informações do app +- Versão e build number + +### RxDart (^0.28.0) +- Extensões reativas +- Streams avançados + +### Timeago (^3.1.0) +- Formatação de tempo relativo +- "há 5 minutos" + +### URL Launcher (^6.1.7) +- Abertura de URLs +- Suporte a schemes customizados + +## Considerações de Versão + +- **Flutter SDK**: Mínimo 3.2.0, máximo 4.0.0 +- **Dart SDK**: Compatível com Flutter SDK +- **Android**: Min SDK 21 (Android 5.0) +- **iOS**: Deployment target 11.0 + +## Gerenciamento de Dependências + +O projeto utiliza: +- **pubspec.yaml** para declaração de dependências +- **pubspec.lock** para versões fixas +- **FVM** para gerenciamento de versão do Flutter + +## Próximos Passos + +- Veja a [Estrutura do Projeto](03-estrutura-projeto.md) para entender a organização do código +- Consulte a [Configuração do Ambiente](09-configuracao-desenvolvimento.md) para setup inicial +- Revise as [Funcionalidades Principais](04-funcionalidades.md) para entender o uso das tecnologias \ No newline at end of file diff --git a/docs/03-estrutura-projeto.md b/docs/03-estrutura-projeto.md new file mode 100644 index 000000000..afe3d9e1a --- /dev/null +++ b/docs/03-estrutura-projeto.md @@ -0,0 +1,253 @@ +# Estrutura do Projeto + +## Visão Geral + +O projeto PenhaS segue uma estrutura modular e bem organizada, facilitando a manutenção, escalabilidade e colaboração entre desenvolvedores. A estrutura reflete os princípios da Clean Architecture e a abordagem modular do Flutter. + +## Estrutura de Diretórios Principal + +``` +penhas-app/ +├── android/ # Configurações específicas do Android +├── ios/ # Configurações específicas do iOS +├── lib/ # Código-fonte principal do app +├── test/ # Testes unitários e de widget +├── assets/ # Recursos estáticos (imagens, fontes, etc.) +├── docs/ # Documentação técnica +├── fastlane/ # Scripts de automação de deploy +├── pubspec.yaml # Dependências e configurações do projeto +└── analysis_options.yaml # Regras de análise de código +``` + +## Estrutura Detalhada do `/lib` + +### `/lib/main.dart` +Ponto de entrada da aplicação. Inicializa o bootstrap e configura os runners. + +### `/lib/bootstrap/` +Sistema de inicialização do app: +``` +bootstrap/ +├── bootstrap.dart # Classe principal de bootstrap +└── impl/ + ├── main_app_runner.dart # Runner da aplicação principal + ├── background_tasks_runner.dart # Runner de tarefas em segundo plano + └── app_runner_mixin.dart # Mixin compartilhado +``` + +### `/lib/app/` +Código principal da aplicação: +``` +app/ +├── app_module.dart # Módulo raiz com configurações globais +├── app_widget.dart # Widget raiz da aplicação +├── core/ # Funcionalidades centrais +├── features/ # Módulos de funcionalidades +└── shared/ # Componentes compartilhados +``` + +## Estrutura Core (`/lib/app/core/`) + +### `analytics/` +- `analytics_wrapper.dart` - Wrapper para Firebase Analytics + +### `data/` +- `formz/` - Validadores de formulário reutilizáveis + +### `entities/` +- Classes de entidade compartilhadas entre features + +### `error/` +- `failures.dart` - Classes de falha customizadas +- Tratamento centralizado de erros + +### `extension/` +- Extensões Dart para tipos built-in +- Helpers e utilities + +### `managers/` +- `app_configuration.dart` - Configurações globais do app +- `user_profile_store.dart` - Gerenciamento do perfil do usuário +- `audio_sync_manager.dart` - Sincronização de áudios +- `background_task_manager.dart` - Gerenciamento de tarefas em segundo plano +- `local_store.dart` - Interface para armazenamento local + +### `network/` +- `api_client.dart` - Cliente HTTP configurado +- `api_server_configure.dart` - Configuração do servidor +- `network_info.dart` - Informações de conectividade + +### `pages/` +- Páginas compartilhadas entre módulos +- Componentes de UI reutilizáveis + +### `remoteconfig/` +- `i_remote_config.dart` - Interface para Remote Config +- `remote_config.dart` - Implementação Firebase Remote Config + +### `states/` +- Estados globais da aplicação +- Enums e tipos compartilhados + +### `storage/` +``` +storage/ +├── cache_storage.dart # Interface para cache +├── i_local_storage.dart # Interface para storage local +├── local_storage_shared_preferences.dart # Implementação SharedPreferences +├── migrator_local_storage.dart # Migração de dados +├── persistent_storage.dart # Storage persistente +├── secure_local_storage.dart # Storage seguro +└── impl/ + ├── hive_cache_storage.dart # Implementação Hive para cache + └── hive_persistent_storage.dart # Implementação Hive persistente +``` + +### `types/` +- Tipos e typedefs globais + +## Estrutura de Features (`/lib/app/features/`) + +Cada feature segue o padrão Clean Architecture: + +``` +feature_name/ +├── data/ +│ ├── models/ # DTOs e modelos de dados +│ └── repositories/ # Implementações concretas +├── domain/ +│ ├── entities/ # Entidades de negócio +│ ├── repositories/ # Interfaces de repositório +│ ├── states/ # Estados específicos da feature +│ └── usecases/ # Casos de uso +├── presentation/ +│ ├── pages/ # Páginas/telas +│ ├── widgets/ # Widgets específicos +│ └── controllers/ # Controllers (MobX stores) +└── feature_module.dart # Módulo da feature +``` + +### Features Principais: + +1. **`appstate/`** - Estado global da aplicação +2. **`authentication/`** - Login, registro, recuperação de senha +3. **`chat/`** - Sistema de mensagens +4. **`escape_manual/`** - Manual de fuga +5. **`feed/`** - Feed de publicações +6. **`filters/`** - Sistema de filtros +7. **`help_center/`** - Central de ajuda e emergência +8. **`main_menu/`** - Menu principal e drawer +9. **`mainboard/`** - Tela principal com bottom navigation +10. **`notification/`** - Sistema de notificações +11. **`quiz/`** - Questionários de avaliação +12. **`splash/`** - Tela de splash +13. **`support_center/`** - Pontos de apoio +14. **`users/`** - Perfis de usuário +15. **`zodiac/`** - Funcionalidade de signos + +## Estrutura Shared (`/lib/app/shared/`) + +``` +shared/ +├── design_system/ # Sistema de design +│ ├── colors.dart # Paleta de cores +│ ├── text_styles.dart # Estilos de texto +│ └── theme.dart # Tema da aplicação +├── logger/ # Sistema de logs +├── navigation/ # Navegação compartilhada +└── widgets/ # Widgets reutilizáveis +``` + +## Estrutura de Assets (`/assets/`) + +``` +assets/ +├── fonts/ # Arquivos de fonte +│ ├── ds/ # Design System +│ └── lato/ # Família Lato +├── images/ # Imagens e ícones + ├── chat/ # Imagens do chat + ├── svg/ # Ícones SVG organizados por feature + ├── support_center/ # Imagens dos pontos de apoio + ├── tutorial_*/ # Imagens dos tutoriais + └── zodiac/ # Ícones dos signos +``` + +## Estrutura de Testes (`/test/`) + +A estrutura de testes espelha a estrutura do código: + +``` +test/ +├── app/ +│ ├── core/ # Testes do core +│ ├── features/ # Testes das features +│ └── shared/ # Testes de componentes compartilhados +├── assets/ # Assets para testes +├── utils/ # Utilitários de teste +│ ├── mocks.dart # Mocks reutilizáveis +│ └── golden_config.dart # Configuração para golden tests +└── flutter_test_config.dart # Configuração global de testes +``` + +## Configurações de Plataforma + +### Android (`/android/`) +- `app/build.gradle` - Configurações do app +- `app/src/*/AndroidManifest.xml` - Manifestos por flavor +- `fastlane/` - Automação Android + +### iOS (`/ios/`) +- `Runner/Info.plist` - Configurações do app +- `Runner.xcodeproj/` - Projeto Xcode +- `fastlane/` - Automação iOS + +## Arquivos de Configuração Importantes + +### `pubspec.yaml` +- Dependências do projeto +- Configuração de assets +- Metadados do app + +### `analysis_options.yaml` +- Regras de linting +- Configurações do analyzer + +### `build.yaml` +- Configurações do build_runner +- Code generation + +### `.fvm/fvm_config.json` +- Versão do Flutter fixada para o projeto + +## Convenções de Nomenclatura + +1. **Arquivos**: snake_case (ex: `user_profile_page.dart`) +2. **Classes**: PascalCase (ex: `UserProfilePage`) +3. **Variáveis/Funções**: camelCase (ex: `getUserProfile`) +4. **Constantes**: SCREAMING_SNAKE_CASE ou camelCase prefixado com 'k' +5. **Arquivos privados**: Prefixo underscore (ex: `_private_helper.dart`) + +## Organização de Imports + +Ordem recomendada: +1. Imports do Dart +2. Imports de packages externos +3. Imports do projeto (relativos) +4. Exports + +```dart +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:mobx/mobx.dart'; + +import '../../core/error/failures.dart'; +import '../domain/entities/user.dart'; +``` + +## Próximos Passos + +- Explore as [Funcionalidades Principais](04-funcionalidades.md) para entender cada módulo +- Veja o [Gerenciamento de Estado](05-gerenciamento-estado.md) para padrões MobX +- Consulte a [Configuração do Ambiente](09-configuracao-desenvolvimento.md) para começar a desenvolver \ No newline at end of file diff --git a/docs/04-funcionalidades.md b/docs/04-funcionalidades.md new file mode 100644 index 000000000..3d307bc8a --- /dev/null +++ b/docs/04-funcionalidades.md @@ -0,0 +1,257 @@ +# Funcionalidades Principais + +## Visão Geral + +O aplicativo PenhaS oferece um conjunto abrangente de funcionalidades projetadas para apoiar mulheres em situações de violência doméstica. Cada funcionalidade foi cuidadosamente desenvolvida considerando segurança, privacidade e facilidade de uso. + +## 1. Autenticação e Segurança + +### Login e Registro +- **Registro seguro** com validação de dados +- **Login tradicional** com email e senha +- **Modo camuflado** (Stealth Mode) para situações de emergência +- **Login anônimo** para acesso rápido a recursos essenciais + +### Modo Camuflado (Stealth Mode) +```dart +// Ativação através de sequência especial na tela de login +// Aparência de calculadora ou app inofensivo +// Acesso rápido às funcionalidades de emergência +``` + +Características: +- Interface disfarçada +- Acesso com PIN especial +- Dados criptografados separadamente +- Logout automático por inatividade + +## 2. Central de Ajuda (Help Center) + +### Botão de Pânico +- **Acionamento rápido** em situações de emergência +- **Envio de localização** para contatos de confiança +- **Gravação de áudio** automática +- **Notificação silenciosa** para não alertar o agressor + +### Rede de Guardiões +- Cadastro de até 5 contatos de confiança +- Envio de alertas via SMS +- Compartilhamento de localização em tempo real +- Histórico de alertas enviados + +### Gravação de Áudio +- Gravação discreta de evidências +- Armazenamento seguro e criptografado +- Sincronização com servidor quando houver conexão +- Limite de duração configurável + +## 3. Feed Social + +### Publicações +- **Compartilhamento de experiências** de forma anônima ou identificada +- **Sistema de apoio** através de reações +- **Moderação de conteúdo** para ambiente seguro +- **Filtros por categoria** (violência física, psicológica, etc.) + +### Interações +```dart +// Tipos de interação disponíveis +enum TweetAction { + like, // Demonstrar apoio + comment, // Adicionar comentário de suporte + report, // Reportar conteúdo inadequado + share // Compartilhar (com cuidado) +} +``` + +### Filtros e Preferências +- Filtros por tipo de violência +- Ocultar conteúdo sensível +- Preferências de notificação +- Modo de leitura anônimo + +## 4. Chat Seguro + +### Canais de Comunicação +1. **Chat com Suporte Profissional** + - Atendimento por profissionais treinados + - Horário de atendimento configurável + - Fila de espera transparente + +2. **Chat Privado entre Usuárias** + - Mensagens criptografadas ponta a ponta + - Verificação de identidade + - Bloqueio e denúncia de usuários + +### Recursos do Chat +- Indicador de digitação +- Confirmação de leitura opcional +- Histórico de conversas +- Busca em mensagens + +## 5. Pontos de Apoio (Support Centers) + +### Mapa Interativo +- **Localização de serviços** próximos +- **Delegacias especializadas** da mulher +- **Centros de atendimento** psicológico e jurídico +- **Abrigos** e casas de acolhimento + +### Funcionalidades do Mapa +```dart +// Tipos de pontos de apoio +enum SupportCenterType { + police, // Delegacias + legal, // Apoio jurídico + psychological, // Apoio psicológico + shelter, // Abrigos + health // Serviços de saúde +} +``` + +### Informações Detalhadas +- Horário de funcionamento +- Telefones de contato +- Serviços oferecidos +- Avaliações de outras usuárias +- Rotas e navegação + +## 6. Manual de Fuga + +### Planejamento Seguro +- **Checklist personalizada** de itens importantes +- **Documentos necessários** organizados +- **Planejamento financeiro** básico +- **Estratégias de saída** segura + +### Tarefas e Acompanhamento +```dart +class EscapeManualTask { + String title; + String description; + bool isCompleted; + DateTime? completedAt; + TaskCategory category; // Documentos, Finanças, Segurança, etc. +} +``` + +### Sincronização e Backup +- Salvamento automático local +- Sincronização quando online +- Exportação segura de dados +- Compartilhamento controlado + +## 7. Quiz de Avaliação de Risco + +### Avaliação Personalizada +- Questionários baseados em evidências científicas +- Análise de risco de violência +- Recomendações personalizadas +- Acompanhamento temporal + +### Tipos de Avaliação +1. **Violência Física** +2. **Violência Psicológica** +3. **Violência Sexual** +4. **Violência Patrimonial** +5. **Violência Moral** + +### Resultados e Orientações +- Score de risco +- Orientações específicas +- Recursos recomendados +- Histórico de avaliações + +## 8. Sistema de Notificações + +### Tipos de Notificação +- **Alertas de segurança** +- **Mensagens do chat** +- **Atualizações do feed** +- **Lembretes do manual de fuga** + +### Configurações +- Notificações silenciosas +- Horários permitidos +- Prioridade por tipo +- Modo não perturbe + +## 9. Perfil e Configurações + +### Gestão de Perfil +- Informações básicas (podem ser fictícias por segurança) +- Avatar personalizado +- Biografia opcional +- Configurações de privacidade + +### Preferências do App +- Tema (claro/escuro) +- Idioma +- Tamanho da fonte +- Modo de economia de dados + +### Segurança da Conta +- Alteração de senha +- Autenticação em duas etapas (planejado) +- Dispositivos autorizados +- Exclusão segura de conta + +## 10. Feature Toggles + +Sistema de ativação/desativação remota de funcionalidades: + +```dart +// Configurações disponíveis via Remote Config +class FeatureFlags { + bool loginOffline; // Login no modo offline + bool chatPrivateEnabled; // Chat privado entre usuárias + bool escapeManualEnabled; // Manual de fuga + int quizAnimationDuration; // Duração das animações +} +``` + +## 11. Modo Offline + +### Funcionalidades Disponíveis Offline +- Acesso ao manual de fuga salvo +- Visualização de pontos de apoio cached +- Gravação de áudio local +- Leitura de conteúdo baixado + +### Sincronização Inteligente +- Queue de ações pendentes +- Sincronização automática ao conectar +- Priorização de dados críticos +- Feedback visual do status + +## 12. Acessibilidade + +### Recursos de Acessibilidade +- Suporte a leitores de tela +- Alto contraste +- Tamanhos de fonte ajustáveis +- Navegação por teclado (em tablets) + +## Integrações Entre Funcionalidades + +As funcionalidades trabalham de forma integrada: + +1. **Help Center + Guardiões**: Alertas automáticos +2. **Chat + Feed**: Suporte direto a autoras +3. **Quiz + Manual de Fuga**: Recomendações baseadas em risco +4. **Pontos de Apoio + Navegação**: Rotas seguras +5. **Notificações + Modo Camuflado**: Alertas discretos + +## Considerações de Segurança + +Todas as funcionalidades são desenvolvidas com foco em: +- **Privacidade**: Dados mínimos e criptografados +- **Discrição**: Interfaces que não chamam atenção +- **Rapidez**: Acesso rápido em emergências +- **Confiabilidade**: Funcionamento em condições adversas + +## Próximos Passos + +- Entenda o [Gerenciamento de Estado](05-gerenciamento-estado.md) das funcionalidades +- Veja [Autenticação e Segurança](06-autenticacao-seguranca.md) em detalhes +- Explore a [Integração com API](07-integracao-api.md) para sincronização de dados \ No newline at end of file diff --git a/docs/05-gerenciamento-estado.md b/docs/05-gerenciamento-estado.md new file mode 100644 index 000000000..2b93629f7 --- /dev/null +++ b/docs/05-gerenciamento-estado.md @@ -0,0 +1,559 @@ +# Gerenciamento de Estado + +## Visão Geral + +O aplicativo PenhaS utiliza **MobX** como solução principal para gerenciamento de estado, combinado com o padrão de **Stores** para organizar a lógica de negócio e estado da aplicação. Esta abordagem proporciona reatividade, performance e facilidade de manutenção. + +## Por que MobX? + +O MobX foi escolhido por várias razões: + +1. **Reatividade Automática**: Atualizações de UI sem boilerplate +2. **Performance**: Apenas componentes afetados são reconstruídos +3. **Simplicidade**: Sintaxe clara e intuitiva +4. **Testabilidade**: Fácil de testar stores isoladamente +5. **Debugging**: Ferramentas excelentes para debug + +## Conceitos Fundamentais + +### 1. Observables + +Valores que podem ser observados por mudanças: + +```dart +import 'package:mobx/mobx.dart'; + +part 'user_store.g.dart'; + +class UserStore = _UserStoreBase with _$UserStore; + +abstract class _UserStoreBase with Store { + @observable + String name = ''; + + @observable + bool isLoading = false; + + @observable + ObservableList permissions = ObservableList(); +} +``` + +### 2. Actions + +Métodos que modificam observables: + +```dart +abstract class _UserStoreBase with Store { + @observable + String name = ''; + + @action + void setName(String newName) { + name = newName; + } + + @action + Future loadUser() async { + isLoading = true; + try { + final user = await repository.getUser(); + name = user.name; + } finally { + isLoading = false; + } + } +} +``` + +### 3. Computed Values + +Valores derivados de outros observables: + +```dart +abstract class _UserStoreBase with Store { + @observable + String firstName = ''; + + @observable + String lastName = ''; + + @computed + String get fullName => '$firstName $lastName'.trim(); + + @computed + bool get hasCompleteName => firstName.isNotEmpty && lastName.isNotEmpty; +} +``` + +### 4. Reactions + +Efeitos colaterais baseados em mudanças de estado: + +```dart +class MyWidget extends StatefulWidget { + @override + _MyWidgetState createState() => _MyWidgetState(); +} + +class _MyWidgetState extends State { + final List _disposers = []; + final store = UserStore(); + + @override + void initState() { + super.initState(); + + // Reaction simples + _disposers.add( + reaction( + (_) => store.isLoggedIn, + (isLoggedIn) { + if (!isLoggedIn) { + Navigator.pushReplacementNamed(context, '/login'); + } + }, + ), + ); + + // AutoRun - executa sempre que observables mudam + _disposers.add( + autorun((_) { + print('User name: ${store.name}'); + }), + ); + } + + @override + void dispose() { + _disposers.forEach((d) => d()); + super.dispose(); + } +} +``` + +## Padrão Store + +### Estrutura Básica de uma Store + +```dart +// user_profile_store.dart +import 'package:mobx/mobx.dart'; +import 'package:dartz/dartz.dart'; + +part 'user_profile_store.g.dart'; + +class UserProfileStore = _UserProfileStoreBase with _$UserProfileStore; + +abstract class _UserProfileStoreBase with Store { + _UserProfileStoreBase({ + required this.repository, + required this.localStorage, + }); + + final IUserRepository repository; + final ILocalStorage localStorage; + + @observable + UserProfile? profile; + + @observable + bool isLoading = false; + + @observable + String? errorMessage; + + @computed + bool get hasProfile => profile != null; + + @action + Future loadProfile() async { + isLoading = true; + errorMessage = null; + + final result = await repository.getUserProfile(); + + result.fold( + (failure) => errorMessage = failure.message, + (userProfile) { + profile = userProfile; + _saveToCache(userProfile); + }, + ); + + isLoading = false; + } + + Future _saveToCache(UserProfile profile) async { + await localStorage.save('user_profile', profile.toJson()); + } +} +``` + +### Integração com Flutter + +Uso do `Observer` widget para reagir a mudanças: + +```dart +import 'package:flutter_mobx/flutter_mobx.dart'; + +class UserProfilePage extends StatelessWidget { + final UserProfileStore store; + + const UserProfilePage({required this.store}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Observer( + builder: (_) { + if (store.isLoading) { + return const CircularProgressIndicator(); + } + + if (store.errorMessage != null) { + return ErrorWidget(store.errorMessage!); + } + + if (!store.hasProfile) { + return const EmptyProfileWidget(); + } + + return ProfileContent(profile: store.profile!); + }, + ), + ); + } +} +``` + +## Organização de Stores + +### Stores Globais + +Gerenciadas pelo sistema de injeção de dependência: + +```dart +// No AppModule ou módulo específico +Bind.lazySingleton( + (i) => UserProfileStore( + repository: i.get(), + localStorage: i.get(), + ), +), +``` + +### Stores Locais + +Para estado específico de uma tela: + +```dart +class ChatChannelPage extends StatefulWidget { + final String channelId; + + @override + _ChatChannelPageState createState() => _ChatChannelPageState(); +} + +class _ChatChannelPageState extends State { + late final ChatChannelStore store; + + @override + void initState() { + super.initState(); + store = ChatChannelStore( + channelId: widget.channelId, + repository: Modular.get(), + ); + store.initialize(); + } + + @override + void dispose() { + store.dispose(); + super.dispose(); + } +} +``` + +## Padrões Comuns + +### 1. Estado de Loading + +```dart +abstract class _BaseStoreWithLoading with Store { + @observable + bool isLoading = false; + + @action + Future runWithLoading(Future Function() operation) async { + isLoading = true; + try { + return await operation(); + } finally { + isLoading = false; + } + } +} +``` + +### 2. Tratamento de Erros + +```dart +abstract class _BaseStoreWithError with Store { + @observable + String? errorMessage; + + @observable + bool hasError = false; + + @action + void setError(String message) { + errorMessage = message; + hasError = true; + } + + @action + void clearError() { + errorMessage = null; + hasError = false; + } +} +``` + +### 3. Paginação + +```dart +abstract class _PaginatedListStore with Store { + @observable + ObservableList items = ObservableList(); + + @observable + bool isLoadingMore = false; + + @observable + bool hasReachedEnd = false; + + @observable + int currentPage = 1; + + @action + Future loadMore() async { + if (isLoadingMore || hasReachedEnd) return; + + isLoadingMore = true; + + final newItems = await fetchPage(currentPage); + + if (newItems.isEmpty) { + hasReachedEnd = true; + } else { + items.addAll(newItems); + currentPage++; + } + + isLoadingMore = false; + } + + Future> fetchPage(int page); +} +``` + +### 4. Formulários + +```dart +abstract class _FormStore with Store { + @observable + String email = ''; + + @observable + String password = ''; + + @observable + ObservableMap errors = ObservableMap(); + + @computed + bool get isValid => email.isNotEmpty && password.isNotEmpty && errors.isEmpty; + + @action + void setEmail(String value) { + email = value; + _validateEmail(); + } + + @action + void setPassword(String value) { + password = value; + _validatePassword(); + } + + void _validateEmail() { + if (!EmailValidator.validate(email)) { + errors['email'] = 'Email inválido'; + } else { + errors.remove('email'); + } + } + + void _validatePassword() { + if (password.length < 6) { + errors['password'] = 'Senha deve ter pelo menos 6 caracteres'; + } else { + errors.remove('password'); + } + } +} +``` + +## Comunicação Entre Stores + +### Via Injeção de Dependência + +```dart +class FeedStore = _FeedStoreBase with _$FeedStore; + +abstract class _FeedStoreBase with Store { + _FeedStoreBase({ + required this.userStore, + required this.repository, + }); + + final UserStore userStore; + final IFeedRepository repository; + + @action + Future createPost(String content) async { + if (!userStore.isLoggedIn) { + throw Exception('User must be logged in'); + } + + final post = await repository.createPost( + content: content, + userId: userStore.profile!.id, + ); + + // Update feed + posts.insert(0, post); + } +} +``` + +### Via Eventos/Streams + +```dart +class EventBus { + static final _streamController = StreamController.broadcast(); + + static Stream on() { + return _streamController.stream.whereType(); + } + + static void fire(AppEvent event) { + _streamController.add(event); + } +} + +// Uso em stores +abstract class _NotificationStoreBase with Store { + _NotificationStoreBase() { + EventBus.on().listen((event) { + addNotification(event.message); + }); + } +} +``` + +## Code Generation + +O MobX usa code generation para criar o boilerplate: + +```bash +# Gerar uma vez +fvm flutter pub run build_runner build + +# Watch mode para desenvolvimento +fvm flutter pub run build_runner watch --delete-conflicting-outputs +``` + +## Testes + +### Testando Stores + +```dart +void main() { + group('UserProfileStore', () { + late UserProfileStore store; + late MockUserRepository mockRepository; + late MockLocalStorage mockStorage; + + setUp(() { + mockRepository = MockUserRepository(); + mockStorage = MockLocalStorage(); + store = UserProfileStore( + repository: mockRepository, + localStorage: mockStorage, + ); + }); + + test('loadProfile success', () async { + final profile = UserProfile(id: '1', name: 'Test'); + when(() => mockRepository.getUserProfile()) + .thenAnswer((_) async => Right(profile)); + + await store.loadProfile(); + + expect(store.profile, equals(profile)); + expect(store.isLoading, isFalse); + expect(store.errorMessage, isNull); + }); + + test('computed values work correctly', () { + expect(store.hasProfile, isFalse); + + store.profile = UserProfile(id: '1', name: 'Test'); + + expect(store.hasProfile, isTrue); + }); + }); +} +``` + +## Boas Práticas + +1. **Mantenha stores focadas**: Uma store por domínio/feature +2. **Use computed values**: Para valores derivados +3. **Actions assíncronas**: Sempre marque com `@action` +4. **Evite lógica na UI**: Coloque na store +5. **Dispose reactions**: Limpe reactions quando não necessárias +6. **Use ObservableList/Map**: Para coleções reativas +7. **Teste stores isoladamente**: Facilita manutenção + +## Performance + +### Otimizações + +1. **Use `Observer` com parcimônia**: Apenas onde necessário +2. **Computed values**: São cached automaticamente +3. **Reactions específicas**: Use `when` e `reaction` ao invés de `autorun` +4. **Batch updates**: MobX agrupa mudanças automaticamente + +### Debug + +```dart +// Habilitar logs do MobX +import 'package:mobx/mobx.dart'; + +void main() { + mainContext.config = mainContext.config.clone( + isSpyEnabled: true, + ); + + runApp(MyApp()); +} +``` + +## Próximos Passos + +- Veja [Autenticação e Segurança](06-autenticacao-seguranca.md) para gerenciamento de sessão +- Explore [Integração com API](07-integracao-api.md) para sincronização de estado +- Consulte [Testes](08-testes.md) para estratégias de teste de stores \ No newline at end of file diff --git a/docs/06-autenticacao-seguranca.md b/docs/06-autenticacao-seguranca.md new file mode 100644 index 000000000..f5197c53c --- /dev/null +++ b/docs/06-autenticacao-seguranca.md @@ -0,0 +1,506 @@ +# Autenticação e Segurança + +## Visão Geral + +A segurança é um aspecto fundamental do aplicativo PenhaS, considerando a natureza sensível dos dados e a vulnerabilidade das usuárias. O sistema implementa múltiplas camadas de segurança, desde a autenticação até o armazenamento de dados. + +## Fluxos de Autenticação + +### 1. Login Tradicional + +```dart +class AuthenticateUserUseCase { + Future> call({ + required String email, + required String password, + }) async { + // Validação local + if (!EmailValidator.validate(email)) { + return Left(ValidationFailure('Email inválido')); + } + + // Chamada à API + final result = await repository.authenticate( + email: email, + password: password, + ); + + return result.fold( + (failure) => Left(failure), + (session) async { + // Salvar token seguramente + await appConfiguration.saveApiToken(token: session.token); + // Salvar perfil do usuário + await userProfileStore.save(session.user); + return Right(session); + }, + ); + } +} +``` + +### 2. Modo Camuflado (Stealth Mode) + +O modo camuflado é uma funcionalidade crítica de segurança: + +```dart +class AuthenticateStealthUserUseCase { + Future> call({ + required String pin, + }) async { + // Hash do PIN para comparação + final hashedPin = _hashPin(pin); + + // Verificar PIN offline primeiro + final storedHash = await appConfiguration.offlineHash; + + if (storedHash.isNotEmpty && hashedPin == storedHash) { + // Login offline bem-sucedido + return _offlineLogin(); + } + + // Tentar login online + return _onlineStealthLogin(hashedPin); + } + + String _hashPin(String pin) { + // Usar crypt para hash seguro + final salt = '\$2a\$10\$' + _generateSalt(); + return Crypt.sha256(pin, salt: salt).toString(); + } +} +``` + +#### Características do Modo Camuflado: +- Interface disfarçada de calculadora +- PIN ao invés de senha completa +- Dados separados e criptografados +- Logout automático por inatividade +- Sem rastros visíveis do app real + +### 3. Login Anônimo + +Para acesso rápido a recursos essenciais: + +```dart +class AuthenticateAnonymousUserUseCase { + Future> call() async { + // Gerar identificador único temporário + final tempId = Uuid().v4(); + + // Criar sessão anônima limitada + final anonymousSession = SessionEntity( + token: 'anonymous_$tempId', + user: UserProfile.anonymous(), + permissions: ['read_public', 'emergency_features'], + ); + + await appConfiguration.saveAnonymousSession(anonymousSession); + + return Right(anonymousSession); + } +} +``` + +## Armazenamento Seguro + +### 1. Flutter Secure Storage + +Para dados sensíveis como tokens e credenciais: + +```dart +class SecureLocalStorage implements ISecureStorage { + static const _storage = FlutterSecureStorage(); + + // Opções de segurança para Android + static const _androidOptions = AndroidOptions( + encryptedSharedPreferences: true, + sharedPreferencesName: 'secure_prefs', + preferencesKeyPrefix: 'secure_', + ); + + // Opções de segurança para iOS + static const _iosOptions = IOSOptions( + accessibility: IOSAccessibility.first_unlock_this_device, + accountName: 'penhas_secure', + ); + + Future saveSecure(String key, String value) async { + await _storage.write( + key: key, + value: value, + aOptions: _androidOptions, + iOptions: _iosOptions, + ); + } + + Future readSecure(String key) async { + return await _storage.read( + key: key, + aOptions: _androidOptions, + iOptions: _iosOptions, + ); + } + + Future deleteSecure(String key) async { + await _storage.delete( + key: key, + aOptions: _androidOptions, + iOptions: _iosOptions, + ); + } +} +``` + +### 2. Hive com Criptografia + +Para dados locais que precisam de criptografia: + +```dart +class HiveCacheStorage implements ICacheStorage { + late final HiveAesCipher _cipher; + late final Box _box; + + Future initialize() async { + // Gerar ou recuperar chave de criptografia + final encryptionKey = await _getOrGenerateEncryptionKey(); + + // Criar cipher com a chave + _cipher = HiveAesCipher(encryptionKey); + + // Abrir box criptografado + _box = await Hive.openBox( + 'secure_cache', + encryptionCipher: _cipher, + ); + } + + Future _getOrGenerateEncryptionKey() async { + const keyName = 'hive_encryption_key'; + + // Tentar recuperar chave existente + final existingKey = await secureStorage.readSecure(keyName); + + if (existingKey != null) { + return base64Decode(existingKey); + } + + // Gerar nova chave + final newKey = Hive.generateSecureKey(); + await secureStorage.saveSecure( + keyName, + base64Encode(newKey), + ); + + return newKey; + } +} +``` + +## Gerenciamento de Sessão + +### Token Management + +```dart +class AppConfiguration implements IAppConfiguration { + static const _tokenKey = 'br.com.penhas.tokenServer'; + static const _tokenExpiry = 'br.com.penhas.tokenExpiry'; + + Future get isTokenValid async { + final token = await apiToken; + if (token.isEmpty) return false; + + final expiryString = await _storage.get(_tokenExpiry); + if (expiryString == null) return false; + + final expiry = DateTime.parse(expiryString); + return DateTime.now().isBefore(expiry); + } + + Future refreshTokenIfNeeded() async { + if (await isTokenValid) return; + + // Implementar refresh token + final result = await _authRepository.refreshToken(); + + result.fold( + (failure) => logout(), + (newToken) => saveApiToken(token: newToken), + ); + } +} +``` + +### Logout e Limpeza de Dados + +```dart +Future logout() async { + // Parar serviços em background + await _flutterBackgroundService.invoke('stopService'); + + // Limpar tokens e credenciais + await Future.wait([ + _storage.delete(_tokenKey), + _storage.delete(_offlineHash), + _storage.delete(_tokenExpiry), + ]); + + // Limpar cache criptografado + await _hive.deleteFromDisk(); + + // Limpar dados do usuário + await _clearUserData(); + + // Navegar para login + Modular.to.navigate('/authentication'); +} +``` + +## Segurança de Rede + +### Interceptor de Autenticação + +```dart +class AuthInterceptor implements InterceptorContract { + final IAppConfiguration appConfiguration; + + @override + Future interceptRequest({ + required BaseRequest request, + }) async { + final token = await appConfiguration.apiToken; + + if (token.isNotEmpty) { + request.headers['Authorization'] = 'Bearer $token'; + } + + // Adicionar headers de segurança + request.headers['X-App-Version'] = packageInfo.version; + request.headers['X-Platform'] = Platform.operatingSystem; + + return request; + } + + @override + Future interceptResponse({ + required BaseResponse response, + }) async { + // Verificar se token expirou + if (response.statusCode == 401) { + await appConfiguration.logout(); + throw UnauthorizedException(); + } + + return response; + } +} +``` + +### Certificate Pinning (Planejado) + +```dart +class SecureHttpClient { + static HttpClient createSecureClient() { + final client = HttpClient(); + + // Configurar certificate pinning + client.badCertificateCallback = (cert, host, port) { + // Verificar fingerprint do certificado + final expectedFingerprint = 'SHA256:...'; + final actualFingerprint = _calculateFingerprint(cert); + + return actualFingerprint == expectedFingerprint; + }; + + return client; + } +} +``` + +## Funcionalidades de Emergência + +### Botão de Pânico + +```dart +class PanicButtonService { + static const _panicChannel = MethodChannel('penhas.panic'); + + Future activatePanicMode() async { + // Ativar modo de emergência + await _panicChannel.invokeMethod('activate'); + + // Enviar localização para guardiões + await _sendEmergencyAlert(); + + // Iniciar gravação de áudio + await _startAudioRecording(); + + // Limpar dados sensíveis da memória + await _clearSensitiveData(); + } + + Future _clearSensitiveData() async { + // Limpar mensagens do chat + await chatStore.clearMessages(); + + // Limpar dados temporários + await cacheStorage.clear(); + + // Mudar para interface camuflada + await _switchToStealthUI(); + } +} +``` + +### Auto-logout por Inatividade + +```dart +class InactivityLogoutUseCase { + static const _inactivityTimeout = Duration(minutes: 5); + Timer? _logoutTimer; + + void startInactivityTimer() { + _logoutTimer?.cancel(); + + _logoutTimer = Timer(_inactivityTimeout, () { + if (appPreferences.autoLogoutEnabled) { + _performAutoLogout(); + } + }); + } + + void resetInactivityTimer() { + startInactivityTimer(); + } + + Future _performAutoLogout() async { + // Salvar estado se necessário + await _saveCurrentState(); + + // Logout silencioso + await appConfiguration.logout(); + + // Mostrar tela de bloqueio + Modular.to.pushReplacementNamed('/lock-screen'); + } +} +``` + +## Criptografia de Dados Locais + +### Criptografia de Arquivos de Áudio + +```dart +class AudioEncryption { + static Future encryptAudio( + Uint8List audioData, + String userId, + ) async { + // Gerar chave derivada do usuário + final key = await _deriveKey(userId); + + // Gerar IV aleatório + final iv = _generateIV(); + + // Criptografar usando AES-256-GCM + final encrypter = Encrypter(AES(key, mode: AESMode.gcm)); + final encrypted = encrypter.encryptBytes( + audioData, + iv: iv, + ); + + // Retornar IV + dados criptografados + return Uint8List.fromList([ + ...iv.bytes, + ...encrypted.bytes, + ]); + } + + static Future _deriveKey(String userId) async { + // Usar PBKDF2 para derivar chave + final salt = await secureStorage.readSecure('user_salt_$userId'); + + return Key.fromBase64( + pbkdf2.generateKey(userId, salt, 10000, 32), + ); + } +} +``` + +## Validações de Segurança + +### Validação de Entrada + +```dart +class SecurityValidators { + static bool isValidPassword(String password) { + // Mínimo 8 caracteres + if (password.length < 8) return false; + + // Deve conter letra maiúscula + if (!password.contains(RegExp(r'[A-Z]'))) return false; + + // Deve conter letra minúscula + if (!password.contains(RegExp(r'[a-z]'))) return false; + + // Deve conter número + if (!password.contains(RegExp(r'[0-9]'))) return false; + + // Deve conter caractere especial + if (!password.contains(RegExp(r'[!@#$%^&*(),.?":{}|<>]'))) { + return false; + } + + return true; + } + + static String sanitizeInput(String input) { + // Remover caracteres perigosos + return input + .replaceAll(RegExp(r'<[^>]*>'), '') // Remove HTML + .replaceAll(RegExp(r'[^\w\s-.]'), '') // Remove especiais + .trim(); + } +} +``` + +## Boas Práticas de Segurança + +1. **Nunca armazene senhas em texto claro** +2. **Use HTTPS para todas as comunicações** +3. **Implemente certificate pinning em produção** +4. **Criptografe dados sensíveis localmente** +5. **Valide todas as entradas do usuário** +6. **Implemente rate limiting** +7. **Use tokens com expiração** +8. **Limpe dados na memória após uso** +9. **Ofusque código em produção** +10. **Monitore tentativas de acesso suspeitas** + +## Auditoria e Logs + +```dart +class SecurityAuditLogger { + static void logSecurityEvent(SecurityEvent event) { + // Não logar informações sensíveis + final sanitizedEvent = event.sanitize(); + + // Enviar para analytics + analytics.logEvent( + 'security_event', + parameters: sanitizedEvent.toMap(), + ); + + // Salvar localmente para análise + if (event.severity == Severity.high) { + _saveForLaterSync(sanitizedEvent); + } + } +} +``` + +## Próximos Passos + +- Veja [Integração com API](07-integracao-api.md) para segurança na comunicação +- Consulte [Testes](08-testes.md) para testes de segurança +- Revise [Resolução de Problemas](12-troubleshooting.md) para issues de segurança \ No newline at end of file diff --git a/docs/08-testes.md b/docs/08-testes.md new file mode 100644 index 000000000..8e4fbd132 --- /dev/null +++ b/docs/08-testes.md @@ -0,0 +1,711 @@ +# Estratégia de Testes + +## Visão Geral + +O aplicativo PenhaS implementa uma estratégia abrangente de testes para garantir qualidade, confiabilidade e manutenibilidade. A abordagem inclui testes unitários, de widget, de integração e golden tests. + +## Tipos de Testes + +### 1. Testes Unitários + +Testam unidades isoladas de código como funções, classes e métodos. + +```dart +// test/app/features/authentication/domain/usecases/authenticate_user_test.dart + +void main() { + group('AuthenticateUserUseCase', () { + late AuthenticateUserUseCase useCase; + late MockAuthenticationRepository mockRepository; + late MockAppConfiguration mockAppConfig; + late MockUserProfileStore mockUserStore; + + setUp(() { + mockRepository = MockAuthenticationRepository(); + mockAppConfig = MockAppConfiguration(); + mockUserStore = MockUserProfileStore(); + + useCase = AuthenticateUserUseCase( + repository: mockRepository, + appConfiguration: mockAppConfig, + userProfileStore: mockUserStore, + ); + }); + + test('should return SessionEntity when authentication succeeds', () async { + // Arrange + const email = 'test@example.com'; + const password = 'Test@123'; + final expectedSession = SessionEntity( + token: 'valid_token', + user: UserProfile(id: '1', email: email), + ); + + when(() => mockRepository.authenticate( + email: email, + password: password, + )).thenAnswer((_) async => Right(expectedSession)); + + when(() => mockAppConfig.saveApiToken(token: any(named: 'token'))) + .thenAnswer((_) async => {}); + + when(() => mockUserStore.save(any())) + .thenAnswer((_) async => {}); + + // Act + final result = await useCase(email: email, password: password); + + // Assert + expect(result.isRight(), isTrue); + result.fold( + (failure) => fail('Should not return failure'), + (session) { + expect(session.token, equals('valid_token')); + expect(session.user.email, equals(email)); + }, + ); + + verify(() => mockAppConfig.saveApiToken(token: 'valid_token')).called(1); + verify(() => mockUserStore.save(expectedSession.user)).called(1); + }); + + test('should return ValidationFailure for invalid email', () async { + // Arrange + const invalidEmail = 'invalid-email'; + const password = 'Test@123'; + + // Act + final result = await useCase(email: invalidEmail, password: password); + + // Assert + expect(result.isLeft(), isTrue); + result.fold( + (failure) => expect(failure, isA()), + (_) => fail('Should not return success'), + ); + + verifyNever(() => mockRepository.authenticate( + email: any(named: 'email'), + password: any(named: 'password'), + )); + }); + }); +} +``` + +### 2. Testes de Widget + +Testam widgets individuais e sua interação. + +```dart +// test/app/features/authentication/presentation/sign_in/sign_in_page_test.dart + +void main() { + group('SignInPage', () { + late SignInController mockController; + + setUp(() { + mockController = MockSignInController(); + when(() => mockController.isLoading).thenReturn(false); + when(() => mockController.errorMessage).thenReturn(null); + }); + + testWidgets('should display email and password fields', (tester) async { + // Arrange & Act + await tester.pumpWidget( + MaterialApp( + home: SignInPage(controller: mockController), + ), + ); + + // Assert + expect(find.byType(TextFormField), findsNWidgets(2)); + expect(find.text('Email'), findsOneWidget); + expect(find.text('Senha'), findsOneWidget); + expect(find.byType(ElevatedButton), findsOneWidget); + }); + + testWidgets('should show loading indicator when isLoading is true', + (tester) async { + // Arrange + when(() => mockController.isLoading).thenReturn(true); + + // Act + await tester.pumpWidget( + MaterialApp( + home: SignInPage(controller: mockController), + ), + ); + + // Assert + expect(find.byType(CircularProgressIndicator), findsOneWidget); + expect(find.byType(ElevatedButton), findsNothing); + }); + + testWidgets('should call signIn when button is pressed', (tester) async { + // Arrange + when(() => mockController.signIn()).thenAnswer((_) async => {}); + + await tester.pumpWidget( + MaterialApp( + home: SignInPage(controller: mockController), + ), + ); + + // Act + await tester.enterText( + find.byKey(const Key('email_field')), + 'test@example.com', + ); + await tester.enterText( + find.byKey(const Key('password_field')), + 'Test@123', + ); + await tester.tap(find.byType(ElevatedButton)); + await tester.pump(); + + // Assert + verify(() => mockController.signIn()).called(1); + }); + }); +} +``` + +### 3. Testes de Integração + +Testam a integração entre múltiplos componentes. + +```dart +// test/app/features/chat/integration/chat_flow_test.dart + +void main() { + group('Chat Flow Integration', () { + late ChatChannelUseCase chatUseCase; + late MockChatRepository mockRepository; + late MockWebSocketService mockWebSocket; + + setUp(() { + mockRepository = MockChatRepository(); + mockWebSocket = MockWebSocketService(); + + chatUseCase = ChatChannelUseCase( + repository: mockRepository, + webSocketService: mockWebSocket, + ); + }); + + test('should load messages and connect to WebSocket', () async { + // Arrange + const channelId = 'channel_123'; + final messages = [ + ChatMessage(id: '1', content: 'Hello', senderId: 'user1'), + ChatMessage(id: '2', content: 'Hi', senderId: 'user2'), + ]; + + when(() => mockRepository.getMessages(channelId)) + .thenAnswer((_) async => Right(messages)); + + when(() => mockWebSocket.connect(channelId)) + .thenAnswer((_) async => {}); + + // Act + final stream = chatUseCase.getChannelStream(channelId); + final events = await stream.take(2).toList(); + + // Assert + expect(events[0], isA()); + expect(events[1], isA()); + + final loadedEvent = events[1] as ChatChannelLoaded; + expect(loadedEvent.messages, equals(messages)); + + verify(() => mockWebSocket.connect(channelId)).called(1); + }); + }); +} +``` + +### 4. Golden Tests + +Testam a aparência visual dos widgets. + +```dart +// test/app/features/feed/presentation/golden/feed_card_golden_test.dart + +void main() { + group('FeedCard Golden Tests', () { + setUpAll(() async { + await loadAppFonts(); + }); + + testGoldens('FeedCard appearances', (tester) async { + final builder = GoldenBuilder.grid( + columns: 2, + widthToHeightRatio: 1, + ) + ..addScenario( + 'Normal post', + FeedCard( + post: Post( + id: '1', + content: 'Este é um post de exemplo', + author: 'Maria Silva', + createdAt: DateTime(2024, 1, 1), + likes: 42, + ), + ), + ) + ..addScenario( + 'Post with image', + FeedCard( + post: Post( + id: '2', + content: 'Post com imagem', + author: 'Ana Santos', + imageUrl: 'assets/test/sample_image.png', + createdAt: DateTime(2024, 1, 1), + likes: 100, + ), + ), + ) + ..addScenario( + 'Anonymous post', + FeedCard( + post: Post( + id: '3', + content: 'Post anônimo sobre violência', + author: 'Anônima', + isAnonymous: true, + createdAt: DateTime(2024, 1, 1), + likes: 15, + ), + ), + ); + + await tester.pumpWidgetBuilder( + builder.build(), + surfaceSize: const Size(800, 1200), + ); + + await screenMatchesGolden(tester, 'feed_card_variations'); + }); + }); +} +``` + +## Configuração de Testes + +### Flutter Test Config + +```dart +// test/flutter_test_config.dart + +import 'dart:async'; +import 'package:alchemist/alchemist.dart'; +import 'utils/test_utils.dart'; + +Future testExecutable(FutureOr Function() testMain) async { + // Configurar golden tests + await preparePageTests(testMain); + + // Configurar fonts para golden tests + await loadAppFonts(); + + // Configurar timezone para testes consistentes + initializeTimeZones(); + + // Executar testes + return testMain(); +} +``` + +### Test Utils + +```dart +// test/utils/test_utils.dart + +/// Configura o ambiente de teste +Future preparePageTests(FutureOr Function() testMain) async { + // Configurar binding de teste + TestWidgetsFlutterBinding.ensureInitialized(); + + // Configurar tamanho padrão de tela + binding.window.physicalSizeTestValue = const Size(414, 896); // iPhone 11 + binding.window.devicePixelRatioTestValue = 2.0; + + // Limpar após testes + tearDown(() { + binding.window.clearPhysicalSizeTestValue(); + binding.window.clearDevicePixelRatioTestValue(); + }); + + await testMain(); +} + +/// Cria um widget testável com todas as dependências +Widget makeTestableWidget({ + required Widget child, + List? modules, + NavigatorObserver? navigatorObserver, +}) { + return ModularApp( + module: TestModule(modules: modules ?? []), + child: MaterialApp( + home: child, + navigatorObservers: [ + if (navigatorObserver != null) navigatorObserver, + ], + localizationsDelegates: const [ + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + supportedLocales: const [Locale('pt', 'BR')], + ), + ); +} +``` + +## Mocks e Stubs + +### Usando Mocktail + +```dart +// test/utils/mocks.dart + +import 'package:mocktail/mocktail.dart'; + +// Mock de Repository +class MockUserRepository extends Mock implements IUserRepository {} + +// Mock de Use Case +class MockAuthenticateUserUseCase extends Mock + implements AuthenticateUserUseCase {} + +// Mock de Store +class MockUserProfileStore extends Mock implements UserProfileStore {} + +// Mock de API Provider +class MockApiProvider extends Mock implements IApiProvider {} + +// Registrar tipos customizados +void registerFallbackValues() { + registerFallbackValue(UserProfile.empty()); + registerFallbackValue(PaginatedRequest()); + registerFallbackValue(Uri.parse('https://example.com')); +} +``` + +### Factory de Dados de Teste + +```dart +// test/utils/factories.dart + +class TestFactory { + static UserProfile createUser({ + String? id, + String? email, + String? name, + }) { + return UserProfile( + id: id ?? const Uuid().v4(), + email: email ?? faker.internet.email(), + name: name ?? faker.person.name(), + createdAt: DateTime.now(), + ); + } + + static Post createPost({ + String? content, + String? authorId, + int? likes, + }) { + return Post( + id: const Uuid().v4(), + content: content ?? faker.lorem.sentence(), + authorId: authorId ?? const Uuid().v4(), + likes: likes ?? faker.randomGenerator.integer(100), + createdAt: DateTime.now(), + ); + } + + static ChatMessage createMessage({ + String? content, + String? senderId, + }) { + return ChatMessage( + id: const Uuid().v4(), + content: content ?? faker.lorem.sentence(), + senderId: senderId ?? const Uuid().v4(), + createdAt: DateTime.now(), + ); + } +} +``` + +## Testes de Store (MobX) + +### Testando Stores MobX + +```dart +// test/app/features/feed/presentation/feed_store_test.dart + +void main() { + group('FeedStore', () { + late FeedStore store; + late MockFeedRepository mockRepository; + + setUp(() { + mockRepository = MockFeedRepository(); + store = FeedStore(repository: mockRepository); + }); + + test('initial values are correct', () { + expect(store.posts, isEmpty); + expect(store.isLoading, isFalse); + expect(store.hasError, isFalse); + expect(store.currentPage, equals(1)); + }); + + test('loadPosts updates posts list', () async { + // Arrange + final posts = List.generate( + 5, + (_) => TestFactory.createPost(), + ); + + when(() => mockRepository.getPosts(page: 1)) + .thenAnswer((_) async => Right(PaginatedResponse( + data: posts, + currentPage: 1, + totalPages: 3, + hasNext: true, + ))); + + // Act + await store.loadPosts(); + + // Assert + expect(store.posts, equals(posts)); + expect(store.isLoading, isFalse); + expect(store.hasError, isFalse); + expect(store.hasMorePages, isTrue); + }); + + test('computed values work correctly', () { + // Arrange + store.posts = ObservableList.of([ + TestFactory.createPost(likes: 10), + TestFactory.createPost(likes: 20), + TestFactory.createPost(likes: 30), + ]); + + // Assert + expect(store.totalLikes, equals(60)); + expect(store.averageLikes, equals(20)); + expect(store.hasPosts, isTrue); + }); + + test('reactions trigger correctly', () async { + // Arrange + final reactions = []; + + final disposer = reaction( + (_) => store.posts.length, + (length) => reactions.add('Posts count: $length'), + ); + + // Act + store.posts.add(TestFactory.createPost()); + await Future.delayed(Duration.zero); // Aguardar reaction + + store.posts.add(TestFactory.createPost()); + await Future.delayed(Duration.zero); + + // Assert + expect(reactions, equals([ + 'Posts count: 1', + 'Posts count: 2', + ])); + + // Cleanup + disposer(); + }); + }); +} +``` + +## Testes de API + +### Mock de Respostas HTTP + +```dart +// test/utils/api_mock_adapter.dart + +class MockApiAdapter extends Mock implements http.Client { + final Map _responses = {}; + + void mockGet(String path, dynamic response, {int statusCode = 200}) { + when(() => get( + any(that: HasPath(path)), + headers: any(named: 'headers'), + )).thenAnswer((_) async => http.Response( + json.encode(response), + statusCode, + )); + } + + void mockPost( + String path, + dynamic response, { + int statusCode = 200, + dynamic Function(Map)? bodyValidator, + }) { + when(() => post( + any(that: HasPath(path)), + headers: any(named: 'headers'), + body: bodyValidator != null ? any(that: BodyValidator(bodyValidator)) : any(named: 'body'), + )).thenAnswer((_) async => http.Response( + json.encode(response), + statusCode, + )); + } + + void mockError(String path, int statusCode, {String? message}) { + when(() => get( + any(that: HasPath(path)), + headers: any(named: 'headers'), + )).thenAnswer((_) async => http.Response( + json.encode({'message': message ?? 'Error'}), + statusCode, + )); + } +} + +// Custom Matcher +class HasPath extends CustomMatcher { + HasPath(String path) : super('Uri with path', 'path', path); + + @override + Object? featureValueOf(actual) => (actual as Uri).path; +} +``` + +## Coverage e Relatórios + +### Executar Testes com Coverage + +```bash +# Executar todos os testes com coverage +fvm flutter test --coverage + +# Executar testes específicos +fvm flutter test test/app/features/authentication --coverage + +# Gerar relatório HTML +genhtml coverage/lcov.info -o coverage/html + +# Abrir relatório +open coverage/html/index.html +``` + +### Configuração de Coverage + +```yaml +# coverage_config.yaml +include: + - lib/** +exclude: + - lib/**/*.g.dart + - lib/**/*.freezed.dart + - lib/generated/** + - lib/l10n/** +``` + +## Testes de Performance + +```dart +// test/performance/feed_scroll_performance_test.dart + +void main() { + group('Feed Performance', () { + testWidgets('scrolling performance', (tester) async { + // Arrange + final posts = List.generate(100, (_) => TestFactory.createPost()); + + await tester.pumpWidget( + makeTestableWidget( + child: FeedPage(posts: posts), + ), + ); + + // Act & Assert + final Stopwatch stopwatch = Stopwatch()..start(); + + // Scroll através da lista + await tester.fling( + find.byType(ListView), + const Offset(0, -300), + 1000, + ); + + await tester.pumpAndSettle(); + + stopwatch.stop(); + + // Verificar performance + expect( + stopwatch.elapsedMilliseconds, + lessThan(100), // Deve completar em menos de 100ms + ); + }); + }); +} +``` + +## Boas Práticas + +1. **AAA Pattern**: Arrange, Act, Assert +2. **Testes isolados**: Cada teste deve ser independente +3. **Nomes descritivos**: Descreva o que está sendo testado +4. **Mock mínimo**: Mock apenas o necessário +5. **Testes determinísticos**: Evite dependências de tempo/random +6. **Coverage adequado**: Foque em qualidade, não quantidade +7. **Testes de regressão**: Adicione testes para bugs corrigidos + +## CI/CD Integration + +```yaml +# .github/workflows/test.yml +name: Tests + +on: [push, pull_request] + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - uses: subosito/flutter-action@v2 + with: + flutter-version: '3.0.5' + + - name: Install dependencies + run: flutter pub get + + - name: Run tests + run: flutter test --coverage + + - name: Upload coverage + uses: codecov/codecov-action@v1 + with: + file: coverage/lcov.info +``` + +## Próximos Passos + +- Veja [Configuração do Ambiente](09-configuracao-desenvolvimento.md) para setup de testes +- Consulte [Build e Deploy](10-build-deploy.md) para testes em CI/CD +- Revise [Resolução de Problemas](12-troubleshooting.md) para problemas comuns em testes \ No newline at end of file diff --git a/docs/09-configuracao-desenvolvimento.md b/docs/09-configuracao-desenvolvimento.md new file mode 100644 index 000000000..3a51d52df --- /dev/null +++ b/docs/09-configuracao-desenvolvimento.md @@ -0,0 +1,472 @@ +# Configuração do Ambiente de Desenvolvimento + +## Visão Geral + +Este guia detalha o processo de configuração do ambiente de desenvolvimento para o aplicativo PenhaS. Siga os passos cuidadosamente para garantir que todas as dependências e ferramentas estejam corretamente instaladas. + +## Pré-requisitos + +### Sistema Operacional +- **macOS**: Recomendado para desenvolvimento iOS e Android +- **Linux**: Ubuntu 20.04+ ou distribuições similares para Android +- **Windows**: Windows 10+ com WSL2 para melhor compatibilidade + +### Ferramentas Essenciais + +1. **Git** + ```bash + # macOS + brew install git + + # Linux + sudo apt-get install git + + # Windows + # Baixar de https://git-scm.com/ + ``` + +2. **VS Code** (Recomendado) + - Download: https://code.visualstudio.com/ + - Extensões recomendadas: + - Flutter + - Dart + - Flutter Coverage + - Coverage Gutters + - GitLens + - Error Lens + +## Instalação do Flutter + +### 1. FVM (Flutter Version Management) + +O projeto usa FVM para gerenciar versões do Flutter: + +```bash +# macOS/Linux +curl -fsSL https://fvm.app/install.sh | bash + +# Windows (PowerShell como Admin) +choco install fvm + +# Verificar instalação +fvm --version +``` + +### 2. Instalar Flutter via FVM + +```bash +# Navegar até o diretório do projeto +cd penhas-app + +# Instalar a versão do Flutter especificada no projeto +fvm install + +# Usar a versão instalada +fvm use + +# Verificar instalação +fvm flutter --version +``` + +### 3. Configurar PATH (Opcional) + +Para usar `flutter` ao invés de `fvm flutter`: + +```bash +# macOS/Linux - adicionar ao ~/.bashrc ou ~/.zshrc +export PATH="$PATH:$HOME/fvm/default/bin" + +# Windows - adicionar às variáveis de ambiente +# %USERPROFILE%\fvm\default\bin +``` + +## Configuração Android + +### 1. Android Studio + +1. Download: https://developer.android.com/studio +2. Durante instalação, certifique-se de instalar: + - Android SDK + - Android SDK Command-line Tools + - Android SDK Build-Tools + - Android SDK Platform-Tools + +### 2. Configurar Android SDK + +```bash +# Verificar instalação +fvm flutter doctor -v + +# Aceitar licenças Android +fvm flutter doctor --android-licenses +``` + +### 3. Configurar Variáveis de Ambiente + +```bash +# macOS/Linux - adicionar ao ~/.bashrc ou ~/.zshrc +export ANDROID_HOME=$HOME/Library/Android/sdk # macOS +export ANDROID_HOME=$HOME/Android/Sdk # Linux +export PATH=$PATH:$ANDROID_HOME/emulator +export PATH=$PATH:$ANDROID_HOME/tools +export PATH=$PATH:$ANDROID_HOME/tools/bin +export PATH=$PATH:$ANDROID_HOME/platform-tools +``` + +### 4. Criar Emulador Android + +1. Abrir Android Studio +2. Tools → AVD Manager +3. Create Virtual Device +4. Recomendado: Pixel 4, API 30+ + +## Configuração iOS (macOS apenas) + +### 1. Xcode + +```bash +# Instalar Xcode da App Store +# Após instalação: +sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer +sudo xcodebuild -runFirstLaunch + +# Instalar ferramentas adicionais +xcode-select --install +``` + +### 2. CocoaPods + +```bash +# Instalar CocoaPods +sudo gem install cocoapods + +# Ou via Homebrew +brew install cocoapods + +# Instalar pods do projeto +cd ios +pod install +cd .. +``` + +### 3. Configurar Simulador iOS + +```bash +# Listar simuladores disponíveis +xcrun simctl list devices + +# Abrir simulador +open -a Simulator + +# Ou via Flutter +fvm flutter devices +``` + +## Configuração do Projeto + +### 1. Clonar Repositório + +```bash +git clone https://github.com/seu-usuario/penhas.git +cd penhas/penhas-app +``` + +### 2. Instalar Dependências + +```bash +# Instalar versão do Flutter +fvm install + +# Baixar dependências do projeto +fvm flutter pub get + +# Gerar código (freezed, json_serializable, etc) +fvm flutter pub run build_runner build --delete-conflicting-outputs +``` + +### 3. Configurar Firebase + +#### Instalar Firebase CLI + +```bash +# macOS +brew install firebase-cli + +# Linux/Windows +npm install -g firebase-tools + +# Login +firebase login +``` + +#### Instalar FlutterFire CLI + +```bash +# Instalar globalmente +dart pub global activate flutterfire_cli + +# Adicionar ao PATH se necessário +export PATH="$PATH:$HOME/.pub-cache/bin" +``` + +#### Configurar Firebase no Projeto + +```bash +# Configurar para desenvolvimento +flutterfire configure -y \ + --project=penhas-v3-dev \ + --out=lib/firebase_options.dart \ + --platforms=android,ios \ + --android-package-name=dev.penhas.com.br \ + --ios-bundle-id=dev.penhas.alphacode.com.br + +# Para produção, usar projeto diferente +flutterfire configure -y \ + --project=penhas-v3 \ + --out=lib/firebase_options_prod.dart \ + --platforms=android,ios \ + --android-package-name=penhas.com.br \ + --ios-bundle-id=br.com.penhas +``` + +### 4. Configurar Google Maps + +#### Obter Chave API + +1. Acessar [Google Cloud Console](https://console.cloud.google.com) +2. Criar ou selecionar projeto +3. Ativar Maps SDK for Android e iOS +4. Criar credenciais (API Key) +5. Restringir chave por aplicativo + +#### Configurar no Projeto + +##### Android +```bash +# Criar arquivo android/secrets.properties +echo "GEO_API_KEY=SUA_CHAVE_AQUI" > android/secrets.properties +``` + +##### iOS +```bash +# Criar arquivo ios/Flutter/Secrets.xcconfig +echo "GEO_API_KEY = SUA_CHAVE_AQUI" > ios/Flutter/Secrets.xcconfig +``` + +### 5. Configurar VS Code + +Criar `.vscode/settings.json`: + +```json +{ + "dart.flutterSdkPath": ".fvm/flutter_sdk", + "search.exclude": { + "**/.fvm": true, + "**/*.freezed.dart": true, + "**/*.g.dart": true + }, + "files.watcherExclude": { + "**/.fvm": true + }, + "[dart]": { + "editor.formatOnSave": true, + "editor.formatOnType": true, + "editor.rulers": [80], + "editor.selectionHighlight": false, + "editor.suggest.snippetsPreventQuickSuggestions": false, + "editor.suggestSelection": "first", + "editor.tabCompletion": "onlySnippets", + "editor.wordBasedSuggestions": false + } +} +``` + +Criar `.vscode/launch.json`: + +```json +{ + "version": "0.0.1", + "configurations": [ + { + "name": "Dev", + "request": "launch", + "type": "dart", + "program": "./lib/main.dart", + "args": [ + "--dart-define=PENHAS_BASE_URL=https://dev-api.penhas.com.br" + ] + }, + { + "name": "Prod", + "request": "launch", + "type": "dart", + "program": "./lib/main.dart", + "args": [ + "--dart-define=PENHAS_BASE_URL=https://api.penhas.com.br" + ] + }, + { + "name": "Local", + "request": "launch", + "type": "dart", + "program": "./lib/main.dart", + "args": [ + "--dart-define=PENHAS_BASE_URL=http://localhost:3000" + ] + } + ] +} +``` + +## Executar o Projeto + +### Via Linha de Comando + +```bash +# Listar dispositivos disponíveis +fvm flutter devices + +# Executar em modo debug +fvm flutter run --dart-define=PENHAS_BASE_URL=https://dev-api.penhas.com.br + +# Executar em dispositivo específico +fvm flutter run -d "iPhone 14" --dart-define=PENHAS_BASE_URL=https://dev-api.penhas.com.br + +# Hot reload +r # No terminal onde o app está rodando + +# Hot restart +R # No terminal onde o app está rodando +``` + +### Via VS Code + +1. Abrir projeto no VS Code +2. Selecionar dispositivo na barra inferior +3. F5 ou Debug → Start Debugging +4. Escolher configuração (Dev, Prod, Local) + +## Ferramentas de Desenvolvimento + +### 1. Flutter Inspector + +- Disponível no VS Code e Android Studio +- Visualizar árvore de widgets +- Debugar layouts +- Performance profiling + +### 2. DevTools + +```bash +# Abrir DevTools +fvm flutter pub global activate devtools +fvm flutter pub global run devtools + +# Ou durante execução +# Pressionar 'd' no terminal do flutter run +``` + +### 3. Code Generation + +```bash +# Watch mode para desenvolvimento +fvm flutter pub run build_runner watch --delete-conflicting-outputs + +# Build único +fvm flutter pub run build_runner build --delete-conflicting-outputs + +# Limpar cache do build_runner +fvm flutter pub run build_runner clean +``` + +## Problemas Comuns + +### 1. Erro de Versão Flutter + +```bash +# Limpar FVM cache +fvm remove [version] +fvm install + +# Recriar links +fvm use --force +``` + +### 2. Erro CocoaPods (iOS) + +```bash +cd ios +pod deintegrate +pod cache clean --all +pod install +cd .. +``` + +### 3. Gradle Issues (Android) + +```bash +cd android +./gradlew clean +./gradlew build +cd .. +``` + +### 4. Build Runner Travado + +```bash +# Matar processos +pkill -f dart + +# Limpar e reconstruir +fvm flutter clean +fvm flutter pub get +fvm flutter pub run build_runner clean +fvm flutter pub run build_runner build --delete-conflicting-outputs +``` + +## Scripts Úteis + +Criar `scripts/setup.sh`: + +```bash +#!/bin/bash + +echo "🚀 Configurando projeto PenhaS..." + +# Instalar Flutter via FVM +echo "📦 Instalando Flutter..." +fvm install +fvm use + +# Instalar dependências +echo "📚 Instalando dependências..." +fvm flutter pub get + +# Gerar código +echo "🔨 Gerando código..." +fvm flutter pub run build_runner build --delete-conflicting-outputs + +# iOS setup +if [[ "$OSTYPE" == "darwin"* ]]; then + echo "🍎 Configurando iOS..." + cd ios && pod install && cd .. +fi + +# Verificar setup +echo "🔍 Verificando configuração..." +fvm flutter doctor + +echo "✅ Setup completo!" +``` + +Tornar executável: +```bash +chmod +x scripts/setup.sh +./scripts/setup.sh +``` + +## Próximos Passos + +- Revise [Build e Deploy](10-build-deploy.md) para distribuição +- Consulte [Testes](08-testes.md) para executar testes +- Veja [Resolução de Problemas](12-troubleshooting.md) para mais soluções \ No newline at end of file diff --git a/docs/10-build-deploy.md b/docs/10-build-deploy.md new file mode 100644 index 000000000..723c5bc55 --- /dev/null +++ b/docs/10-build-deploy.md @@ -0,0 +1,507 @@ +# Build e Deploy + +## Visão Geral + +Este documento detalha o processo de build e deploy do aplicativo PenhaS para as plataformas Android e iOS. O projeto utiliza Fastlane para automatizar grande parte do processo de distribuição. + +## Configuração Inicial + +### Fastlane + +O projeto já possui configuração do Fastlane. Para instalar as dependências: + +```bash +# Instalar Fastlane +gem install fastlane + +# Ou via Bundler (recomendado) +bundle install + +# Verificar instalação +fastlane --version +``` + +### Estrutura Fastlane + +``` +penhas-app/ +├── fastlane/ +│ ├── Fastfile # Configurações globais +│ └── Pluginfile # Plugins utilizados +├── android/ +│ └── fastlane/ +│ ├── Appfile # Configurações Android +│ └── Fastfile # Lanes Android +└── ios/ + └── fastlane/ + ├── Appfile # Configurações iOS + ├── Deliverfile # Metadados App Store + └── Fastfile # Lanes iOS +``` + +## Build Android + +### 1. Configuração de Assinatura + +#### Criar Keystore + +```bash +keytool -genkey -v -keystore penhas-release.keystore \ + -alias penhas -keyalg RSA -keysize 2048 -validity 10000 +``` + +#### Configurar `android/key.properties` + +```properties +storePassword=sua_senha_aqui +keyPassword=sua_senha_aqui +keyAlias=penhas +storeFile=../penhas-release.keystore +``` + +#### Configurar `android/app/build.gradle` + +```gradle +def keystoreProperties = new Properties() +def keystorePropertiesFile = rootProject.file('key.properties') +if (keystorePropertiesFile.exists()) { + keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) +} + +android { + signingConfigs { + release { + keyAlias keystoreProperties['keyAlias'] + keyPassword keystoreProperties['keyPassword'] + storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null + storePassword keystoreProperties['storePassword'] + } + } + + buildTypes { + release { + signingConfig signingConfigs.release + } + } +} +``` + +### 2. Build Manual + +#### APK (Android Package) + +```bash +# Build APK para testes +fvm flutter build apk --release \ + --dart-define=PENHAS_BASE_URL=https://api.penhas.com.br + +# APK split por arquitetura (menor tamanho) +fvm flutter build apk --split-per-abi --release \ + --dart-define=PENHAS_BASE_URL=https://api.penhas.com.br +``` + +#### AAB (Android App Bundle) + +```bash +# Build AAB para Play Store +fvm flutter build appbundle --release \ + --dart-define=PENHAS_BASE_URL=https://api.penhas.com.br +``` + +### 3. Build via Fastlane + +```bash +cd android + +# Build APK +fastlane build_apk + +# Build Bundle +fastlane build_bundle + +# Distribuir via Firebase +fastlane firebase_distribute + +# Deploy para Play Store +fastlane release_distribute +``` + +### 4. Configuração Play Store + +#### Service Account + +1. Acessar [Google Play Console](https://play.google.com/console) +2. Configurações → Acesso à API → Criar novo projeto +3. Criar conta de serviço +4. Baixar JSON de credenciais +5. Adicionar ao projeto como `android/play-store-credentials.json` + +#### Fastlane Appfile + +```ruby +# android/fastlane/Appfile +json_key_file("../play-store-credentials.json") +package_name("penhas.com.br") +``` + +## Build iOS + +### 1. Configuração de Certificados + +#### Criar Certificados e Provisioning Profiles + +```bash +cd ios + +# Login na Apple +fastlane fastlane-credentials add \ + --username seu_apple_id@example.com + +# Criar certificados +fastlane match development +fastlane match adhoc +fastlane match appstore +``` + +#### Configurar Match + +Criar `ios/fastlane/Matchfile`: + +```ruby +git_url("https://github.com/seu-usuario/certificates") +storage_mode("git") +type("development") +app_identifier(["br.com.penhas", "dev.penhas.alphacode.com.br"]) +username("seu_apple_id@example.com") +``` + +### 2. Build Manual + +```bash +# Build sem assinatura +fvm flutter build ios --release --no-codesign \ + --dart-define=PENHAS_BASE_URL=https://api.penhas.com.br + +# Abrir no Xcode para assinar +open ios/Runner.xcworkspace +``` + +### 3. Build via Fastlane + +```bash +cd ios + +# Gerar IPA para testes +fastlane build + +# Distribuir via Firebase +fastlane firebase_distribute + +# Deploy para TestFlight +fastlane release_distribute +``` + +### 4. Configuração App Store Connect + +#### API Key + +1. Acessar [App Store Connect](https://appstoreconnect.apple.com) +2. Usuários e Acesso → Chaves → Gerar chave API +3. Baixar arquivo .p8 +4. Criar `ios/AuthKey.json`: + +```json +{ + "key_id": "XXXXXXXXXX", + "issuer_id": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX", + "key": "-----BEGIN PRIVATE KEY-----\nMIGTAgEAMBMGByqGSM49...\n-----END PRIVATE KEY-----", + "duration": 1200, + "in_house": false +} +``` + +## Firebase App Distribution + +### Configuração + +1. Instalar plugin: +```bash +fastlane add_plugin firebase_app_distribution +``` + +2. Configurar Firebase CLI: +```bash +firebase login +firebase projects:list +``` + +3. Adicionar testers no [Firebase Console](https://console.firebase.google.com) + +### Distribuição Android + +```bash +cd android + +# Distribuir para grupo de testers +fastlane firebase_distribute + +# Distribuir para tester específico +fastlane firebase_distribute tester_email:teste@example.com +``` + +### Distribuição iOS + +```bash +cd ios + +# Sincronizar dispositivos +fastlane sync_device_info + +# Distribuir +fastlane firebase_distribute +``` + +## CI/CD com GitHub Actions + +### Workflow Android + +```yaml +# .github/workflows/android-deploy.yml +name: Android Deploy + +on: + push: + tags: + - 'v*' + +jobs: + deploy: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Setup Java + uses: actions/setup-java@v3 + with: + java-version: '11' + + - name: Setup Flutter + uses: subosito/flutter-action@v2 + with: + flutter-version: '3.0.5' + + - name: Decode Keystore + run: | + echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > android/app/penhas-release.keystore + echo "${{ secrets.KEY_PROPERTIES }}" > android/key.properties + + - name: Build AAB + run: | + flutter pub get + flutter build appbundle --release \ + --dart-define=PENHAS_BASE_URL=${{ secrets.API_URL }} + + - name: Upload to Play Store + uses: r0adkll/upload-google-play@v1 + with: + serviceAccountJsonPlainText: ${{ secrets.PLAY_STORE_JSON }} + packageName: penhas.com.br + releaseFiles: build/app/outputs/bundle/release/app-release.aab + track: internal +``` + +### Workflow iOS + +```yaml +# .github/workflows/ios-deploy.yml +name: iOS Deploy + +on: + push: + tags: + - 'v*' + +jobs: + deploy: + runs-on: macos-latest + + steps: + - uses: actions/checkout@v3 + + - name: Setup Flutter + uses: subosito/flutter-action@v2 + with: + flutter-version: '3.0.5' + + - name: Setup Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.0' + bundler-cache: true + + - name: Decode Certificates + run: | + echo "${{ secrets.MATCH_PASSWORD }}" > /tmp/match_password + echo "${{ secrets.APPLE_API_KEY }}" | base64 -d > ios/AuthKey.json + + - name: Install Dependencies + run: | + flutter pub get + cd ios && pod install + + - name: Build and Deploy + run: | + cd ios + bundle exec fastlane release_distribute + env: + MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} + FASTLANE_PASSWORD: ${{ secrets.FASTLANE_PASSWORD }} +``` + +## Versionamento + +### Semantic Versioning + +O projeto segue [Semantic Versioning](https://semver.org/): +- **MAJOR.MINOR.PATCH+BUILD** +- Exemplo: `3.7.2+69` + +### Atualizar Versão + +```yaml +# pubspec.yaml +version: 3.7.2+69 # version+buildNumber +``` + +### Script de Versionamento + +```bash +#!/bin/bash +# scripts/bump_version.sh + +current_version=$(grep "version:" pubspec.yaml | sed 's/version: //') +echo "Current version: $current_version" + +read -p "New version (x.y.z): " new_version +read -p "Build number: " build_number + +sed -i '' "s/version: .*/version: $new_version+$build_number/" pubspec.yaml + +echo "Updated to: $new_version+$build_number" +``` + +## Configurações de Release + +### Android + +#### Proguard/R8 + +```proguard +# android/app/proguard-rules.pro +-keep class com.penhas.** { *; } +-keep class io.flutter.** { *; } +-keep class com.google.firebase.** { *; } +``` + +#### Build Variants + +```gradle +// android/app/build.gradle +android { + flavorDimensions "environment" + + productFlavors { + dev { + dimension "environment" + applicationIdSuffix ".dev" + versionNameSuffix "-dev" + } + + prod { + dimension "environment" + } + } +} +``` + +### iOS + +#### Build Configurations + +No Xcode: +1. Runner → Project → Info +2. Adicionar configurações: + - Debug-Dev + - Debug-Prod + - Release-Dev + - Release-Prod + +#### Schemes + +Criar schemes para cada ambiente: +- Runner-Dev +- Runner-Prod + +## Monitoramento Pós-Deploy + +### Crashlytics + +```dart +// Verificar crashes em produção +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + + await Firebase.initializeApp(); + + // Capturar erros Flutter + FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterError; + + // Capturar erros Dart + runZonedGuarded(() { + runApp(MyApp()); + }, FirebaseCrashlytics.instance.recordError); +} +``` + +### Analytics + +Monitorar: +- Taxa de instalação +- Taxa de retenção +- Crashes +- Performance + +## Rollback + +### Android (Play Store) + +1. Console → Release → Production +2. Create new release +3. Add APK/AAB from previous release +4. Roll out + +### iOS (App Store) + +1. App Store Connect → My Apps +2. Remove current version from sale +3. Resubmit previous version + +## Checklist de Release + +- [ ] Atualizar versão no `pubspec.yaml` +- [ ] Testar em dispositivos reais +- [ ] Verificar feature toggles +- [ ] Atualizar screenshots (se necessário) +- [ ] Preparar release notes +- [ ] Tag no Git +- [ ] Build de produção +- [ ] Upload para stores +- [ ] Monitorar Crashlytics +- [ ] Comunicar equipe + +## Próximos Passos + +- Revise [Feature Toggles](11-feature-toggles.md) para configurações remotas +- Consulte [Troubleshooting](12-troubleshooting.md) para problemas comuns +- Veja [Configuração do Ambiente](09-configuracao-desenvolvimento.md) para setup inicial \ No newline at end of file diff --git a/docs/11-feature-toggles.md b/docs/11-feature-toggles.md new file mode 100644 index 000000000..1c3f47bc3 --- /dev/null +++ b/docs/11-feature-toggles.md @@ -0,0 +1,495 @@ +# Feature Toggles + +## Visão Geral + +Feature toggles (ou feature flags) são uma técnica poderosa que permite ativar ou desativar funcionalidades do aplicativo remotamente, sem necessidade de nova publicação nas lojas. O PenhaS utiliza o Firebase Remote Config para gerenciar essas configurações. + +## Por que usar Feature Toggles? + +1. **Lançamento Gradual**: Liberar funcionalidades para grupos específicos +2. **Testes A/B**: Testar diferentes versões de uma funcionalidade +3. **Rollback Rápido**: Desativar funcionalidades problemáticas instantaneamente +4. **Desenvolvimento Contínuo**: Mesclar código incompleto sem afetar produção +5. **Personalização**: Adaptar a experiência por região, dispositivo ou usuário + +## Configuração do Firebase Remote Config + +### 1. Setup Inicial + +```dart +// lib/app/core/remoteconfig/remote_config.dart + +class RemoteConfigService implements IRemoteConfigService { + const RemoteConfigService(); + + static final _remoteConfig = FirebaseRemoteConfig.instance; + + Future initialize() async { + try { + // Configurar intervalo mínimo de fetch + await _remoteConfig.setConfigSettings( + RemoteConfigSettings( + fetchTimeout: const Duration(minutes: 1), + minimumFetchInterval: const Duration(hours: 1), // Produção: 1 hora + ), + ); + + // Definir valores padrão + await _remoteConfig.setDefaults(_defaultValues); + + // Buscar e ativar valores + await _remoteConfig.fetchAndActivate(); + + // Escutar mudanças em tempo real (opcional) + _remoteConfig.onConfigUpdated.listen((event) async { + await _remoteConfig.activate(); + _notifyListeners(); + }); + + } catch (e) { + logger.error('Erro ao inicializar Remote Config', e); + // Usar valores padrão em caso de erro + } + } +} +``` + +### 2. Valores Padrão + +```dart +// Valores padrão caso o Remote Config falhe +static const Map _defaultValues = { + 'feature_login_offline': false, + 'feature_chat_private': false, + 'feature_escape_manual': true, + 'feature_audio_recording_max_duration': 300, // 5 minutos + 'config_quiz_animation_duration': 400, + 'config_auto_logout_minutes': 5, + 'config_max_guardians': 5, + 'maintenance_mode': false, + 'maintenance_message': '', + 'minimum_app_version': '3.0.0', +}; +``` + +## Implementação de Feature Toggles + +### 1. Criar Feature Toggle + +```dart +// lib/app/features/authentication/domain/usecases/login_offline_toggle.dart + +class LoginOfflineToggleFeature { + LoginOfflineToggleFeature({ + required IRemoteConfigService remoteConfig, + }) : _remoteConfig = remoteConfig; + + final IRemoteConfigService _remoteConfig; + + static const String _featureKey = 'feature_login_offline'; + + Future get isEnabled async { + try { + return _remoteConfig.getBool(_featureKey); + } catch (e) { + // Retornar valor padrão em caso de erro + return _defaultValues[_featureKey] as bool; + } + } + + // Para desenvolvimento/testes + @visibleForTesting + Future setEnabled(bool value) async { + if (kDebugMode) { + await _remoteConfig.setDefaults({_featureKey: value}); + } + } +} +``` + +### 2. Usar no Código + +```dart +// Em um Use Case +class AuthenticationUseCase { + final LoginOfflineToggleFeature _loginOfflineToggle; + + Future> authenticate() async { + // Verificar se login offline está habilitado + if (await _loginOfflineToggle.isEnabled) { + return _attemptOfflineLogin(); + } + + return _attemptOnlineLogin(); + } +} + +// Em uma Store +class LoginStore extends _LoginStoreBase with _$LoginStore { + @observable + bool showOfflineOption = false; + + @action + Future checkFeatures() async { + showOfflineOption = await _loginOfflineToggle.isEnabled; + } +} + +// Em um Widget +class LoginPage extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Observer( + builder: (_) { + return Column( + children: [ + // Campos de login padrão + EmailField(), + PasswordField(), + + // Mostrar opção offline apenas se habilitada + if (store.showOfflineOption) + OfflineLoginButton(), + + LoginButton(), + ], + ); + }, + ); + } +} +``` + +## Toggles Disponíveis no PenhaS + +### Features + +| Toggle | Tipo | Padrão | Descrição | +|--------|------|---------|-----------| +| `feature_login_offline` | `bool` | `false` | Habilita login offline no modo camuflado | +| `feature_chat_private` | `bool` | `false` | Habilita chat privado entre usuárias | +| `feature_escape_manual` | `bool` | `true` | Mostra/oculta manual de fuga | +| `feature_support_center_map` | `bool` | `true` | Habilita mapa de pontos de apoio | +| `feature_audio_panic_button` | `bool` | `true` | Habilita gravação no botão de pânico | +| `feature_quiz_module` | `bool` | `true` | Habilita módulo de questionários | +| `feature_feed_anonymous` | `bool` | `true` | Permite posts anônimos no feed | + +### Configurações + +| Toggle | Tipo | Padrão | Descrição | +|--------|------|---------|-----------| +| `config_quiz_animation_duration` | `int` | `400` | Duração das animações do quiz (ms) | +| `config_auto_logout_minutes` | `int` | `5` | Tempo para logout automático | +| `config_max_guardians` | `int` | `5` | Número máximo de guardiões | +| `config_audio_max_duration` | `int` | `300` | Duração máxima de gravação (segundos) | +| `config_feed_page_size` | `int` | `20` | Itens por página no feed | +| `config_chat_reconnect_delay` | `int` | `5000` | Delay para reconexão do chat (ms) | + +### Manutenção + +| Toggle | Tipo | Padrão | Descrição | +|--------|------|---------|-----------| +| `maintenance_mode` | `bool` | `false` | Ativa modo de manutenção | +| `maintenance_message` | `String` | `""` | Mensagem durante manutenção | +| `minimum_app_version` | `String` | `"3.0.0"` | Versão mínima do app | +| `force_update_message` | `String` | `""` | Mensagem para atualização forçada | + +## Gerenciamento no Console + +### 1. Acessar Remote Config + +1. [Firebase Console](https://console.firebase.google.com) +2. Selecionar projeto PenhaS +3. Remote Config no menu lateral + +### 2. Criar/Editar Parâmetros + +```json +// Exemplo de parâmetro com condições +{ + "parameter_key": "feature_chat_private", + "default_value": false, + "conditional_values": [ + { + "condition_name": "Beta Testers", + "value": true + }, + { + "condition_name": "iOS Users", + "value": true + } + ] +} +``` + +### 3. Condições Disponíveis + +- **Plataforma**: iOS, Android +- **Versão do App**: Específica ou range +- **País/Região**: Por localização +- **Idioma**: Por configuração do dispositivo +- **Audiência**: Grupos do Analytics +- **Porcentagem**: Para rollout gradual + +## Estratégias de Rollout + +### 1. Rollout Gradual + +```javascript +// No Firebase Console +{ + "conditions": [ + { + "name": "10% dos usuários", + "expression": "percent <= 10", + "value": true + }, + { + "name": "50% dos usuários", + "expression": "percent <= 50", + "value": true + }, + { + "name": "Todos", + "expression": "true", + "value": true + } + ] +} +``` + +### 2. Beta Testing + +```dart +class FeatureManager { + // Verificar se usuário é beta tester + Future isBetaTester() async { + final userId = await _userStore.userId; + final betaTesters = _remoteConfig.getStringList('beta_testers'); + + return betaTesters.contains(userId); + } + + // Habilitar features beta + Future> getBetaFeatures() async { + if (!await isBetaTester()) { + return {}; + } + + return { + 'chat_private': true, + 'advanced_analytics': true, + 'experimental_ui': true, + }; + } +} +``` + +### 3. A/B Testing + +```dart +// Configurar experimento +class ExperimentManager { + Future getExperimentVariant(String experimentName) async { + // Remote Config retorna variante + return _remoteConfig.getString('experiment_$experimentName'); + } + + // Exemplo: Testar diferentes textos de CTA + Future getLoginButtonText() async { + final variant = await getExperimentVariant('login_cta'); + + switch (variant) { + case 'A': + return 'Entrar'; + case 'B': + return 'Acessar minha conta'; + case 'C': + return 'Fazer login'; + default: + return 'Entrar'; + } + } +} +``` + +## Monitoramento e Analytics + +### 1. Tracking de Features + +```dart +class FeatureAnalytics { + static void trackFeatureUsage(String featureName, bool isEnabled) { + analytics.logEvent( + 'feature_toggle_checked', + parameters: { + 'feature_name': featureName, + 'is_enabled': isEnabled, + 'timestamp': DateTime.now().toIso8601String(), + }, + ); + } + + static void trackFeatureInteraction(String featureName, String action) { + analytics.logEvent( + 'feature_interaction', + parameters: { + 'feature_name': featureName, + 'action': action, + 'user_segment': _getUserSegment(), + }, + ); + } +} +``` + +### 2. Dashboard de Monitoramento + +Métricas importantes: +- Taxa de adoção por feature +- Erros relacionados a features +- Performance por variante +- Feedback dos usuários + +## Boas Práticas + +### 1. Nomenclatura + +```dart +// Prefixos recomendados +feature_* // Para features completas +config_* // Para configurações +experiment_* // Para testes A/B +debug_* // Para desenvolvimento +``` + +### 2. Documentação + +```dart +/// Feature Toggle para chat privado entre usuárias +/// +/// Quando habilitado: +/// - Adiciona aba "Pessoas" no chat +/// - Permite buscar e conversar com outras usuárias +/// - Habilita sistema de bloqueio/denúncia +/// +/// Dependências: +/// - Requer backend v2.5+ +/// - Incompatível com modo anônimo +class ChatPrivateToggleFeature { + // ... +} +``` + +### 3. Fallbacks + +```dart +class SafeFeatureToggle { + Future getValue( + String key, + T defaultValue, { + Duration? timeout, + }) async { + try { + // Tentar buscar com timeout + return await _remoteConfig + .getValue(key) + .timeout(timeout ?? const Duration(seconds: 3)); + } catch (e) { + // Log erro mas não quebrar app + logger.warning('Feature toggle fallback: $key', e); + return defaultValue; + } + } +} +``` + +### 4. Cache Local + +```dart +class CachedRemoteConfig { + final Map _cache = {}; + DateTime? _lastFetch; + + Future getCachedValue(String key, T defaultValue) async { + // Usar cache se fetch recente + if (_lastFetch != null && + DateTime.now().difference(_lastFetch!) < Duration(minutes: 5)) { + return _cache[key] as T? ?? defaultValue; + } + + // Buscar novo valor + try { + await _remoteConfig.fetchAndActivate(); + _lastFetch = DateTime.now(); + _updateCache(); + } catch (e) { + // Usar cache antigo se disponível + } + + return _cache[key] as T? ?? defaultValue; + } +} +``` + +## Troubleshooting + +### Problema: Valores não atualizam + +```dart +// Forçar fetch ignorando cache +await FirebaseRemoteConfig.instance.setConfigSettings( + RemoteConfigSettings( + fetchTimeout: const Duration(minutes: 1), + minimumFetchInterval: Duration.zero, // Apenas para debug! + ), +); +await FirebaseRemoteConfig.instance.fetchAndActivate(); +``` + +### Problema: App quebra sem internet + +```dart +// Sempre ter fallback local +class ResilientFeatureToggle { + Future isEnabled(String key) async { + try { + // Tentar Remote Config + return _remoteConfig.getBool(key); + } catch (e) { + // Fallback para SharedPreferences + final prefs = await SharedPreferences.getInstance(); + return prefs.getBool('cached_$key') ?? _defaultValues[key]; + } + } +} +``` + +## Migração de Features + +### Deprecação Gradual + +```dart +class DeprecatedFeature { + Future shouldShowDeprecationWarning() async { + final deprecationLevel = _remoteConfig.getString('feature_x_deprecation'); + + switch (deprecationLevel) { + case 'none': + return false; + case 'warning': + return true; + case 'disabled': + throw FeatureDisabledException('Feature X foi descontinuada'); + default: + return false; + } + } +} +``` + +## Próximos Passos + +- Consulte [Resolução de Problemas](12-troubleshooting.md) para issues comuns +- Veja [Build e Deploy](10-build-deploy.md) para publicar com toggles +- Revise [Testes](08-testes.md) para testar feature toggles \ No newline at end of file diff --git a/docs/12-troubleshooting.md b/docs/12-troubleshooting.md new file mode 100644 index 000000000..83a613bbb --- /dev/null +++ b/docs/12-troubleshooting.md @@ -0,0 +1,553 @@ +# Resolução de Problemas + +## Visão Geral + +Este guia aborda os problemas mais comuns encontrados durante o desenvolvimento, build e execução do aplicativo PenhaS, fornecendo soluções práticas e dicas de debugging. + +## Problemas de Configuração + +### Flutter/FVM + +#### Erro: "Flutter SDK not found" + +```bash +# Verificar instalação FVM +fvm list + +# Reinstalar versão do Flutter +fvm remove 3.0.5 +fvm install 3.0.5 +fvm use 3.0.5 --force + +# Verificar PATH +echo $PATH | grep fvm +``` + +#### Erro: "Dart SDK version mismatch" + +```bash +# Limpar cache do Dart +fvm flutter pub cache clean + +# Recriar pubspec.lock +rm pubspec.lock +fvm flutter pub get +``` + +### Dependências + +#### Erro: "pub get failed" + +```bash +# Limpar cache completo +fvm flutter clean +rm -rf .dart_tool +rm pubspec.lock + +# Reinstalar dependências +fvm flutter pub get + +# Se persistir, verificar proxy +export https_proxy=http://seu-proxy:porta +export http_proxy=http://seu-proxy:porta +``` + +#### Erro: "version solving failed" + +```yaml +# Verificar conflitos no pubspec.yaml +# Usar dependency_overrides temporariamente +dependency_overrides: + http: ^0.13.5 + collection: ^1.17.0 +``` + +## Problemas de Build + +### Android + +#### Erro: "Gradle build failed" + +```bash +# Limpar build Android +cd android +./gradlew clean +cd .. + +# Atualizar Gradle wrapper +cd android +./gradlew wrapper --gradle-version=7.5 +cd .. + +# Invalidar caches +rm -rf ~/.gradle/caches +``` + +#### Erro: "SDK location not found" + +```bash +# Criar local.properties +cd android +echo "sdk.dir=$HOME/Android/Sdk" > local.properties # Linux +echo "sdk.dir=$HOME/Library/Android/sdk" > local.properties # macOS +cd .. +``` + +#### Erro: "Duplicate class kotlin" + +```gradle +// android/build.gradle +buildscript { + ext.kotlin_version = '1.7.10' // Atualizar versão +} + +// android/app/build.gradle +configurations.all { + resolutionStrategy { + force 'org.jetbrains.kotlin:kotlin-stdlib:1.7.10' + } +} +``` + +#### Erro: Multidex + +```gradle +// android/app/build.gradle +android { + defaultConfig { + multiDexEnabled true + } +} + +dependencies { + implementation 'androidx.multidex:multidex:2.0.1' +} +``` + +### iOS + +#### Erro: "Pod install failed" + +```bash +# Limpar CocoaPods +cd ios +rm -rf Pods +rm Podfile.lock +pod cache clean --all + +# Reinstalar +pod install --repo-update +cd .. +``` + +#### Erro: "Module not found" + +```bash +# Limpar build iOS +cd ios +rm -rf ~/Library/Developer/Xcode/DerivedData +rm -rf build +cd .. + +# Recriar projeto +fvm flutter create . --platforms=ios +``` + +#### Erro: "Code signing" + +```bash +# Verificar certificados +security find-identity -p codesigning + +# Limpar provisioning profiles +rm -rf ~/Library/MobileDevice/Provisioning\ Profiles/* + +# Baixar novamente via Xcode +open ios/Runner.xcworkspace +# Xcode → Preferences → Accounts → Download Manual Profiles +``` + +## Problemas de Execução + +### Hot Reload/Restart + +#### Hot Reload não funciona + +```dart +// Verificar se widgets são const +class MyWidget extends StatelessWidget { + const MyWidget({Key? key}) : super(key: key); // Adicionar const + + @override + Widget build(BuildContext context) { + // Evitar closures em build + return const Text('Hello'); // Usar const quando possível + } +} +``` + +#### Estado não atualiza + +```dart +// Para MobX - verificar Observer +Widget build(BuildContext context) { + return Observer( // Necessário para reatividade + builder: (_) => Text(store.value), + ); +} + +// Verificar se action foi chamada +@action +void updateValue(String newValue) { + value = newValue; // Deve ser dentro de @action +} +``` + +### Performance + +#### App lento/travando + +```dart +// 1. Verificar logs +fvm flutter logs + +// 2. Usar DevTools +fvm flutter pub global activate devtools +fvm flutter pub global run devtools + +// 3. Profile mode +fvm flutter run --profile +``` + +#### Memória/Vazamentos + +```dart +// Verificar dispose +class _MyPageState extends State { + late final StreamSubscription _subscription; + late final TextEditingController _controller; + + @override + void initState() { + super.initState(); + _controller = TextEditingController(); + _subscription = stream.listen((_) {}); + } + + @override + void dispose() { + _subscription.cancel(); // Importante! + _controller.dispose(); + super.dispose(); + } +} +``` + +## Problemas de API/Rede + +### Conexão recusada + +```dart +// Verificar URL base +print('API URL: ${appConfiguration.penhasServer}'); + +// Para localhost no emulador +// Android: usar 10.0.2.2 +// iOS: usar 127.0.0.1 ou nome da máquina + +// Verificar CORS (desenvolvimento) +// Backend deve permitir origem do Flutter +``` + +### SSL/Certificate errors + +```dart +// APENAS para desenvolvimento! +class MyHttpOverrides extends HttpOverrides { + @override + HttpClient createHttpClient(SecurityContext? context) { + return super.createHttpClient(context) + ..badCertificateCallback = (cert, host, port) => true; + } +} + +void main() { + if (kDebugMode) { + HttpOverrides.global = MyHttpOverrides(); + } + runApp(MyApp()); +} +``` + +### Timeout + +```dart +// Aumentar timeout +final response = await http.get(uri).timeout( + const Duration(seconds: 60), + onTimeout: () { + throw TimeoutException('Request timeout'); + }, +); + +// Implementar retry +Future retryRequest(Future Function() request) async { + int attempts = 0; + + while (attempts < 3) { + try { + return await request(); + } catch (e) { + attempts++; + if (attempts >= 3) rethrow; + await Future.delayed(Duration(seconds: attempts * 2)); + } + } + + throw Exception('Max retries exceeded'); +} +``` + +## Problemas de Estado/MobX + +### Reactions não disparam + +```dart +// Verificar se está dentro de runApp +void main() { + runApp(MyApp()); // MobX precisa do contexto Flutter +} + +// Verificar @observable +@observable // Necessário! +String value = ''; + +// Verificar Observer widget +Observer( + builder: (_) => Text(store.value), // Deve acessar observable +) +``` + +### "There are no observables detected" + +```dart +// 1. Verificar code generation +fvm flutter pub run build_runner build + +// 2. Verificar part directive +part 'my_store.g.dart'; // Necessário + +// 3. Verificar classe base +class MyStore = _MyStoreBase with _$MyStore; // Padrão correto +``` + +## Problemas de Testes + +### Testes falhando + +```dart +// 1. Verificar mocks +@GenerateMocks([Repository]) // Gerar mocks +void main() { + setUpAll(() { + registerFallbackValue(FakeUri()); // Para argumentos + }); +} + +// 2. Verificar async +test('async test', () async { // Note o async + await tester.pumpWidget(MyWidget()); + await tester.pump(); // Aguardar frame +}); + +// 3. Limpar entre testes +tearDown(() { + reset(mockRepository); // Limpar mocks +}); +``` + +### Golden tests diferentes + +```bash +# Atualizar goldens +fvm flutter test --update-goldens + +# Usar font loader +setUpAll(() async { + await loadAppFonts(); +}); + +# Definir tamanho fixo +await tester.pumpWidgetBuilder( + widget, + surfaceSize: const Size(400, 800), +); +``` + +## Problemas de Firebase + +### Configuração não encontrada + +```bash +# Reconfigurar Firebase +flutterfire configure + +# Verificar arquivos +# Android: android/app/google-services.json +# iOS: ios/Runner/GoogleService-Info.plist +``` + +### Crashlytics não reporta + +```dart +// Verificar inicialização +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + await Firebase.initializeApp(); // Necessário! + + FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterError; + + runApp(MyApp()); +} + +// Forçar crash de teste +FirebaseCrashlytics.instance.crash(); +``` + +## Problemas de Produção + +### App rejeitado na loja + +#### Google Play +- Verificar permissões não utilizadas +- Remover logs de debug +- Atualizar targetSdkVersion +- Adicionar política de privacidade + +#### App Store +- Verificar Info.plist permissions +- Adicionar descrições de uso +- Remover código não utilizado +- Testar em dispositivo real + +### Crash em produção + +```dart +// 1. Verificar Crashlytics +// 2. Adicionar mais logs +void logError(dynamic error, StackTrace? stack) { + if (kReleaseMode) { + FirebaseCrashlytics.instance.recordError(error, stack); + } else { + print('Error: $error'); + print('Stack: $stack'); + } +} + +// 3. Tratamento global +runZonedGuarded(() { + runApp(MyApp()); +}, (error, stack) { + logError(error, stack); +}); +``` + +## Debug Avançado + +### Breakpoints não funcionam + +```json +// .vscode/launch.json +{ + "configurations": [ + { + "name": "Debug", + "type": "dart", + "request": "launch", + "program": "lib/main.dart", + "args": ["--debug"] // Forçar modo debug + } + ] +} +``` + +### Logs detalhados + +```dart +// Logger configurável +class AppLogger { + static void log(String message, {Level level = Level.info}) { + if (kDebugMode || level == Level.error) { + final timestamp = DateTime.now().toIso8601String(); + print('[$timestamp] [${level.name}] $message'); + } + } +} + +// Interceptor HTTP para debug +class LoggingInterceptor extends InterceptorContract { + @override + Future interceptRequest({required BaseRequest request}) async { + print('→ ${request.method} ${request.url}'); + return request; + } +} +``` + +### Memory profiling + +```bash +# Dump de memória +fvm flutter debug dump-memory + +# Análise de performance +fvm flutter analyze --performance +``` + +## Comandos Úteis de Emergência + +```bash +# Reset completo do projeto +fvm flutter clean +rm -rf .dart_tool build .packages pubspec.lock +rm -rf ios/Pods ios/Podfile.lock +rm -rf ~/.pub-cache +fvm flutter pub get +cd ios && pod install && cd .. + +# Verificar saúde do projeto +fvm flutter doctor -v +fvm flutter analyze +fvm flutter test + +# Logs em tempo real +fvm flutter logs + +# Rebuild específico +fvm flutter pub run build_runner build --delete-conflicting-outputs +``` + +## Recursos Adicionais + +### Documentação +- [Flutter Docs](https://docs.flutter.dev) +- [Dart Docs](https://dart.dev/guides) +- [Stack Overflow](https://stackoverflow.com/questions/tagged/flutter) + +### Comunidade +- Flutter Brasil (Telegram) +- Flutter Community (Slack) +- r/FlutterDev (Reddit) + +### Ferramentas +- [Dart DevTools](https://docs.flutter.dev/development/tools/devtools) +- [Flutter Inspector](https://docs.flutter.dev/development/tools/inspector) +- [Firebase Console](https://console.firebase.google.com) + +## Suporte + +Para problemas específicos do PenhaS: +1. Verificar issues no GitHub +2. Consultar documentação interna +3. Contatar equipe de desenvolvimento +4. Abrir novo issue com detalhes e logs \ No newline at end of file diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000..29b78ab0b --- /dev/null +++ b/docs/README.md @@ -0,0 +1,54 @@ +# PenhaS App - Documentação Técnica + +Bem-vindo à documentação técnica do aplicativo móvel PenhaS. Esta documentação fornece informações abrangentes sobre a arquitetura do app, configuração de desenvolvimento e detalhes de implementação. + +## Índice + +1. [Visão Geral da Arquitetura](01-arquitetura.md) - Compreendendo o design arquitetural e padrões do app +2. [Pilha Tecnológica](02-pilha-tecnologica.md) - Frameworks, bibliotecas e ferramentas utilizadas +3. [Estrutura do Projeto](03-estrutura-projeto.md) - Organização de diretórios e estrutura de arquivos +4. [Funcionalidades Principais](04-funcionalidades.md) - Principais funcionalidades e módulos da aplicação +5. [Gerenciamento de Estado](05-gerenciamento-estado.md) - Padrões e implementação de gerenciamento de estado +6. [Autenticação e Segurança](06-autenticacao-seguranca.md) - Fluxo de autenticação e medidas de segurança +7. [Integração com API](07-integracao-api.md) - Comunicação com backend e sincronização de dados +8. [Estratégia de Testes](08-testes.md) - Abordagem de testes e melhores práticas +9. [Configuração do Ambiente](09-configuracao-desenvolvimento.md) - Configurando o ambiente de desenvolvimento +10. [Build e Deploy](10-build-deploy.md) - Construindo e implantando a aplicação +11. [Feature Toggles](11-feature-toggles.md) - Configuração remota e gerenciamento de funcionalidades +12. [Resolução de Problemas](12-troubleshooting.md) - Problemas comuns e soluções + +## Início Rápido + +Para desenvolvedores novos no projeto: + +1. Comece com a [Configuração do Ambiente](09-configuracao-desenvolvimento.md) para preparar seu ambiente +2. Revise a [Visão Geral da Arquitetura](01-arquitetura.md) para entender o design do app +3. Explore a [Estrutura do Projeto](03-estrutura-projeto.md) para navegar pelo código +4. Verifique as [Funcionalidades Principais](04-funcionalidades.md) para entender a funcionalidade do app + +## Sobre o PenhaS + +PenhaS é um aplicativo móvel projetado para apoiar mulheres em situações de violência doméstica. O app fornece várias funcionalidades incluindo: + +- Assistência de emergência e botão de pânico +- Canais de comunicação seguros +- Gerenciamento de rede de apoio +- Conteúdo educacional e recursos +- Localizador de centros de apoio baseado em localização +- Ferramentas de planejamento de fuga + +O app é construído com Flutter e segue os princípios da Clean Architecture, garantindo manutenibilidade, testabilidade e escalabilidade. + +## Contribuindo + +Ao contribuir para o projeto: + +1. Siga os padrões arquiteturais descritos nesta documentação +2. Escreva testes para novas funcionalidades +3. Atualize a documentação ao fazer mudanças significativas +4. Use o estilo de código e convenções estabelecidas + +## Recursos Adicionais + +- [README Principal](../README.md) - Informações básicas do projeto e configuração +- [Guia de Feature Toggles](feature_toggles.md) - Documentação existente sobre feature toggles \ No newline at end of file