- Introduced SpoolPreset and User domain models with necessary DTOs and utility functions. - Created AuthRepository, FilamentRepository, and SpoolPresetRepository interfaces for authentication and data management. - Implemented UI components for filament display, including ColorSwatch, FilamentCard, and StockBar. - Developed layout components such as Header and Screen for consistent app structure. - Added reusable UI components like Badge, Button, Card, and Input for better user interaction. - Established global constants and theme settings for consistent styling across the application. - Implemented utility functions for filament calculations and formatting. - Created Zustand stores for managing authentication, filament, and preset states. - Configured TypeScript settings for improved development experience.
354 lines
16 KiB
Markdown
354 lines
16 KiB
Markdown
# MeowSpool Mobile — Agent Guide
|
|
|
|
## Visão Geral
|
|
|
|
Aplicação mobile do **MeowSpool** construída com **React Native** + **Expo** (SDK ~51), usando **expo-router** para navegação baseada em sistema de arquivos. A arquitetura espelha o backend Rust: **Clean Code / Hexagonal**, com camadas bem delimitadas do domínio até a apresentação.
|
|
|
|
O app é **offline-first**: todos os dados são persistidos localmente em **SQLite** (expo-sqlite + SQLCipher) e sincronizados com o backend Rust em background usando estratégia **last-write-wins** via campo `updatedAt`.
|
|
|
|
---
|
|
|
|
## Stack
|
|
|
|
| Componente | Biblioteca | Observação |
|
|
|-----------------------|------------------------------------|-----------------------------------------------|
|
|
| Framework | `expo` ~51 | Managed Workflow |
|
|
| Navigation | `expo-router` ~3 | File-system routing |
|
|
| Linguagem | TypeScript 5.x | strict mode |
|
|
| Node Version | 22 LTS | Gerenciado via `mise` (`.mise.toml`) |
|
|
| UI | React Native + `@expo/vector-icons`| Ionicons |
|
|
| Forms | `react-hook-form` + `zod` | Validação em runtime |
|
|
| Estado global | `zustand` | Stores em `src/store/` |
|
|
| HTTP | `axios` | Interceptor Bearer + refresh automático |
|
|
| DB local | `expo-sqlite` + SQLCipher | WAL mode, foreign keys |
|
|
| Auth persistência | `expo-secure-store` | JWT cifrado no keychain |
|
|
| Safe Area | `react-native-safe-area-context` | |
|
|
| Gesture Handler | `react-native-gesture-handler` | |
|
|
| Animations | `react-native-reanimated` | |
|
|
|
|
---
|
|
|
|
## Arquitetura de Camadas
|
|
|
|
```
|
|
src/
|
|
├── domain/ ← Entidades puras (sem dependências externas)
|
|
├── ports/ ← Interfaces/contratos (traits equivalentes ao Rust)
|
|
├── application/ ← Use Cases (orquestram domínio + ports)
|
|
├── adapters/
|
|
│ ├── remote/ ← Implementações HTTP (Axios → API Rust)
|
|
│ └── local/ ← Implementações SQLite (expo-sqlite)
|
|
├── store/ ← Estado em memória (Zustand) — cache das queries
|
|
├── shared/ ← Design tokens, constantes, utilitários
|
|
└── presentation/
|
|
└── components/ ← Componentes UI reutilizáveis
|
|
```
|
|
|
|
### Regra de dependência
|
|
|
|
```
|
|
presentation → store → application → ports ← adapters
|
|
↑
|
|
domain
|
|
```
|
|
|
|
Nenhuma camada interna importa de camadas externas. Os `adapters` implementam os `ports`.
|
|
|
|
---
|
|
|
|
## Estrutura de Arquivos
|
|
|
|
```
|
|
mobile/
|
|
├── .mise.toml ← node 22 LTS
|
|
├── app.json ← Expo config, scheme "meowspool"
|
|
├── babel.config.js ← module-resolver + reanimated
|
|
├── tsconfig.json ← aliases @domain, @ports, @application,
|
|
│ @adapters, @presentation, @store, @shared
|
|
├── index.js ← expo-router entry point
|
|
├── package.json
|
|
│
|
|
├── app/ ← expo-router file-system routes
|
|
│ ├── _layout.tsx ← Root layout (carrega sessão)
|
|
│ ├── (auth)/
|
|
│ │ ├── _layout.tsx ← Redireciona se já autenticado
|
|
│ │ ├── login.tsx ← G3-0
|
|
│ │ ├── register.tsx ← IX-0
|
|
│ │ ├── forgot-password.tsx ← L2-0
|
|
│ │ ├── verify-email.tsx ← RU-0
|
|
│ │ ├── reset-password.tsx ← 117-0
|
|
│ │ └── password-reset-done.tsx ← 135-0
|
|
│ └── (app)/
|
|
│ ├── _layout.tsx ← Stack autenticado
|
|
│ ├── (tabs)/
|
|
│ │ ├── _layout.tsx ← Bottom tabs: Início|Estoque|[+]|Config|Perfil
|
|
│ │ ├── home.tsx ← 1-0 Dashboard
|
|
│ │ ├── inventory.tsx ← CW-0 Inventário
|
|
│ │ ├── add.tsx ← FAB → redireciona para /inventory/new
|
|
│ │ ├── config.tsx ← M4-0 Presets de Carretéis
|
|
│ │ └── profile.tsx ← 1LQ-0 Perfil
|
|
│ ├── inventory/
|
|
│ │ ├── new.tsx ← 2X-0 Cadastro de Filamento
|
|
│ │ ├── filters.tsx ← 17H-0 Filtros (bottom sheet)
|
|
│ │ ├── [id].tsx ← 6L-0 Detalhe do Filamento
|
|
│ │ └── [id]/
|
|
│ │ └── edit.tsx ← 13O-0 Editar Filamento
|
|
│ ├── config/
|
|
│ │ └── presets/
|
|
│ │ ├── new.tsx ← QM-0 Novo Preset
|
|
│ │ └── [id]/
|
|
│ │ └── edit.tsx ← 1KD-0 Editar Preset
|
|
│ └── filaments/
|
|
│ └── [id]/
|
|
│ ├── qrcode.tsx ← 1CF-0 Ver QR Code
|
|
│ └── label.tsx ← 1FZ-0 Exportar Etiqueta SVG
|
|
│
|
|
└── src/
|
|
├── domain/
|
|
│ ├── Filament.ts ← interface Filament, calcNetWeight
|
|
│ ├── SpoolPreset.ts ← interface SpoolPreset, isUserOwnedPreset
|
|
│ ├── User.ts ← interface User, AuthSession, DTOs
|
|
│ └── Dashboard.ts ← DashboardData, MaterialSummary, etc.
|
|
├── ports/
|
|
│ ├── FilamentRepository.ts ← interface IFilamentRepository
|
|
│ ├── SpoolPresetRepository.ts ← interface ISpoolPresetRepository
|
|
│ └── AuthRepository.ts ← interface IAuthRepository
|
|
├── application/
|
|
│ ├── filament/
|
|
│ │ ├── CreateFilamentUseCase.ts
|
|
│ │ ├── UpdateFilamentUseCase.ts
|
|
│ │ ├── ListFilamentsUseCase.ts
|
|
│ │ └── DeleteFilamentUseCase.ts
|
|
│ ├── preset/
|
|
│ │ └── PresetUseCases.ts ← List, Create, Update, Delete
|
|
│ └── auth/
|
|
│ └── AuthUseCases.ts ← Login, Register, Google, Logout, etc.
|
|
├── adapters/
|
|
│ ├── remote/
|
|
│ │ ├── httpClient.ts ← Axios + interceptors JWT + refresh
|
|
│ │ ├── ApiAuthRepository.ts ← /api/v1/auth/*
|
|
│ │ ├── ApiFilamentRepository.ts ← /api/v1/filaments/*
|
|
│ │ └── ApiSpoolPresetRepository.ts ← /api/v1/spool-presets/*
|
|
│ └── local/
|
|
│ ├── database.ts ← init SQLite, WAL, foreign keys, tabelas
|
|
│ ├── LocalFilamentRepository.ts
|
|
│ └── LocalSpoolPresetRepository.ts
|
|
├── store/
|
|
│ ├── authStore.ts ← Zustand: sessão, SecureStore
|
|
│ ├── filamentStore.ts ← Zustand: lista + filtros em memória
|
|
│ └── presetStore.ts ← Zustand: systemPresets + userPresets
|
|
├── shared/
|
|
│ ├── theme.ts ← Design tokens (cores, tipografia, espaçamento)
|
|
│ ├── constants.ts ← API_BASE_URL, SecureStore keys, MATERIALS
|
|
│ └── utils/
|
|
│ └── filament.ts ← calcFilamentPercentage, formatWeight, etc.
|
|
└── presentation/
|
|
└── components/
|
|
├── ui/
|
|
│ ├── Button.tsx ← primary, secondary, ghost, danger
|
|
│ ├── Input.tsx ← label, leftIcon, rightLabel, isPassword, error
|
|
│ ├── Card.tsx ← container surface com borda
|
|
│ └── Badge.tsx ← badge dinâmico de estoque
|
|
├── layout/
|
|
│ ├── Screen.tsx ← SafeArea + scroll + keyboardAvoiding
|
|
│ └── Header.tsx ← título centralizado, back, slot direito
|
|
└── filament/
|
|
├── StockBar.tsx ← barra de progresso + badge numérico
|
|
├── ColorSwatch.tsx ← quadrado/círculo com cor hex
|
|
└── FilamentCard.tsx ← card de lista
|
|
```
|
|
|
|
---
|
|
|
|
## Design Tokens
|
|
|
|
Arquivo: `src/shared/theme.ts`
|
|
|
|
### Paleta (siamês)
|
|
|
|
| Token | Valor | Uso |
|
|
|------------------|-------------|--------------------------------------|
|
|
| `bgBase` | `#1E1B18` | Fundo geral |
|
|
| `bgSurface` | `#2A2622` | Cards, containers |
|
|
| `bgHover` | `#332F2B` | Hover/pressed em cards |
|
|
| `textPrimary` | `#F5EEDC` | Títulos, peso líquido |
|
|
| `textSecondary` | `#C9C1B0` | Descrições, labels |
|
|
| `accent` | `#38BCC2` | Botões de ação, elementos ativos |
|
|
| `accentMuted` | `#38BCC226` | Background de badges accent (10%) |
|
|
| `stockLow` | `#FF6B6B` | ≤ 15% — vermelho |
|
|
| `stockMedium` | `#FF9F43` | ≤ 35% — laranja |
|
|
| `stockOk` | `#38BCC2` | > 35% — accent |
|
|
| `border` | `#3D3830` | Bordas sutis |
|
|
| `error` | `#FF6B6B` | Mensagens de erro |
|
|
|
|
### Tipografia
|
|
|
|
- UI: **Inter**
|
|
- Mono (slugs, hex, URLs): **JetBrains Mono**
|
|
- App é **dark-mode exclusivo** — nenhum suporte a light mode
|
|
|
|
---
|
|
|
|
## Navegação (expo-router)
|
|
|
|
### Estrutura de grupos
|
|
|
|
```
|
|
(auth) ← sem autenticação; redireciona para (app) se sessão válida
|
|
(app) ← com autenticação; redireciona para (auth) se sem sessão
|
|
(tabs) ← bottom tabs fixos
|
|
```
|
|
|
|
### Deep links
|
|
|
|
- Scheme: `meowspool://`
|
|
- Filamento: `meowspool://filament/<id>` → `/(app)/inventory/<id>`
|
|
- QR público: `meowspool.app/f/<slug>` (web)
|
|
|
|
---
|
|
|
|
## Estratégia Offline-First
|
|
|
|
1. **Escrita**: toda mutação persiste primeiro no SQLite (`synced = 0`) e enfileira em `sync_queue`.
|
|
2. **Leitura**: lê sempre do SQLite; a API é usada apenas para sync.
|
|
3. **Sync**: background job consome `sync_queue` e envia para o backend Rust.
|
|
4. **Conflito**: last-write-wins via `updatedAt` (ISO 8601). O backend é a fonte de verdade em conflitos.
|
|
5. **Auth offline**: token armazenado no `expo-secure-store`; refresh automático via interceptor Axios.
|
|
|
|
### Schema SQLite
|
|
|
|
```sql
|
|
-- filaments
|
|
id TEXT PRIMARY KEY, user_id TEXT, material TEXT, brand TEXT, model TEXT,
|
|
color_hex TEXT, spool_preset_id TEXT, total_weight_g REAL, net_weight_g REAL,
|
|
temp_hotend_c REAL, temp_bed_c REAL, flow_factor_pct REAL, notes TEXT,
|
|
synced INTEGER DEFAULT 0, updated_at TEXT, created_at TEXT
|
|
|
|
-- spool_presets
|
|
id TEXT PRIMARY KEY, user_id TEXT, name TEXT, spool_weight_g REAL,
|
|
is_system INTEGER DEFAULT 0, synced INTEGER DEFAULT 0, created_at TEXT
|
|
|
|
-- sync_queue
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT, entity_type TEXT, entity_id TEXT,
|
|
operation TEXT, payload TEXT, created_at TEXT
|
|
```
|
|
|
|
---
|
|
|
|
## Convenções de Código
|
|
|
|
### Nomenclatura
|
|
|
|
- **Componentes React**: PascalCase (`FilamentCard.tsx`)
|
|
- **Hooks**: camelCase com prefixo `use` (`useFilamentStore`)
|
|
- **Use Cases**: PascalCase com sufixo `UseCase` (`CreateFilamentUseCase`)
|
|
- **Repositórios**: PascalCase com prefixo de implementação (`ApiFilamentRepository`, `LocalFilamentRepository`)
|
|
- **Stores**: camelCase (`filamentStore.ts`), exportados como `useXxxStore`
|
|
|
|
### Imports
|
|
|
|
Sempre usar aliases em vez de caminhos relativos:
|
|
|
|
```ts
|
|
import { Filament } from '@domain/Filament';
|
|
import { useFilamentStore } from '@store/filamentStore';
|
|
import { colors } from '@shared/theme';
|
|
import { Button } from '@presentation/components/ui/Button';
|
|
```
|
|
|
|
### Componentes de tela
|
|
|
|
- Toda tela usa `SafeAreaView` com `backgroundColor: colors.bgBase`
|
|
- Header manual (não usa o header do expo-router): `headerShown: false`
|
|
- CTA fixo: `View` com `padding: spacing[5]` + `borderTopWidth: 1` + `borderTopColor: colors.border`
|
|
- ScrollViews com `showsVerticalScrollIndicator={false}` e `keyboardShouldPersistTaps="handled"`
|
|
|
|
### Formulários
|
|
|
|
- `react-hook-form` + `zodResolver` para todos os formulários
|
|
- Validações no schema Zod (nunca inline)
|
|
- `isLoading` local no componente durante submit
|
|
- Erros de API exibidos via `Alert.alert`
|
|
|
|
---
|
|
|
|
## API do Backend (rotas relevantes)
|
|
|
|
Base: `EXPO_PUBLIC_API_URL` (padrão: `http://localhost:3000/api/v1`)
|
|
|
|
### Auth
|
|
|
|
| Método | Rota | Descrição |
|
|
|--------|-------------------------------|------------------------------|
|
|
| POST | `/auth/login` | Login email/senha |
|
|
| POST | `/auth/register` | Cadastro |
|
|
| POST | `/auth/google` | OAuth Google |
|
|
| POST | `/auth/refresh` | Refresh token |
|
|
| POST | `/auth/logout` | Logout |
|
|
| POST | `/auth/forgot-password` | Solicitar reset de senha |
|
|
| POST | `/auth/reset-password` | Confirmar reset com token |
|
|
| POST | `/auth/verify-email` | Verificar e-mail com código |
|
|
|
|
### Filaments
|
|
|
|
| Método | Rota | Descrição |
|
|
|--------|-------------------------------|------------------------------|
|
|
| GET | `/filaments` | Listar (com filtros) |
|
|
| POST | `/filaments` | Criar |
|
|
| GET | `/filaments/:id` | Detalhe |
|
|
| PATCH | `/filaments/:id` | Atualizar |
|
|
| DELETE | `/filaments/:id` | Excluir |
|
|
| GET | `/filaments/:id/qrcode` | QR Code SVG |
|
|
| GET | `/filaments/:id/label` | Etiqueta SVG |
|
|
|
|
### Spool Presets
|
|
|
|
| Método | Rota | Descrição |
|
|
|--------|-------------------------------|------------------------------|
|
|
| GET | `/spool-presets` | Listar (sistema + usuário) |
|
|
| POST | `/spool-presets` | Criar preset do usuário |
|
|
| PATCH | `/spool-presets/:id` | Atualizar preset do usuário |
|
|
| DELETE | `/spool-presets/:id` | Excluir preset do usuário |
|
|
|
|
### Dashboard
|
|
|
|
| Método | Rota | Descrição |
|
|
|--------|-------------------------------|------------------------------|
|
|
| GET | `/dashboard` | Dados agregados do dashboard |
|
|
|
|
---
|
|
|
|
## Próximos Passos (integrações pendentes)
|
|
|
|
- [ ] Instanciar e injetar repositórios concretos nos Use Cases (DI container simples ou Context)
|
|
- [ ] Implementar sync background com `sync_queue` SQLite → API
|
|
- [ ] Integrar `react-native-qrcode-svg` para render real do QR Code
|
|
- [ ] Gerar SVG de etiqueta (integração com `/filaments/:id/label` do backend)
|
|
- [ ] Expo Notifications para alertas de estoque baixo
|
|
- [ ] Google OAuth com `expo-auth-session`
|
|
- [ ] Testes de integração com Jest + Testing Library
|
|
|
|
---
|
|
|
|
## Executando o Projeto
|
|
|
|
```bash
|
|
# Instalar dependências (na pasta mobile/)
|
|
mise install # garante Node 22
|
|
npm install
|
|
|
|
# Iniciar servidor de desenvolvimento
|
|
npx expo start
|
|
|
|
# iOS
|
|
npx expo run:ios
|
|
|
|
# Android
|
|
npx expo run:android
|
|
```
|
|
|
|
> **Variável de ambiente**: crie `mobile/.env` com:
|
|
> ```
|
|
> EXPO_PUBLIC_API_URL=http://localhost:3000/api/v1
|
|
> ```
|