Um app de anotações offline-first construído com Flutter como projeto de aprendizado prático.
Anotai é um exercício pedagógico: um app real sendo desenvolvido do zero com foco em arquitetura de software, boas práticas e versionamento com Git.
MVP completo — funcionalidades principais implementadas. Em fase de expansão e adaptação para Android.
✅ Criação e edição
- Criação de notas com título e conteúdo
- Edição em tempo real com salvamento automático (5 segundos de debounce)
- Salvamento imediato ao fechar a nota
✅ Organização
- Arquivamento de notas em pasta dedicada
- Desarquivamento com volta automática à aba original
- Marcação de notas como favoritas
- Categorias personalizadas com chips de filtro na tela inicial
- Criação, renomear e exclusão de categorias (hub de gerenciamento)
- Filtro AND multi-chip (ex: "Favoritas" + "Trabalho" ao mesmo tempo)
- Associação de categorias a notas pela EditorView
- Excluir categoria remove automaticamente o vínculo em todas as notas
✅ Lixeira
- Exclusão soft (move para lixeira, não deleta de verdade)
- Exclusão automática após 30 dias na lixeira (
limparExpiradasroda na inicialização) - Restauração mantém estado anterior (se era arquivada, volta ao arquivo)
- Exclusão permanente com confirmação
✅ Busca
- Filtragem em tempo real por título e conteúdo
- Isolada por aba — buscar em "Anotações" não afeta "Arquivo"
- Limpa automaticamente ao trocar de aba
✅ Interface
- Três abas: Anotações, Arquivo, Lixeira
- Menu de contexto dinâmico por aba
- Data de criação exibida em cada nota na lista
- Dialog de informações da nota (criação, última edição, palavras, caracteres)
- Indicador visual de favoritas (estrela)
- Design system centralizado (
AppTheme) com cores, tipografia, raios, sombras e espaçamentos
Planejado para o futuro (Fase 3+)
- Lock/unlock de notas (modo read-only)
- Histórico de versões (reverter para estado anterior)
- Suporte a imagens nas anotações
- Exportação PDF e backup manual
- Plataforma Android
- Sincronização entre dispositivos
- Flutter / Dart — framework UI multiplataforma
- Hive — banco de dados local NoSQL, otimizado para Flutter
- Provider — gerenciamento de estado (ChangeNotifier)
Padrão MVVM (Model-View-ViewModel) com Service Layer, camadas bem definidas:
lib/
├── models/ # Nota, Categoria: classes de dados com serialização
├── repositories/ # NotaRepository + CategoriaRepository (interfaces) + implementações Hive
├── services/ # Regras de negócio: TrashService, ArchiveService, NoteEditorService, CategoriaService
├── viewmodels/ # NotaViewModel: estado reativo, ChangeNotifier, delega para Services
├── ui/ # Tudo relacionado à interface
│ ├── views/ # HomeView, EditorView: orquestram a tela
│ ├── components/ # Widgets reutilizáveis (home/ e editor/)
│ ├── styles/ # AppTheme: tokens implementados (cores, tipografia, raios, sombras, espaçamentos)
│ └── utils/ # Funções auxiliares puras (formatadores, etc.)
└── main.dart # Inicialização (Hive, Provider, app raiz)
O fluxo entre camadas:
View → ViewModel → Services → Repository → Hive
UI estado + regras acesso aos banco
notifica negócio dados
- Repository Pattern — abstração entre lógica e persistência
- Service Layer — regras de negócio isoladas do ViewModel (
TrashService,ArchiveService,NoteEditorService,FavoriteService,SearchService,CategoriaService) - Entity with ID —
Categoriaé uma entidade com ID próprio; notas armazenamcategoriaIds(lista de IDs), não os nomes — renomear uma categoria não exige atualizar as notas - Soft Delete — exclusão lógica com flag
isApagada+apagadaEm(timestamp para expiração de 30 dias) - Change Notifier — reatividade: Views escutam mudanças no ViewModel
- Debounce — salvamento automático após 5s de inatividade
- Injeção de Dependência — serviços e ViewModel recebem Repository no construtor
Uma nota tem três estados independentes:
isFavorita: marca como favorita (estrela)isArquivada: marca como arquivada (aba "Arquivo")isApagada: marca como deletada (aba "Lixeira")
Exemplo: uma nota pode estar isArquivada=true e depois ser isApagada=true.
Ao restaurar da lixeira, volta com isArquivada=true (lembra do estado anterior).
Pré-requisitos
- Flutter SDK (versão 3.12+)
- Google Chrome (para rodar no web)
Instalação
git clone https://github.com/rogial12/anotai.git
cd anotai
flutter pub get
flutter run -d chromeEstrutura do projeto
anotai/
├── lib/
│ ├── models/
│ │ ├── nota.dart # Modelo com toMap/fromMap; inclui categoriaIds
│ │ └── categoria.dart # Entidade Categoria com id + nome
│ ├── repositories/
│ │ ├── nota_repository.dart # Interface (contrato)
│ │ ├── local_nota_repository.dart # Implementação Hive
│ │ ├── categoria_repository.dart # Interface (contrato)
│ │ └── local_categoria_repository.dart # Implementação Hive (box 'categorias')
│ ├── services/
│ │ ├── trash_service.dart # Soft delete, restauração, exclusão permanente
│ │ ├── archive_service.dart # Arquivamento e desarquivamento
│ │ ├── note_editor_service.dart # Criação de nota vazia, salvamento
│ │ ├── favorite_service.dart # Toggle de favorita
│ │ ├── search_service.dart # Filtro em memória (stateless)
│ │ └── categoria_service.dart # Criar, renomear, deletar categorias
│ ├── viewmodels/nota_viewmodel.dart # Estado reativo, delega operações para Services
│ ├── ui/
│ │ ├── views/
│ │ │ ├── home_view.dart # Tela principal (3 abas)
│ │ │ └── editor_view.dart # Tela de edição (padrão "sempre editando")
│ │ ├── components/
│ │ │ ├── home/ # NoteTile, DockBar, HomeHeader, ChipBar,
│ │ │ │ # GerenciarCategoriasDialog, RenomearCategoriaDialog
│ │ │ └── editor/ # EditorHeader, CategoriasDialog
│ │ ├── styles/app_theme.dart # Tokens implementados: 16 cores, 13 estilos, raios, sombras, espaçamentos
│ │ └── utils/formatters.dart # Funções auxiliares puras: formatDate, wordCount, charCount
│ └── main.dart # Entry point
├── pubspec.yaml # Dependências (hive, provider)
├── docs/diagrama.md # Diagrama de classes (Mermaid)
└── README.md # Este arquivo
Diagrama de classes do projeto: ver docs/diagrama.md
- Estados independentes:
isFavorita,isArquivada,isApagada isApagada— soft delete com lixeira de 30 diasapagadaEm— timestamp de quando a nota foi para a lixeira; usado peloTrashService.limparExpiradas()- Serialização:
toMap()efromMap()para persistência Hive
- Novo:
_notaEmEdicao— rastreia nota em edição - Novos métodos:
desarquivarNota(),deletarPermanentemente(),setNotaEmEdicao() - Refatorado:
restaurarNota()— mantém estadoisArquivadaao restaurar - Refatorado: getters filtrados agora usam
isApagadaem lugar deapagadaEm
- HomeView: Menu dinâmico por aba,
PopupMenuButtonpara posicionamento correto - EditorView: Envolvida em
Consumerpara escutar mudanças, botão favorita funcional, bottom sheet de informações
| Fase | Feature | Status |
|---|---|---|
| Fase 2 | Busca por título/conteúdo | ✅ Concluído |
| Chips de categorias (inclui Favoritas) | ✅ Concluído | |
| Diálogo de confirmação + Undo de exclusão | ✅ Concluído | |
| Countdown de 30 dias na lixeira | ✅ Concluído | |
| Melhorias na UI — linguagem de design consistente | ✅ Concluído | |
| Contagem de caracteres/palavras na edição | ✅ Concluído | |
| Fase 3 | Exportação PDF | ⏳ Planejado |
| Exportação/backup manual | ⏳ Planejado | |
| Suporte a imagens nas anotações | ⏳ Planejado | |
| Lock/unlock (modo read-only) | ⏳ Planejado | |
| Histórico de versões (reverter estado) | ⏳ Planejado | |
| Anotações criptografadas | ⏳ Planejado | |
| Modo escuro (dark mode) | ⏳ Planejado | |
| Fase 4 | Autenticação biométrica (digital/face) | ⏳ Planejado |
| Sincronização entre dispositivos | ⏳ Planejado | |
| Resolução de conflitos de sincronização | ⏳ Planejado |
- EditorView: header sobreposto pela barra de sistema —
SafeArea(top: true)adicionado ao body - HomeHeader: wordmark quebrando linha — refatorado para
StatefulWidgetcom dois modos (normal/busca); wordmark com tamanho dinâmico viaMediaQuery - HomeView: espaçamento extra antes das tiles —
ListView.builderaplicando padding do sistema automaticamente
- Exportação PDF — último item formal do MVP pendente
- SettingsView — tela de configurações (botão existe no header, sem destino)
- Aba Favoritas — aba dedicada na DockBar para notas marcadas como favoritas
- Valores responsivos (clamp) —
AppThemeusa valores fixos intermediários; implementarMediaQuery-based clamp para fontes, paddings e espaçamentos
- Dialog "apagar nota esvaziada" — exibir confirmação quando o usuário apaga todo o conteúdo de uma nota existente; adiado até o histórico de versões estar implementado (sem ele, cancelar o dialog não restaura o conteúdo)
- Fase 2 foca em polimento do MVP (UX, busca, confirmações)
- Fase 3 adiciona features avançadas (histórico, criptografia, tags)
- Fase 4 expande para multiplataforma com sincronização
- Todas as fases mantêm compatibilidade com versões anteriores
- Commits direto na
maindurante MVP (migrar para feature branches + PRs na Fase 2) - Todos os métodos têm comentários explicativos (educacional)
- Estado reativo via
Consumer<NotaViewModel>— View não precisa saber de persistência - Testes ainda não implementados (foco em funcionalidade + aprendizado)